Skip to content

Add WiFi provisioning, admin web UI, MQTT publisher, and chat decoder for ESP32-S3 boards#2623

Open
zfouts wants to merge 12 commits into
meshcore-dev:devfrom
zfouts:feature/wifi-esp32s3
Open

Add WiFi provisioning, admin web UI, MQTT publisher, and chat decoder for ESP32-S3 boards#2623
zfouts wants to merge 12 commits into
meshcore-dev:devfrom
zfouts:feature/wifi-esp32s3

Conversation

@zfouts
Copy link
Copy Markdown

@zfouts zfouts commented May 25, 2026

Summary

Adds runtime WiFi provisioning (AP captive portal → STA, NVS-persisted) and a
self-contained web admin / TCP-CLI / MQTT stack to MeshCore's ESP32-S3
variants. Everything is opt-in via build flags and gated by #ifdef WIFI_PROVISIONING / #ifdef MQTT_PUBLISHER, so all existing envs are
unchanged. New _repeater_wifi and _room_server_wifi envs are added for
every WiFi-capable ESP32-S3 board MeshCore supports; existing
_companion_radio_wifi envs are migrated from baked WIFI_SSID/WIFI_PWD
to the provisioning portal.

Motivation

Today, getting a MeshCore node onto a network requires baking the SSID and
password into firmware at compile time, and there is no in-firmware
remote-admin surface beyond the serial CLI. For users running headless
repeaters and room servers - exactly the roles where being unable to plug
in a USB cable matters most - that means re-flashing to change networks
and no live visibility into mesh traffic without external tooling.

This PR closes that gap on the ESP32-S3 family (where the chip already has
WiFi + BLE silicon that today is mostly idle in repeater/room-server
roles):

  • Provision over WiFi without recompiling. Boot, connect to the AP,
    pick a network, save. Falls back to AP automatically after 3 failed STA
    attempts so a router password change doesn't brick the node.
  • See and control the node from a phone or laptop on the same LAN.
    Status, mesh/radio/packet stats, live decoded chat (Public channel +
    user-added group PSKs), live raw packet feed, editable LoRa params,
    region presets, name-pattern forwarding blacklist, MQTT publisher.
  • Stream every observed RX and repeated TX packet to MQTT as JSON for
    external dashboards / Grafana / Home Assistant.

What's included

New shared helpers (under src/helpers/esp32/)

Module Purpose
WifiProvisioning.{h,cpp} AP-first captive portal (SSID MeshCore-Setup-XXXX, password meshcore123), NVS-persisted STA creds in namespace mc-wifi, 3-attempt STA fallback to AP, USER_BTN long-press wipe (graceful no-op if
PIN_USER_BTN<0), defensive WiFi.disconnect(true,true) + setAutoReconnect(true) to recover from first-attempt auth failures observed on some routers.
WifiAdminUI.{h,cpp} AsyncWebServer admin UI: home (status / radio config / mesh stats / radio stats / packet stats / network), /channels (manage group PSKs), /blacklist (wildcard name patterns to drop ADVERTs from),
/mqtt-setup. WebSocket /ws streams every RX and TX packet plus decoded chat as JSON. Console panel with inline CLI input, command history (localStorage), preset buttons, and /api/cmd POST routed through the same handleCommand the
serial CLI uses. Shared /style.css with dark-mode CSS variables; all sub-pages link to it.
WifiCliBridge.{h,cpp} Telnet-style TCP CLI on port 5050, one client at a time, displaces older connections. Lines are routed through the same handler as serial.
MqttPublisher.{h,cpp} PubSubClient-based publisher with 16-slot async queue (drops oldest under burst), NVS broker config in namespace mc-mqtt, configurable via /mqtt-setup. Publishes RX (logRxRaw) to <topic>/rx and TX
(logTx) to <topic>/tx as JSON.

Role wiring

  • examples/companion_radio/main.cppWIFI_PROVISIONING gate replaces
    the baked WIFI_SSID/WIFI_PWD WiFi.begin() path. SerialWifiInterface
    still runs on TCP 5000 once STA is up. Optional WIFI_SSID/WIFI_PWD
    become bootstrap defaults seeded into NVS on first boot.
  • examples/simple_repeater/main.cpp and MyMesh.{h,cpp} — wires
    provisioning, admin UI, CLI bridge, MQTT publisher.
    • Chat decoder: pre-configures the well-known Public group channel
      PSK; loadPersistentChatChannels() reads user-added channels from NVS
      namespace mc-chans on boot. searchChannelsByHash +
      onGroupDataRecv overrides extract sender + text from decrypted
      GRP_TXT payloads and push to the WS feed.
    • Forwarding blacklist: glob patterns (* and ?) matched against
      the advertised node name. Hits drop the ADVERT in
      allowPacketForward and skip neighbour-table insert in
      onAdvertRecv. Persisted in NVS namespace mc-block with per-pattern
      hit counters.
    • logRxRaw and logTx call wifiAdminPush* and mqttPublish* extern
      helpers (no-ops if their respective modules aren't compiled in).
  • examples/simple_room_server/main.cpp — provisioning + admin + CLI
    bridge (no MQTT, no chat decode by default).
  • examples/{simple_repeater,simple_room_server}/UITask.cpp — render WiFi
    state on the home screen below freq/bw/cr: green IP: + RSSI: in STA
    mode, yellow AP: SSID in setup mode. Gated by WIFI_PROVISIONING so
    non-WiFi envs are unaffected.

Infrastructure

  • src/helpers/ESP32Board.cpp - move legacy WiFi-OTA AsyncWebServer
    from port 80 to 8306 to avoid colliding with the provisioning web
    server when both code paths are present on the same boot.

New / modified build envs

Pattern: every WiFi-capable ESP32-S3 board gets a new _repeater_wifi and
_room_server_wifi env; the existing _companion_radio_wifi (where
present) flips from baked SSID/PWD to provisioning.

Variant New envs Modified env
xiao_s3_wio Xiao_S3_WIO_repeater_wifi, Xiao_S3_WIO_room_server_wifi Xiao_S3_WIO_companion_radio_wifi
heltec_v4 (OLED + TFT) heltec_v4_repeater_wifi, heltec_v4_room_server_wifi, heltec_v4_tft_repeater_wifi, heltec_v4_tft_room_server_wifi heltec_v4_companion_radio_wifi, heltec_v4_tft_companion_radio_wifi
heltec_v3 Heltec_v3_repeater_wifi, Heltec_v3_room_server_wifi Heltec_v3_companion_radio_wifi
heltec_tracker Heltec_Wireless_Tracker_repeater_wifi, _room_server_wifi
heltec_tracker_v2 heltec_tracker_v2_repeater_wifi, _room_server_wifi heltec_tracker_v2_companion_radio_wifi
heltec_wireless_paper Heltec_Wireless_Paper_repeater_wifi, _room_server_wifi
heltec_t190 Heltec_T190_repeater_wifi_, _room_server_wifi_
rak3112 RAK_3112_repeater_wifi, RAK_3112_room_server_wifi RAK_3112_companion_radio_wifi
lilygo_teth_elite LilyGo_TETH_Elite_sx1262_repeater_wifi, _room_server_wifi
heltec_e213 Heltec_E213_repeater_wifi, Heltec_E213_room_server_wifi
heltec_e290 Heltec_E290_repeater_wifi, Heltec_E290_room_server_wifi

No existing envs were removed or had their existing behavior changed
beyond the explicit _companion_radio_wifi migration noted above. BLE,
USB, serial, plain-repeater, plain-room-server, sensor, and bridge envs
are all untouched.

Hardware testing

  • Validated end-to-end on Seeed XIAO ESP32-S3 + Wio-SX1262 (US 910.525
    / SF7 / BW62.5 / CR5): captive portal, STA reconnect (including a
    first-attempt WIFI_REASON_AUTH_FAIL that the defensive code now
    recovers from automatically), web admin UI, packet feed via WebSocket,
    chat decoding from the Public channel, blacklist drops verified against
    a live node, inline editing of TX power / coding rate / path hash mode,
    region preset switching.
  • Compile-verified for all 11 ESP32-S3 variants listed above
    (pio run -e <env> clean for every new _repeater_wifi; spot-built
    Companion + Room Server flavors of Heltec V4).
  • MQTT publishing, the chat decoder on user-added channels, the e-paper
    display variants, and most boards beyond Xiao have not yet been
    hardware-tested.
    The shared modules are board-agnostic ESP32 code so
    the per-board risk is limited to integration glue, but worth flagging
    for reviewers.

Things explicitly NOT done in this PR

  • No auth on web / CLI / WebSocket endpoints. Anyone on the LAN can
    view stats, reboot, wipe creds, or run arbitrary CLI commands. This
    matches the threat model assumed by today's serial CLI (trusted
    physical/local access) but is worth a separate hardening pass before
    recommending exposure to untrusted LANs.
  • No TLS for MQTT or the web admin (plain PubSubClient over TCP,
    HTTP-only).
  • Default AP password is meshcore123, same on every device. Fine for
    initial bring-up; a per-device-derived default would be a worthwhile
  • TFT WiFi-status rendering only added to Heltec V4 TFT envs (via the
    existing UITask code paths for repeater and room server). Other TFT
    boards' UITasks would benefit from the same treatment in a follow-up.
  • No nRF52 / RP2040 / STM32 support. These platforms either don't
    have WiFi hardware or (for RP2040 + Pico W) don't expose it in
    MeshCore today. The modules are ESP32-specific by design.
  • Companion radio's transport stays single-instance (WiFi or BLE,
    picked at compile time). Running both simultaneously is a bigger
    refactor and isn't done here.

Test plan for reviewers

  • Build at least one new env per variant family:
    pio run -e Xiao_S3_WIO_repeater_wifi -e heltec_v4_repeater_wifi -e RAK_3112_repeater_wifi
  • Flash to any ESP32-S3 board, confirm the AP MeshCore-Setup-XXXX
    appears, password meshcore123 connects, captive portal pops up,
    /scan lists nearby networks, save → reboot → STA.
  • On the admin UI: confirm packet feed populates when another node
    transmits, confirm decoded chat appears from a Public-channel
    message, confirm CLI commands (neighbors, stats, set tx 20)
    run through /api/cmd and through the TCP CLI bridge on port 5050.
  • In /blacklist, add a glob matching another node's name and
    verify the hit counter increments while that node's adverts no
    longer show up in neighbors.
  • In /mqtt-setup, point at a broker, reboot, confirm
    <base>/rx and <base>/tx messages flow.
  • Confirm the legacy WiFi-OTA flow (startOTAUpdate) still works
    on its new port 8306 and doesn't clash with the provisioning
    server.

Process notes for maintainers

A few things I'd like to surface up front:

  • No pre-PR issue was opened per CONTRIBUTING.md's process for
    larger changes. Happy to open one and split this into smaller PRs if
    you'd prefer — provisioning could land first, admin UI second, MQTT
    third, blacklist + chat decoder fourth.
  • Branched from main not dev. Will rebase onto dev on request.
  • Style broadly matches the existing codebase, but the new helpers
    use more modern C++ (range-for, lambdas, String-based JSON
    assembly). Happy to refactor toward whatever matches the rest of the
    esp32 helpers if there's a preferred style.

liamcottle and others added 12 commits May 25, 2026 17:47
Removed links to outdated resources and links
Include the 'ver' command for retrieving the firmware version
… for Xiao S3 WIO

New helpers under src/helpers/esp32/:
- WifiProvisioning.{h,cpp}: AP-first captive portal (SSID MeshCore-Setup-XXXX,
  password meshcore123), NVS-persisted STA credentials in namespace mc-wifi,
  3-attempt STA fallback to AP, USER_BTN long-press wipe, defensive
  WiFi.disconnect + setAutoReconnect to recover from first-attempt auth fails
  seen on some routers.
- WifiAdminUI.{h,cpp}: AsyncWebServer admin UI with WebSocket packet feed at
  /ws, chat panel for decoded GRP_TXT messages, console (CLI over /api/cmd),
  status/stats/radio/packets/neighbours/radio-config endpoints, /channels
  page (manage group PSKs), /blacklist page (wildcard name patterns to drop
  ADVERTs from), inline editors for TX power / coding rate / path hash mode.
  Shared /style.css with dark-mode CSS variables; sub-pages link to it.
- WifiCliBridge.{h,cpp}: line-based TCP CLI server on port 5050, routes
  CR-terminated lines through handleCommand.
- MqttPublisher.{h,cpp}: PubSubClient-based publisher with 16-slot async
  queue (drops oldest under burst), NVS broker config in namespace mc-mqtt,
  publishes RX (logRxRaw) and TX (logTx) packets to <topic>/rx and
  <topic>/tx as JSON with rssi/snr/raw-hex.

Role wiring:
- examples/companion_radio/main.cpp: WIFI_PROVISIONING gate replaces the
  baked WIFI_SSID/WIFI_PWD WiFi.begin() path; SerialWifiInterface still
  runs on TCP 5000 once STA is up. Optional WIFI_SSID/WIFI_PWD become
  bootstrap defaults seeded into NVS on first boot.
- examples/simple_repeater/main.cpp: full wiring of provisioning, admin UI,
  TCP CLI bridge, and MQTT publisher under WIFI_PROVISIONING + MQTT_PUBLISHER.
  _RepeaterMeshInfo adapter delegates listChannels/addChannel/listBlocked/
  addBlocked/removeBlocked/formatRadioConfig to MyMesh.
- examples/simple_repeater/MyMesh.{h,cpp}: chat decoder (Public channel
  pre-configured with the well-known PSK; user channels added via /channels
  and stored in NVS namespace mc-chans). NVS-persisted forwarding blacklist
  in namespace mc-block with glob patterns (* and ?) matched against
  advertised names; hits drop the ADVERT in allowPacketForward and skip
  neighbour-table insert in onAdvertRecv. logRxRaw and logTx hook calls to
  wifiAdmin* and mqtt* extern functions.
- examples/simple_room_server/main.cpp: WIFI_PROVISIONING brings up
  provisioning, admin UI, and TCP CLI bridge (no MQTT, no chat decode).

Infrastructure:
- src/helpers/ESP32Board.cpp: move legacy OTA AsyncWebServer from port 80
  to 8306 so it doesn't collide with provisioning's port 80 server when
  both are running.
- variants/xiao_s3_wio/platformio.ini: override LoRa region defaults from
  EU (869.618 MHz, SF8) to USA/Canada (910.525 MHz, SF7) at the Xiao_S3_WIO
  base via build_unflags. Modify Xiao_S3_WIO_companion_radio_wifi to use
  WIFI_PROVISIONING instead of compile-time SSID/PWD. New envs
  Xiao_S3_WIO_repeater_wifi (with MQTT_PUBLISHER + densaugeo/base64 +
  PubSubClient deps) and Xiao_S3_WIO_room_server_wifi.

Hardware-validated on a Seeed XIAO ESP32-S3 + Wio-SX1262: provisioning,
STA reconnect, web admin, chat decoding from Public channel, blacklist,
and live editing of TX power / coding rate / path hash mode all work.
MQTT publishing and full Companion/Room Server boots are code-compiled
but not yet hardware-tested.
variants/heltec_v4/platformio.ini:
- New env heltec_v4_repeater_wifi: WIFI_PROVISIONING + WifiAdminUI +
  WifiCliBridge + MqttPublisher on the OLED variant. Mirrors
  Xiao_S3_WIO_repeater_wifi.
- New env heltec_v4_room_server_wifi: provisioning + admin + CLI bridge
  (no MQTT).
- New env heltec_v4_tft_repeater_wifi: same feature set as the OLED
  repeater, on the ST7789LCDDisplay TFT variant.
- New env heltec_v4_tft_room_server_wifi: same as OLED room server, on TFT.
- Modify heltec_v4_companion_radio_wifi and heltec_v4_tft_companion_radio_wifi:
  drop the baked WIFI_SSID/WIFI_PWD, switch to WIFI_PROVISIONING, add
  ${esp32_ota.lib_deps}. Optional bootstrap creds are now commented out.

examples/simple_repeater/UITask.cpp,
examples/simple_room_server/UITask.cpp:
- Add WiFi status to the home screen under freq/bw/cr (gated on
  ESP_PLATFORM + WIFI_PROVISIONING). In STA mode shows IP + RSSI in
  green; in AP mode shows the captive-portal SSID in yellow. Lets a
  user with a display see whether the node is ready or still expecting
  WiFi setup, without opening the web UI.

Builds verified clean for all six new/modified Heltec V4 envs plus a
Xiao_S3_WIO_repeater_wifi regression check.
…faults to upstream

variants/xiao_s3_wio/platformio.ini:
- Drop the build_unflags / -D LORA_FREQ=910.525 / -D LORA_SF=7 overrides.
  Xiao_S3_WIO base now inherits arduino_base defaults (869.618 MHz, BW62.5,
  SF8). Users pick their region at runtime from the admin UI instead of
  baking it into firmware at build time.

src/helpers/esp32/WifiAdminUI.cpp:
- Radio config card: Frequency is now a number input (150-2500 MHz,
  step 0.025). Bandwidth and Spreading Factor are selects with the
  full legal value sets (10 BW options for SX1262; SF5-12). Each
  change fires `set radio f,b,s,c` with the other current values,
  prompting reboot to apply.
- Add region preset row below the card: USA/Canada (910.525/62.5/SF7/CR5),
  EU868 (869.618/62.5/SF8/CR5), Legacy Wide (869.525/250/SF11/CR5).
  Clicking a preset confirms then applies all four params in one call.
- Refactor setCr to share a setRadio(freq,bw,sf,cr,reason) helper used
  by all four "set radio" callers (manual edits + presets).

Verified: Xiao_S3_WIO_repeater_wifi, heltec_v4_repeater_wifi, and
heltec_v4_tft_repeater_wifi all compile clean.
…iants

For every WiFi-capable ESP32-S3 board MeshCore supports, add the same env
pair we shipped for Xiao S3 WIO and Heltec V4:
- *_repeater_wifi: WifiProvisioning + WifiAdminUI + WifiCliBridge + MqttPublisher
- *_room_server_wifi: WifiProvisioning + WifiAdminUI + WifiCliBridge

Where the variant already had a *_companion_radio_wifi env with baked
WIFI_SSID/WIFI_PWD, switch it to -D WIFI_PROVISIONING=1 and add
${esp32_ota.lib_deps} (matches the Heltec V4 / Xiao change).

variants/heltec_v3/platformio.ini:
- Add Heltec_v3_repeater_wifi, Heltec_v3_room_server_wifi (SSD1306Display)
- Modify Heltec_v3_companion_radio_wifi to use provisioning
- WSL3 sub-family untouched

variants/heltec_tracker/platformio.ini:
- Add Heltec_Wireless_Tracker_repeater_wifi, _room_server_wifi (ST7735Display)

variants/heltec_tracker_v2/platformio.ini:
- Add heltec_tracker_v2_repeater_wifi, _room_server_wifi (ST7735Display)
- Modify heltec_tracker_v2_companion_radio_wifi to use provisioning

variants/heltec_wireless_paper/platformio.ini:
- Add Heltec_Wireless_Paper_repeater_wifi, _room_server_wifi (E213Display)

variants/heltec_t190/platformio.ini:
- Add Heltec_T190_repeater_wifi_, _room_server_wifi_ (ST7789Display inherited
  from base; trailing underscore matches the existing env naming convention
  in this file)

variants/rak3112/platformio.ini:
- Add RAK_3112_repeater_wifi, RAK_3112_room_server_wifi (headless, no display)
- Modify RAK_3112_companion_radio_wifi to use provisioning
- Bridge envs (rs232, espnow) untouched

variants/lilygo_teth_elite/platformio.ini:
- Add LilyGo_TETH_Elite_sx1262_repeater_wifi, _room_server_wifi (headless)

variants/heltec_e213/platformio.ini:
- Add Heltec_E213_repeater_wifi, Heltec_E213_room_server_wifi (E213Display)

variants/heltec_e290/platformio.ini:
- Add Heltec_E290_repeater_wifi, Heltec_E290_room_server_wifi (E290Display)

All new envs verified to compile clean. Existing BLE / USB / serial /
plain-repeater / plain-room-server / sensor / bridge envs unchanged: this
is purely additive, users pick their connectivity at flash time.
src/helpers/esp32/WifiAdminUI.cpp:
- Pull the Radio config card out of the small `.cards` grid (which uses
  repeat(auto-fit, minmax(220px, 1fr)) and squeezed editor selects to
  the point that "2B (recommended)" was clipped and the MHz / dBm unit
  labels next to the freq and tx_power inputs were pushed offscreen).
- New full-width `.rcard` section with looser inner grid
  (minmax(9em,11em) for the label column, auto for the value column).
- Move unit suffixes into a dedicated `<span class=unit>` so they stay
  next to their input instead of disappearing under right-align width
  competition.
- Region preset chips now render in a dedicated #rcfg-presets container
  with its own border-top, so they read as a related but separate row
  rather than a fourth column getting absorbed into the kv grid.
- Drop the inappropriate `class=kv` on `#rcfg` itself (it now wraps a
  kv child instead of being the grid).

Verified visually via Playwright screenshot before/after on a live Xiao
S3 WIO repeater: editors aligned, units visible, presets fit on one row.
@cwichura
Copy link
Copy Markdown

Have you seen https://github.com/agessaman/MeshCore/tree/mqtt-bridge-implementation-flex? It also adds native MQTT upload for ESP32-based units and has been in active development for a while now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants