Networking (WiFi & HTTP)

WiFi and HTTP are thin classes: new WiFi(...) carries the whole link policy (credentials, security, timeout), and new Request(...) carries the whole request (method, URL, body, TLS policy). Every method lowers onto Zephyr’s own networking — wifi_mgmt for the link, sockets and http_client for requests. https:// requests are encrypted.

The same calls work three ways — blocking at the top level, fire-and-forget with callbacks, or async/await for programs that need to keep doing other things while the link comes up.

WiFi and HTTP require a networked target — an ESP32 board on the Zephyr framework (esp32_devkitc, esp32s3_devkitc). They compile-time-error on boards without a radio, including the nRF52840-based XIAO.


Connecting to WiFi

Basic connect

Construct the link policy, then join(). The constructor carries the credentials; join() associates and waits — bounded by timeoutMs (default 15 s) — until the board has an IP address.

import { WiFi } from '@typecad/hal';
import { UART0 } from '@typecad/board';

const wifi = new WiFi('MyNetwork', { psk: 'correct-horse-battery-staple' });
if (wifi.join()) {
  UART0.writeLine(wifi.ip());        // the IP address as a string
}

Omit psk for an open network. Security defaults to WPA2 when a psk is present and OPEN otherwise; override it with the WiFi.WPA3 / WiFi.WPA2_WPA3 tokens.

Static IP and radio tuning

Both are construction facts, set at the same time as the credentials:

const lab = new WiFi('LabNet', {
  psk: 'correct-horse-battery-staple',
  timeoutMs: 30000,                                        // 30 s deadline
  ipv4: { addr: '10.0.0.5', gateway: '10.0.0.1', netmask: '255.255.255.0' },
  powerSave: WiFi.PS_OFF,   // disable modem sleep for lower latency
  channel: 6,               // 0 / omitted = any
});

With static ipv4 facts the link comes up without DHCP — join() returns as soon as the address is applied.

Async connect

Inside an async function, the join splits into a non-blocking start plus a poll, so other tasks keep running while the station associates:

const wifi = new WiFi('MyNetwork', { psk: 'hunter22' });

async function network() {
  wifi.joinStart();                       // fire-and-forget associate
  while (!wifi.linked()) { await Time.sleep(100); }
  UART0.writeLine(wifi.ip());
}

Link events

wifi.onUp(() => UART0.writeLine('online at ' + wifi.ip()));
wifi.onDrop(() => UART0.writeLine('link lost'));   // safe to call join() from here
wifi.join();

Scanning

scan() performs one blocking scan and returns a read-only handle over a fixed pool of 16 results:

const results = wifi.scan();
for (let i = 0; i < results.count(); i++) {
  UART0.writeLine(results.ssid(i) + '  ' + results.rssi(i) + ' dBm  ch' + results.channel(i));
}

Access point mode

Bring the radio up as an access point for provisioning:

import { WiFiAP } from '@typecad/hal';

const ap = new WiFiAP('cuttlefish-setup', { psk: 'config123', channel: 6 });
ap.start();
// …provisioning…
ap.stop();

HTTP — Request

Request is the HTTP/S client. Construction carries method, URL, timeout, body, and TLS policy; header() chains request headers; send() performs the request and leaves the response in the client until the next send.

import { Request } from '@typecad/hal';

const req = new Request('GET', 'http://192.168.2.184:8080/health');
req.header('X-Device', 'cuttlefish');
if (req.send()) {
  UART0.writeLine(req.status());              // 200
  UART0.writeLine(req.ok());                  // true for any 2xx
  UART0.writeLine(req.text());                // the body, until the next send
  UART0.writeLine(req.responseHeader('Content-Type'));
}

Method tokens and chaining:

new Request(Request.POST, 'http://api.local/telemetry', {
  body: '{"temp":21.5}',
  json: true,                             // sets Content-Type: application/json
  timeoutMs: 5000,
}).header('Authorization', 'Bearer x').send();

HTTPS and certificate pinning

HTTPS is native. The CA policy is a construction fact:

const pinned = new Request(Request.GET, 'https://internal.corp/api', { caCert: CORP_CA_PEM });
const lab    = new Request(Request.GET, 'https://lab-server.local', { insecure: true });

caCert pins a specific CA (a PEM string — it is converted to binary form at build time, so no PEM parser ships on the board). insecure: true encrypts but skips verification; do not ship it in production.

Async requests

Inside an async function, await req.send() splits into send-start plus done-polling — a slow request never stalls the rest of the program:

async function pollCloud() {
  while (true) {
    const req = new Request(Request.GET, 'http://api.local/health');
    await req.send();
    UART0.writeLine(req.status());
    await Time.sleep(5000);
  }
}

Put the await on its own statement. A value-position await (const ok = await req.send()) cannot suspend mid-expression and falls back to the blocking form.

⚙️ If the board reboots right after joining

Some ESP32-S3 dev kits reboot the moment the radio transmits, with E BOD: Brownout detector was triggered in the serial log. The power amplifier draws a brief current spike on the first probe frame, and on a marginal USB supply (a long cable, a weak charger, a cheap regulator) that spike droops the 3.3 V rail below the brownout threshold.

The workaround is a lower transmit power while diagnosing. The root cause is the power supply, not firmware — for production, fix the rail (shorter cable, beefier 5 V source, bulk capacitance near the module).


Build-time checks

Two checks run before you flash. Using WiFi, Request, or Mqtt on a board with no WiFi radio (the nRF52840-based XIAO, for example) is a build error naming an ESP32 target. And a program that issues HTTP requests without ever bringing a WiFi link up (join() or an AP) is flagged with a build-time warning — every request would fail at runtime.


API Reference

WiFi

MemberDescription
new WiFi(ssid, opts?)Link policy: psk, security, channel, band, timeoutMs (default 15 s), powerSave, ipv4 { addr, gateway, netmask }.
join()Associate and wait for IP connectivity. Awaitable.
joinStart()Fire-and-forget associate — poll linked().
leave()Disassociate.
linked() / rssi() / ip() / mac()Link state and info.
onUp(fn) / onDrop(fn)Link-up / link-down callbacks.
scan()One blocking scan; results via the returned handle. Awaitable.

Request

MemberDescription
new Request(method, url, opts?)timeoutMs (default 10 s), body, json, insecure, caCert.
Request.GET / POST / PUT / DELETE / HEAD / PATCHMethod tokens.
header(name, value)Attach a request header; chainable.
send()Perform the request. Awaitable.
status() / ok() / text()Response status, 2xx check, body (until the next send).
contentLength() / responseHeader(name)Response metadata.