> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adsumnetworks.com/llms.txt
> Use this file to discover all available pages before exploring further.

# BLE-to-WiFi industrial gateway

> Build production BLE-to-WiFi gateway firmware for the Fanstel BWG840 with a single spec. Two chips, two toolchains, one working session.

Build production BLE-to-WiFi gateway firmware for the Fanstel BWG840 with a single spec.

<iframe className="w-full aspect-video rounded-xl my-6" src="https://www.youtube.com/embed/WrRYutpmHi8" title="Building BLE-to-WiFi gateway firmware with Adsum IoT Coder" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen />

## 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 from the launch video. Starting from a single written spec, Adsum IoT Coder built the complete firmware for a production industrial gateway, the Fanstel BWG840, in one working session. The video shows it done in under 30 minutes. 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. The developer reviewed and approved each step along the way.

<Note>
  Adsum is an AI-based coding agent and can make mistakes. It worked from a spec and proposed each step; the developer approved the plan, the builds, and the flashes. This is an assisted build, not an unattended one.
</Note>

## The hardware

The [Fanstel BWG840](https://www.fanstel.com/wifi-ble-5-iot-gateway) (board IOT-BWG840-V1) is a dual-chip industrial gateway. Two SoCs share one board and split the work:

* **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.

The two chips talk to each other over **UART at 115200 baud** (nRF `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 the UART link between them is the seam where a custom protocol has to be designed and kept in sync on both sides.

<Note>
  The SKY66112 power amplifier is not optional. It must be initialised through the nRF PA/LNA config (`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`.
</Note>

### 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 monitor` auto-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:

1. A BLE sensor broadcasts an **advertisement**.
2. The **nRF52840** scanner (Zephyr) receives it and forwards every advertisement (MAC, RSSI, raw payload) downstream. It does no filtering.
3. The nRF52840 sends it across the **UART bridge** as a framed packet (start byte, length, and CRC).
4. 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.
5. The reading reaches the **MQTT broker**, and in parallel is visible on the ESP32's **embedded web dashboard** at `http://bwg840-XXXX.local` over 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:

```mermaid theme={null}
sequenceDiagram
    participant BLE as BLE Devices (tags/sensors)
    participant NRF as nRF52840 (Scanner)
    participant ESP as ESP32 (Gateway)
    participant MQTT as MQTT Broker
    participant UI as User Browser

    Note over NRF: Init SKY66112 PA/LNA
    Note over ESP: Boot, check NVS

    alt First boot (empty NVS)
        ESP->>ESP: Start AP named BWG840-XXXX
        UI->>ESP: Connect to AP
        UI->>ESP: Open dashboard, configure WiFi + MQTT
        UI->>ESP: Save and Reboot
        ESP->>ESP: Persist to NVS, reboot
    end

    ESP->>ESP: Connect WiFi (STA) + start mDNS
    ESP->>MQTT: Connect to broker
    UI->>ESP: Dashboard at http://bwg840-XXXX.local

    loop Continuous
        BLE-->>NRF: Advertisement (MAC + RSSI + raw data)
        NRF->>ESP: Framed UART STX/LEN/CRC ADV report
        ESP->>ESP: Filter by MAC whitelist
        ESP->>MQTT: Publish to topic per MAC (RAW or PARSED)
    end

    alt Button 10s hold
        NRF->>ESP: Config reset frame
        ESP->>ESP: Reboot to AP mode (old config pre-filled)
    end

    alt WiFi fails repeatedly
        ESP->>ESP: Auto-fallback to AP setup mode
    end
```

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**: hardware and task watchdogs, 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:

| Gateway state                                 | Blue LED1                                   | Red LED2   |
| --------------------------------------------- | ------------------------------------------- | ---------- |
| AP setup mode                                 | slow blink (1 Hz)                           | off        |
| Connecting to WiFi                            | fast blink (4 Hz)                           | off        |
| WiFi OK, MQTT connecting or down              | solid on                                    | slow blink |
| Normal operation (WiFi + MQTT OK)             | solid on                                    | off        |
| WiFi lost, retrying                           | off                                         | fast blink |
| Internal fault (UART link dead, scanner down) | off                                         | solid on   |
| Config reset accepted (10 s hold)             | three flashes together, then the AP pattern |            |

## 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.

```text Professional BLE-to-WiFi Gateway (Ep1) theme={null}
Build a production-grade BLE-to-MQTT gateway on my Fanstel BWG840 board
(nRF52840 + ESP32, hardware details at the end). Two firmware projects.

ESP32 (ESP-IDF):

1. First boot (empty NVS) → WiFi Access Point "BWG840-XXXX" (XXXX from MAC)
   with a web dashboard to configure: WiFi credentials, MQTT broker
   (host, port, user, password, topic prefix), sensor MAC whitelist,
   and payload mode (see 4).
2. "Save & Reboot" → persist all to NVS, reboot into Station mode.
3. Station mode: connect WiFi + MQTT, dashboard stays reachable at
   http://bwg840-XXXX.local (mDNS) with tabs: Status, Config, Live Logs.
4. Receive BLE advertisements from the nRF52840 over UART, filter by
   MAC whitelist, publish to MQTT (topic per device MAC).
   Two payload modes, selectable in the dashboard:
   - RAW: JSON with mac, rssi, timestamp, raw advertisement hex
   - PARSED: decode standard BLE AD structures (device name, TX power,
     manufacturer ID, iBeacon, Eddystone, service data) → like nRF Connect
     does. Generic BLE parsing only, no vendor-specific decoding.
5. WiFi fails repeatedly → automatic fallback to AP setup mode.
   Never brick on bad credentials.
6. Dashboard UI: professional product quality → modern dark theme,
   card-based layout, clean typography, styled inputs/toggles, live
   status badges, monospace auto-scrolling log view, toast notification
   on save, responsive (usable from a phone in AP mode).
   ALL assets embedded in firmware → zero external CDN/fonts/scripts
   (AP mode has no internet; the dashboard must render perfectly offline).

nRF52840 (nRF Connect SDK / Zephyr):

7. Continuous BLE scanner: forward ALL advertisements (MAC, RSSI, raw
   payload) over UART. No filtering here → ESP32 filters. Keep it simple
   and reliable.
8. Button (10-second hold) → send "config reset" frame to ESP32 →
   ESP32 returns to AP mode BUT keeps the old config pre-filled in the
   dashboard form as placeholders, so the user changes one field and
   Save & Reboot. Acknowledge the reset: both LEDs flash 3× together.

Both sides:

9. Framed UART protocol: start byte, length, CRC → never raw prints
   between chips. Include a status frame so both chips know the global
   state (needed for the LED table below).
10. LED status system → implement EXACTLY this table:

    | Gateway state                        | Blue LED1        | Red LED2        |
    |--------------------------------------|------------------|-----------------|
    | AP setup mode                        | slow blink (1 Hz)| off             |
    | STA: connecting to WiFi              | fast blink (4 Hz)| off             |
    | WiFi OK, MQTT connecting / down      | solid ON         | slow blink      |
    | Normal operation (WiFi + MQTT OK)    | solid ON         | off             |
    | WiFi lost, retrying                  | off              | fast blink      |
    | Internal fault (UART link dead, scanner down) | off     | solid ON        |
    | Config reset accepted (button 10 s)  | 3 flashes together, then AP pattern |

    Rule: blue = mode/connectivity, red = problems only.
11. Watchdogs (hardware + task), reconnect/backoff everywhere,
    proper prj.conf + sdkconfig, clean module structure, comments.
12. Logging discipline: INFO level everywhere. DEBUG must never be
    enabled globally → if deep inspection is needed, enable DEBUG only
    for the single module under investigation, capture a window of a
    few seconds, then restore INFO. Logs must stay short, meaningful,
    and rate-limited (no per-advertisement printing in normal operation).
13. When development is done and I confirm: generate GATEWAY-GUIDE.md →
    user manual covering LEDs (include the state table), button,
    dashboard, MQTT topics, payload formats, reset procedure →
    everything a new user needs to deploy this gateway.

Work step by step: scaffold both projects, then build, flash, and verify
each stage on the real hardware using the live logs.

Hardware context

- Board IOT-BWG840-V1: BT840XE (nRF52840 + SKY66112 PA, ext. antenna)
  + ESP32-WROOM-32D.
- Inter-chip UART @115200: nRF P1.01=RX, P1.02=TX → ESP32 UART0
  (GPIO1 TX, GPIO3 RX). Cross-connected by IOT-JS5 jumper in operation.
- Flash ESP32: IOT-UART-ESP-V1 bridge replaces jumper, JS2 connector,
  auto-program works (idf.py flash monitor). Bridge in = chips
  disconnected from each other.
- Flash nRF52840: SWD via TC2050 Tag-Connect → nRF52840DK Debug Out
  (west flash). Logs via RTT → works even with jumper installed.
- LEDs/button → VERIFIED ON HARDWARE (V1B schematic), ALL ACTIVE-LOW:
  - LED1 (blue) = nRF P0.11 → 1K → 3V3. Drive LOW (sink) = ON.
  - LED2 (red)  = nRF P1.00 → 1K → 3V3. Drive LOW = ON.
    (P1.00 doubles as SWO → do NOT enable SWO/trace in debug config;
    plain RTT over SWDIO/SWCLK is unaffected.)
  - Button SW1 = nRF P0.16, shorts to GND when pressed →
    internal pull-up, pressed = reads 0.
- MANDATORY: init SKY66112 PA (PA=P0.17, LNA=P0.19, CPS=P0.06,
  CHL=P0.08) or radio range collapses. TX ≤ +2 dBm FCC / −4 dBm CE.
- Integration debugging: nRF via RTT, ESP32 logs mirrored to dashboard
  Logs tab + serial, UART link sniffable at the jumper header.
- First task before any feature: blink test both LEDs (active-low!)
  and verify the button reads on P0.16.
```

## What the agent did, step by step

<Steps>
  <Step title="Loaded curated firmware knowledge">
    The agent pulled the relevant human-curated knowledge for the task: nRF Connect SDK and Zephyr for the scanner side, ESP-IDF for the WiFi and MQTT side. This is the engineering knowledge that grounds what it writes, loaded per task rather than guessed.
  </Step>

  <Step title="Planned the architecture">
    It laid out the two-project split, the UART protocol between them, and the data flow from BLE advertisement to MQTT, and presented the plan for approval before writing code.
  </Step>

  <Step title="Wrote code for both projects">
    It wrote the nRF52840 BLE scanner on Zephyr and the ESP32 WiFi, MQTT, and web-dashboard firmware on ESP-IDF, including the shared UART framing on both sides.
  </Step>

  <Step title="Built with the real toolchains">
    It built each project with its actual toolchain, the nRF Connect SDK build for the nRF52840 and the ESP-IDF build for the ESP32, not a mock or a simulation.
  </Step>

  <Step title="Self-corrected build errors">
    When a build failed, it read the compiler and toolchain output, found the cause, and fixed the code, then rebuilt until both projects compiled cleanly.
  </Step>

  <Step title="Flashed both chips">
    It flashed the nRF52840 and the ESP32 on the Fanstel board.
  </Step>

  <Step title="Verified on hardware">
    It confirmed the end-to-end path on live hardware: a BLE reading arriving at the nRF52840, crossing the UART bridge, and being published by the ESP32 over MQTT, with the dashboard reflecting it.
  </Step>
</Steps>

## Reproduce it yourself

### Prerequisites

* **VS Code**
* **Adsum IoT Coder**, from the [Marketplace](https://marketplace.visualstudio.com/items?itemName=AdsumNetwork.nrf-ai-debugger) or [Open VSX](https://open-vsx.org/extension/AdsumNetwork/nrf-ai-debugger). The free tier needs no key and no account.
* **nRF Connect SDK** and its toolchain, for the nRF52840 side. See [platforms](/platforms-and-roadmap).
* **ESP-IDF**, for the ESP32 side.
* A **Fanstel BWG840**, or any comparable dual-chip board pairing an nRF52 with an ESP32 over UART.

<Info>
  No board? You can still see the agent work end to end on the bundled samples with the [free tier](/free-tier), no hardware and no key required. Bring your own board when you are ready to flash and verify.
</Info>

### Steps

<Steps>
  <Step title="Install the extension and open a workspace">
    Install Adsum IoT Coder, open an empty folder for the project, and open the Adsum panel.
  </Step>

  <Step title="Paste the spec">
    Paste the [prompt above](#the-prompt), adapted to your board and behaviour, and let the agent plan the architecture. Review the plan before it writes code.
  </Step>

  <Step title="Approve the build for each chip">
    Let it write and build both projects with their real toolchains. Approve the builds, and let it self-correct any build errors.
  </Step>

  <Step title="Flash and verify on hardware">
    On the BWG840 the two chips flash by different paths: the ESP32 over the IOT-UART-ESP-V1 bridge with `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.
  </Step>
</Steps>

## Links

* **Video**: [youtu.be/WrRYutpmHi8](https://youtu.be/WrRYutpmHi8)
* **Source and issues**: [github.com/adsumnetworks/Adsum-IoT-Coder](https://github.com/adsumnetworks/Adsum-IoT-Coder)
* **Install**: [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=AdsumNetwork.nrf-ai-debugger) or [Open VSX](https://open-vsx.org/extension/AdsumNetwork/nrf-ai-debugger)

## Try it

Free to try, no API key, no account. Bring your own board when you want to flash and verify, or start on the bundled samples with no hardware at all. [Install Adsum IoT Coder](https://marketplace.visualstudio.com/items?itemName=AdsumNetwork.nrf-ai-debugger) and give it a spec.
