Overview
The hard part of IoT is rarely the hardware. It is the firmware. Off-the-shelf gateways ship as black boxes: you get whatever protocols, cloud endpoints, and update story the vendor chose, and little room to change them. The moment a real project needs custom behaviour, you are writing firmware for two different chips, in two different toolchains, that have to agree on a wire protocol between them. That is where projects stall. This walkthrough follows one project across the whole series. From a single written spec, the agent planned the architecture, wrote both codebases, built them with the real toolchains, fixed its own build errors, flashed both chips, and verified the result on live hardware, in under 30 minutes on the video. The same session then went two steps further: a radio bug that no amount of code reading could find, debugged over the air, and a CRA readiness check on the firmware it had just built. Both have their own chapter below.The hardware

The BWG840X as it ships
- Nordic nRF52840 (BT840XE module, with a SKY66112 power amplifier and external antenna): runs a BLE scanner on Zephyr, using the nRF Connect SDK. It listens for advertisements from nearby BLE sensors.
- Espressif ESP32 (ESP32-WROOM-32D): handles WiFi, the MQTT client, and an embedded web dashboard, on ESP-IDF. It is the uplink to the network and the cloud.

IOT-BWG840-V1, opened: the BT840XE on the radio side, the ESP32-WROOM-32D on the uplink side
P1.01=RX, P1.02=TX, cross-connected to the ESP32 UART0, GPIO1=TX, GPIO3=RX, via the IOT-JS5 jumper). The nRF52840 is the sensor-facing radio; the ESP32 is the network-facing uplink. Neither chip does the other’s job, and that UART link is the seam where a custom protocol has to be designed and kept in sync on both sides.
PA=P0.17, LNA=P0.19, CPS=P0.06, CHL=P0.08), or the radio range collapses. TX power is capped at +2 dBm (FCC) and -4 dBm (CE). The status LEDs and button are all active-low: blue LED1 on P0.11, red LED2 on P1.00 (which doubles as SWO, so trace must stay disabled), and the button on P0.16.From Fanstel
Fanstel publish the designs for this board, which is why the board details below can be checked rather than taken on trust. All of these are on their open-source download page, with no login and no form:Flashing the two chips
They flash by different paths, and the inter-chip UART matters here:- ESP32: the IOT-UART-ESP-V1 bridge board replaces the jumper (JS2 connector is PC-to-ESP32), and
idf.py flash monitorauto-programs. While the bridge is in, the two chips are disconnected from each other. - nRF52840: SWD over the TC2050 Tag-Connect pads into a nRF52840DK Debug Out (
west flash). RTT logs stay available even with the jumper installed, so the nRF side is observable during integration.
Data flow
The path a single reading takes, end to end:- A BLE sensor broadcasts an advertisement.
- The nRF52840 scanner (Zephyr) receives it and forwards every advertisement (MAC, RSSI, raw payload) downstream. It does no filtering.
- The nRF52840 sends it across the UART bridge as a framed packet (start byte, length, and CRC).
- The ESP32 (ESP-IDF) validates the frame, filters by the configured MAC whitelist, and publishes to MQTT (a topic per device MAC), in either a RAW hex or a PARSED payload mode.
- The reading reaches the MQTT broker, and in parallel is visible on the ESP32’s embedded web dashboard at
http://bwg840-XXXX.localover the local network.
Architecture
The end-to-end flow, from BLE advertisement to MQTT, covering first-boot provisioning, the continuous scan loop, the button config reset, and the WiFi-fail fallback: The build produced two separate firmware projects that agree on one wire protocol:- BLE scanner (nRF52840, Zephyr / nRF Connect SDK): a continuous, deliberately simple scanner that forwards every advertisement (MAC, RSSI, raw payload) downstream. All filtering lives on the ESP32 side, so the radio stays reliable and dumb.
- UART framed-packet protocol: a framed byte protocol between the two chips, with a start byte, a length, and a CRC, so the ESP32 can reject corrupt or partial frames rather than trusting the bus. It also carries a status frame, so both chips share the gateway’s global state (which drives the LED table below). Never raw prints between the chips.
- First-boot provisioning (ESP32, ESP-IDF): on an empty NVS the ESP32 comes up as a WiFi Access Point (
BWG840-XXXX, derived from its MAC) serving a setup dashboard for WiFi credentials, the MQTT broker, the sensor MAC whitelist, and the payload mode. Save and reboot persists to NVS and switches to Station mode. - Embedded web server and dashboard (ESP32, ESP-IDF): in Station mode the dashboard stays reachable over mDNS at
http://bwg840-XXXX.local, with Status, Config, and Live Logs tabs. It is a dark, card-based UI with all assets embedded in the firmware, so it renders perfectly offline in AP mode where there is no internet, and it is usable from a phone. - Two payload modes: readings publish as RAW (a JSON of MAC, RSSI, timestamp, and raw advertisement hex) or PARSED (decoded standard BLE AD structures: device name, TX power, manufacturer ID, iBeacon, Eddystone, service data). Parsing is generic BLE only, with no vendor-specific decoding.
- MQTT client with reconnect and backoff (ESP32, ESP-IDF): the cloud uplink, written to survive a flaky network by reconnecting with backoff rather than giving up on the first drop.
- Button config reset: a 10-second button hold makes the nRF52840 send a config-reset frame, and the ESP32 returns to AP mode with the previous config pre-filled as placeholders, so a single field can be changed and saved. Both LEDs flash three times to acknowledge.
- Resilience and logging on both sides: a fed task watchdog on the ESP32, reset-on-fatal-error on the nRF52840, reconnect and backoff throughout, and a WiFi-fail-to-AP fallback so bad credentials never brick the gateway. Logging stays at INFO and rate-limited, with no per-advertisement printing in normal operation, and DEBUG enabled only per module when something needs a closer look.
LED status
The two on-board LEDs report state, with blue for mode and connectivity, and red for problems only:The prompt
This is the complete spec used in the video. Copy it, adapt the board and behaviour to your own project, and run it in the extension.What the agent did, step by step
Loaded curated firmware knowledge
Planned the architecture
Wrote code for both projects
Built with the real toolchains
Self-corrected build errors
Flashed both chips
Verified on hardware
Where this build goes next
The same session carried on twice more, and each has its own chapter.2 · Troubleshooting
3 · CRA readiness
Reproduce it yourself
Prerequisites
- VS Code
- Adsum IoT Coder, from the Marketplace or Open VSX. The free tier needs no key and no account.
- nRF Connect SDK and its toolchain, for the nRF52840 side. See platforms.
- ESP-IDF, for the ESP32 side.
- A Fanstel BWG840, or any comparable dual-chip board pairing an nRF52 with an ESP32 over UART.
Steps
Install the extension and open a workspace
Paste the spec
Approve the build for each chip
Flash and verify on hardware
idf.py flash monitor, and the nRF52840 over SWD with west flash, with RTT for its logs. Approve the flash for each chip, then confirm the BLE-to-UART-to-MQTT path on live hardware, watching the nRF RTT logs and the dashboard Live Logs tab.Then go further, as the videos did
Before it leaves the bench
The firmware above is a working gateway built for a bench. Taking it to a production unit is three short jobs, each one the agent can do in the same project when you ask for it by name:Feed a hardware watchdog on the nRF52840
Feed a hardware watchdog on the nRF52840
CONFIG_RESET_ON_FATAL_ERROR=y catches faults, but no WDT device is opened and fed yet. A few lines in Zephyr; ask for the wdt0 node to be enabled and fed from the scanner loop.Give the gateway its own board definition
Give the gateway its own board definition
nrf52840dk_nrf52840, with the DK used purely as an SWD probe into the BT840XE over TC2050. It runs on the gateway’s own silicon, and a production port starts with a custom board file so the LED, button and PA pins live in the devicetree rather than in the application.Set your own DFU pairing password
Set your own DFU pairing password
CONFIG_MCUMGR_SMP_BT_AUTHEN_PW_VAL is a fixed value in this build. Change it before a unit ships. The CRA readiness check in chapter three lists it as a finding, which is exactly what the check is for.Verified with
Links
- The series: full playlist · Episode 1, the build · Episode 2, the over-the-air debug · Episode 3, the CRA check · the one-minute cut
- Source and issues: github.com/adsumnetworks/Adsum-IoT-Coder
- Install: VS Code Marketplace or Open VSX

