Projects Embedded

PacketScope — Wi-Fi Network Analyzer

A handheld dual-band Wi-Fi analyzer on the BW16 (RTL8720DN) with a 1.14" TFT — channel heatmaps, AP discovery, and signal logging for fixing dead spots.

Prototype since Jan 2026 #wifi#rtl8720dn#embedded#rf-analysis
2
Bands scanned
39
Channels covered
4.2 s
Full scan time
9 h
Battery runtime

PacketScope exists because my home network had a dead spot I couldn’t explain, and every phone app I tried only showed 2.4 GHz or hid the data behind a subscription. The BW16 module is one of the cheapest ways to get real dual-band scanning — the RTL8720DN inside sees both 2.4 and 5 GHz for about $4 — so I built a handheld analyzer around it. It’s strictly a defensive and diagnostic tool: it listens, it never transmits attacks.

Dual-band scanning on the RTL8720DN

The AmebaD SDK exposes a scan API that returns SSID, BSSID, channel, and RSSI per AP. A full pass over 13 channels of 2.4 GHz plus the 5 GHz UNII bands takes about 4.2 seconds. Occupancy is estimated by binning every discovered AP’s RSSI into its channel plus the adjacent channels it bleeds into (20 MHz channels on 2.4 GHz overlap four neighbors each side). One hard-won lesson: the Arduino AmebaD board package version matters enormously — a minor version bump silently broke low-level scan callbacks and cost me a weekend of “why does it freeze” debugging before I pinned the toolchain to 3.1.8.

Rendering heatmaps on a 1.14” TFT

The ST7789 panel is 240×135 pixels, which is not much room for 39 channels of history. The heatmap draws each channel as a 5-pixel column and scrolls time vertically, with occupancy mapped through a 16-entry color LUT precomputed in RGB565. Full-screen redraws flickered badly at first; the fix was drawing only the new scanline into a small buffer and pushing it with a single windowed SPI transfer:

// Push one heatmap row (39 channels x 5 px) in a single window write.
tft.setAddrWindow(x0, row, CHANNELS * COL_W, 1);
for (int c = 0; c < CHANNELS; c++) {
  uint16_t color = heatLUT[occupancy[c] >> 4];
  for (int p = 0; p < COL_W; p++) lineBuf[c * COL_W + p] = color;
}
tft.writePixels(lineBuf, CHANNELS * COL_W);

That dropped a full frame update from ~180 ms to under 8 ms and eliminated the tearing.

Battery design

A 1200 mAh single-cell LiPo through a TP4056 charge board runs the whole stack — BW16, backlit TFT, and a resistor-divider fuel gauge on an ADC pin — for about nine hours of continuous scanning. Dimming the backlight between scan cycles bought roughly two of those hours. The current build is a perfboard sandwich in a 3D-printed shell; a proper PCB with a ground plane that doesn’t sit under the 5 GHz antenna is next, because the perfboard layout measurably drags down 5 GHz sensitivity.

Development timeline

  1. 2026-01

    Breadboard bring-up

    BW16 scanning both bands and dumping AP lists over serial.

  2. 2026-02

    TFT UI

    ST7789 driver working with a paged UI and the first heatmap render.

  3. 2026-04

    Battery build

    Moved to a LiPo + TP4056 perfboard stack in a printed enclosure.

  4. 2026-06

    Site survey mode

    RSSI logging to flash while walking the house, one sample per second.