xiao-esp32s3-mqtt-dashboard

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

XIAO ESP32S3 multi-board MQTT dashboard

XIAO ESP32S3 多板MQTT仪表盘

Architecture: N boards (STA mode, same WiFi) → MQTT broker (Mosquitto, one PC on that WiFi) → browser dashboard (MQTT-over-WebSocket,
mqtt.js
). Each board runs the same compiled firmware — no per-board edits — because it derives a unique ID from its own MAC address.
Read the
xiao-esp32s3
skill (same repo) first for base arduino-cli workflow and pitfalls (PSRAM flag, DTR reset-stuck recovery, bounded serial reads). This skill adds the broker + networking + dashboard layer on top.
架构:N块开发板(STA模式,接入同一WiFi)→ MQTT代理(Mosquitto,部署在该WiFi网络内的一台PC上)→ 浏览器仪表盘(基于WebSocket的MQTT,使用
mqtt.js
)。每块开发板运行相同的编译后固件——无需针对单块板修改——因为它会通过自身MAC地址生成唯一ID。
请先阅读
xiao-esp32s3
技能(同一仓库),了解基础arduino-cli工作流程及常见问题(PSRAM标志、DTR重置卡顿恢复、有限串行读取)。本技能在其基础上增加了代理、网络及仪表盘层。

Step 1 — ALWAYS ask the user first

步骤1 — 务必先询问用户

Never invent credentials or assume where the broker runs. Ask for:
  1. WiFi SSID / password — must be a network every board and the broker PC can join (2.4 GHz; ESP32 cannot see 5 GHz networks).
  2. Where Mosquitto runs — this PC (skill sets it up, see Step 2), or an existing broker elsewhere (get host + port, skip Step 2's local setup).
  3. Whether more than one physical board will be flashed now (affects nothing in the firmware — it self-assigns IDs — but changes how you phrase the "flash this to every board" instruction back to the user).
切勿自行设置凭证或假设代理部署位置,请向用户确认以下信息:
  1. WiFi SSID/密码 — 必须是所有开发板和代理PC都能接入的网络(仅支持2.4 GHz;ESP32无法识别5 GHz网络)。
  2. Mosquitto部署位置 — 本地PC(本技能将指导搭建,见步骤2),或已存在的远程代理(需获取主机地址和端口,跳过步骤2的本地搭建)。
  3. 当前是否要刷写多块物理开发板(对固件无影响——固件会自动分配ID——但会影响向用户说明“将此固件刷写到所有开发板”的表述方式)。

Step 2 — set up the broker (skip if one already exists elsewhere)

步骤2 — 搭建代理(若已有远程代理则跳过)

Find the broker machine's LAN IP on the shared WiFi (Windows):
powershell
Get-NetIPAddress -AddressFamily IPv4 | Where-Object {$_.InterfaceAlias -eq 'Wi-Fi'} | Select-Object IPAddress
Check whether Mosquitto is installed and what's listening:
powershell
netstat -an | findstr "1883 9001"
Get-Service -Name mosquitto -ErrorAction SilentlyContinue
If nothing is listening on
0.0.0.0:1883
/
0.0.0.0:9001
(see pitfalls 1–3 below for why this is the default), copy
assets/mqtt-broker/setup_broker.ps1
and
assets/mqtt-broker/setup_firewall.ps1
next to each other in the user's workspace, then run both elevated (see pitfall 2 for why a plain shell silently fails):
powershell
Start-Process powershell -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','<path>\setup_broker.ps1' -Verb RunAs -Wait
Start-Process powershell -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','<path>\setup_firewall.ps1' -Verb RunAs -Wait
This triggers a Windows UAC consent prompt — tell the user it will appear and that they need to click "Yes"; you cannot click it for them. Both scripts write a
setup_result.txt
next to themselves — read it back to confirm success instead of assuming the elevated window's own output was captured.
Verify LAN exposure actually worked (not just that the process is listening):
powershell
netstat -an | findstr "1883 9001"   # expect 0.0.0.0:1883 and 0.0.0.0:9001, not 127.0.0.1
在共享WiFi网络中查找代理机器的LAN IP(Windows系统):
powershell
Get-NetIPAddress -AddressFamily IPv4 | Where-Object {$_.InterfaceAlias -eq 'Wi-Fi'} | Select-Object IPAddress
检查Mosquitto是否已安装及监听端口:
powershell
netstat -an | findstr "1883 9001"
Get-Service -Name mosquitto -ErrorAction SilentlyContinue
0.0.0.0:1883
/
0.0.0.0:9001
无监听(以下问题1-3将说明默认情况),请将
assets/mqtt-broker/setup_broker.ps1
assets/mqtt-broker/setup_firewall.ps1
复制到用户工作目录中,然后以管理员权限运行两个脚本(问题2将说明普通Shell会静默执行失败):
powershell
Start-Process powershell -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','<path>\setup_broker.ps1' -Verb RunAs -Wait
Start-Process powershell -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','<path>\setup_firewall.ps1' -Verb RunAs -Wait
这会触发Windows UAC授权提示——请告知用户将弹出该提示,需点击“是”;你无法替用户操作。两个脚本会在自身目录下生成
setup_result.txt
——请读取该文件确认操作成功,不要假设管理员窗口的输出已被捕获。
验证LAN暴露是否生效(不只是进程在监听):
powershell
netstat -an | findstr "1883 9001"   # 预期显示0.0.0.0:1883和0.0.0.0:9001,而非127.0.0.1

Step 3 — generate the sketch from the template

步骤3 — 从模板生成代码草图

assets/esp32s3_mqtt_dashboard.ino.tpl
is verified working code — do not rewrite it. Copy it to
<workspace>\<name>\<name>.ino
(folder name must match the
.ino
name) and replace the placeholders literally:
PlaceholderMeaningExample
__WIFI_SSID__
WiFi SSID (2.4 GHz)
myhome
__WIFI_PASS__
WiFi password
pass1234
__MQTT_HOST__
broker machine's LAN IP
192.168.0.42
Install the one extra library the sketch needs (
WiFi
/
WiFiClient
ship with the esp32 core already):
powershell
arduino-cli lib install "PubSubClient"
assets/esp32s3_mqtt_dashboard.ino.tpl
是经过验证的可用代码——请勿重写。将其复制到
<workspace>\<name>\<name>.ino
(文件夹名称必须与
.ino
文件名一致),并替换以下占位符:
占位符含义示例
__WIFI_SSID__
WiFi SSID(2.4 GHz)
myhome
__WIFI_PASS__
WiFi密码
pass1234
__MQTT_HOST__
代理机器的LAN IP
192.168.0.42
安装代码草图所需的额外库(
WiFi
/
WiFiClient
已随esp32核心库附带):
powershell
arduino-cli lib install "PubSubClient"

Step 4 — compile, upload

步骤4 — 编译、上传

powershell
arduino-cli compile --fqbn esp32:esp32:XIAO_ESP32S3 <SKETCH_DIR>
arduino-cli upload -p COM4 --fqbn esp32:esp32:XIAO_ESP32S3 <SKETCH_DIR>
To add more boards, flash the exact same compiled sketch to each one — no edits needed between boards.
powershell
arduino-cli compile --fqbn esp32:esp32:XIAO_ESP32S3 <SKETCH_DIR>
arduino-cli upload -p COM4 --fqbn esp32:esp32:XIAO_ESP32S3 <SKETCH_DIR>
如需添加更多开发板,将完全相同的编译后固件刷写到每块板即可——无需在板间修改代码。

Step 5 — verify WITHOUT living in the serial monitor

步骤5 — 无需依赖串口监视器验证

Read serial ONCE right after flashing to confirm WiFi + MQTT connect (use the base skill's
read_serial.ps1
, never
arduino-cli monitor
). After that, stop opening the serial port and verify ongoing behavior purely over MQTT (pitfall 6 explains why re-opening it can look like a regression that isn't one):
powershell
undefined
刷写完成后读取一次串口,确认WiFi+MQTT连接成功(使用基础技能的
read_serial.ps1
,切勿使用
arduino-cli monitor
)。之后,停止打开串口,仅通过MQTT验证持续运行状态(问题6将说明重新打开串口可能会导致看似回归的误判):
powershell
undefined

expects: <topic-prefix>/<id>/status online, then a touch value every ~0.5s

预期输出:<topic-prefix>/<id>/status online,之后每约0.5秒输出一次触摸数值

mosquitto_sub -h <broker-ip> -p 1883 -t "xiao/#" -v
mosquitto_sub -h <broker-ip> -p 1883 -t "xiao/#" -v

flip a board's LED from the command line the same way the dashboard does

与仪表盘操作方式相同,从命令行切换开发板LED状态

mosquitto_pub -h <broker-ip> -p 1883 -t "xiao/<id>/led/set" -m "ON"

(If `mosquitto_sub`/`mosquitto_pub` aren't on PATH, they ship next to
`mosquitto.exe`, typically `C:\Program Files\Mosquitto\`.)
mosquitto_pub -h <broker-ip> -p 1883 -t "xiao/<id>/led/set" -m "ON"

(若`mosquitto_sub`/`mosquitto_pub`不在PATH中,它们随`mosquitto.exe`一同安装,通常位于`C:\Program Files\Mosquitto\`目录下。)

Step 6 — run the dashboard

步骤6 — 运行仪表盘

assets/dashboard_web/
(
index.html
,
app.js
,
styles.css
) is a static site — no build step, no placeholders to fill (the broker address is typed into the page itself, not baked into the JS). Serve it any way the user likes, e.g.:
powershell
cd assets/dashboard_web
python -m http.server 8080
Then open
http://localhost:8080
(or
http://<broker-ip>:8080
from another device on the same WiFi). Type the broker's LAN IP into the "브로커 주소" box and click 연결 — it remembers the value in
localStorage
after that. Boards appear as cards automatically as their
status
/
touch
messages arrive; no per-board dashboard configuration exists or is needed.
assets/dashboard_web/
(包含
index.html
app.js
styles.css
)是一个静态站点——无需构建步骤,无需填充占位符(代理地址需在页面中手动输入,而非嵌入JS)。可通过任意方式部署,例如:
powershell
cd assets/dashboard_web
python -m http.server 8080
然后打开
http://localhost:8080
(或从同一WiFi网络内的其他设备打开
http://<broker-ip>:8080
)。在“브로커 주소”输入框中输入代理的LAN IP并点击“연결”——之后该值会保存在
localStorage
中。当开发板发送
status
/
touch
消息时,会自动以卡片形式显示在仪表盘中;无需针对单块板配置仪表盘。

MQTT topic scheme

MQTT主题规则

<id>
is the board's own MAC-derived hex ID (auto-generated, e.g.
4b4cf4
) so many boards never collide on one broker.
TopicDirectionPayloadNotes
xiao/<id>/touch
board → dashboardinteger stringevery 500 ms
xiao/<id>/led/set
dashboard → board
ON
/
OFF
/
TOGGLE
command
xiao/<id>/led/state
board → dashboard
ON
/
OFF
, retained
current state
xiao/<id>/status
board → dashboard
online
/
offline
, retained
LWT — broker auto-publishes
offline
on ungraceful disconnect
Touch baseline is roughly 16,000–18,000 on bare wire/pad; values RISE well past 40,000 when touched (ESP32-S3 touch direction — see base skill).
<id>
是开发板通过MAC地址生成的十六进制唯一ID(自动生成,例如
4b4cf4
),确保多块板在同一代理下不会出现主题冲突。
主题方向负载说明
xiao/<id>/touch
开发板→仪表盘整数字符串每500毫秒发送一次
xiao/<id>/led/set
仪表盘→开发板
ON
/
OFF
/
TOGGLE
控制指令
xiao/<id>/led/state
开发板→仪表盘
ON
/
OFF
(保留消息)
当前状态
xiao/<id>/status
开发板→仪表盘
online
/
offline
(保留消息)
LWT——当开发板异常断开时,代理会自动发布
offline
裸线/焊盘的触摸基准值约为16000–18000;触摸时数值会升至40000以上(ESP32-S3触摸数值变化方向——见基础技能)。

Known pitfalls — read before "debugging"

已知问题——调试前请阅读

  1. Mosquitto 2.x binds to loopback ONLY when no listener is configured — this is a deliberate security default, not a bug. A fresh Windows install with the stock
    mosquitto.conf
    (all comments, no
    listener
    line) will show
    127.0.0.1:1883
    in
    netstat
    , never
    0.0.0.0:1883
    , until you add an explicit
    listener 1883 0.0.0.0
    .
  2. The Windows Mosquitto service runs as
    LocalSystem
    .
    A normal (non-admin) shell gets a silent
    Access is denied
    from
    Stop-Service
    /
    Restart-Service
    , and cannot write
    C:\Program Files\Mosquitto\mosquitto.conf
    either. Use
    Start-Process powershell -Verb RunAs -Wait
    to run the two setup scripts elevated (Step 2) — this is a legitimate, narrowly-scoped use of UAC elevation for a config change the user already asked for; it is not a privilege-escalation workaround, and it still requires the user to click "Yes" on the consent prompt themselves.
  3. Windows Firewall blocks LAN-inbound to a newly opened port by default, especially when the WiFi adapter's network profile is "Public" (check with
    Get-NetConnectionProfile
    ). Opening the Mosquitto listener alone is not enough — add explicit
    New-NetFirewallRule -Direction Inbound
    rules for both ports (Step 2's
    setup_firewall.ps1
    ). False-positive warning:
    Test-NetConnection <own-LAN-IP> -Port 1883
    run FROM the broker machine itself can report
    TcpTestSucceeded: True
    even when the firewall would block a genuinely remote device — self-to-self tests over your own LAN IP are not a reliable substitute for testing from the actual board.
  4. PubSubClient::connect()
    returning
    rc=-2
    (
    MQTT_CONNECT_FAILED
    ) means the TCP connection itself failed — broker unreachable or firewalled — not a credentials/auth rejection. Chasing
    allow_anonymous
    /username config for this code wastes time; check pitfalls 1 and 3 first.
  5. Opening the ESP32S3's serial port resets the board every time (DTR toggle, same as the base skill's pitfall 1) — but for a board that's already connected to WiFi/MQTT, this doesn't just cost a reboot: closing the port afterward can leave it in the reset-stuck state (base skill pitfall 8), which drops the MQTT session a few seconds later. The broker then reports that board
    offline
    via its LWT even though the firmware itself has no bug. Once you've confirmed the WiFi+MQTT connect log once, stop opening the serial port — verify ongoing liveness with
    mosquitto_sub
    or the dashboard instead, never by reopening serial to "just double check."
  6. Browsers cannot speak raw MQTT over TCP. The dashboard needs Mosquitto's separate
    listener 9001 ... protocol websockets
    — the same
    allow_anonymous
    /firewall requirements from pitfalls 1–3 apply to it independently of port 1883. "Firmware connects fine, dashboard shows nothing" almost always means the websockets listener was forgotten.
  7. Never hardcode one user's WiFi SSID/password or one machine's LAN IP into a shared template. Ask (Step 1), template it in (Step 3) per deployment. Also warn that a DHCP-assigned broker IP can change on that PC's reboot — a router-side DHCP reservation avoids having to re-flash every board afterward.
  8. Each board must derive its own topic-unique ID (MAC-based, as the template does) rather than using a fixed client ID/topic — two boards with the same MQTT client ID will fight over the connection (repeated disconnects) and two boards on the same topic will stomp each other's
    touch
    /
    led/state
    values on the dashboard.
  1. 未配置监听时,Mosquitto 2.x仅绑定回环地址——这是刻意设置的安全默认值,并非Bug。全新Windows安装使用默认
    mosquitto.conf
    (全注释,无
    listener
    行)时,
    netstat
    会显示
    127.0.0.1:1883
    ,而非
    0.0.0.0:1883
    ,需添加显式配置
    listener 1883 0.0.0.0
  2. Windows Mosquitto服务以
    LocalSystem
    身份运行
    。普通(非管理员)Shell执行
    Stop-Service
    /
    Restart-Service
    会静默返回
    Access is denied
    ,且无法写入
    C:\Program Files\Mosquitto\mosquitto.conf
    。请使用
    Start-Process powershell -Verb RunAs -Wait
    以管理员权限运行两个设置脚本(步骤2)——这是用户请求的合法、窄范围配置变更,并非权限提升手段,仍需用户在授权提示中点击“是”。
  3. Windows防火墙默认阻止新开放端口的LAN入站连接,尤其是当WiFi适配器的网络配置文件为“公共”时(可通过
    Get-NetConnectionProfile
    查看)。仅打开Mosquitto监听端口不足以解决问题——需为两个端口添加显式
    New-NetFirewallRule -Direction Inbound
    规则(步骤2的
    setup_firewall.ps1
    已包含)。误判警告:在代理机器上运行
    Test-NetConnection <own-LAN-IP> -Port 1883
    可能会返回
    TcpTestSucceeded: True
    ,但防火墙仍会阻止真正远程设备的连接——自身LAN IP的自测试无法替代实际开发板的测试。
  4. PubSubClient::connect()
    返回
    rc=-2
    MQTT_CONNECT_FAILED
    )表示TCP连接失败——代理不可达或被防火墙阻止——并非凭证/授权拒绝。针对此代码排查
    allow_anonymous
    /用户名配置纯属浪费时间,请先检查问题1和3。
  5. 打开ESP32S3串口会每次重置开发板(DTR切换,与基础技能的问题1相同)——但对于已连接WiFi/MQTT的开发板,这不仅会导致重启:关闭串口后可能会使开发板陷入重置卡顿状态(基础技能的问题8),几秒后MQTT会话会断开。此时代理会通过LWT报告该开发板
    offline
    ,但固件本身并无Bug。一旦确认WiFi+MQTT连接日志无误,请勿再打开串口——请使用
    mosquitto_sub
    或仪表盘验证持续运行状态,切勿为“再次确认”而重新打开串口。
  6. 浏览器无法通过TCP直接传输原始MQTT数据。仪表盘需要Mosquitto单独配置
    listener 9001 ... protocol websockets
    ——问题1-3中的
    allow_anonymous
    /防火墙要求同样适用于该端口,与1883端口独立。“固件连接正常,但仪表盘无显示”几乎总是因为忘记配置WebSocket监听。
  7. 切勿将用户的WiFi SSID/密码或某台机器的LAN IP硬编码到共享模板中。请询问用户(步骤1),并针对每次部署进行模板替换(步骤3)。同时提醒用户:DHCP分配的代理IP可能会在PC重启后变更——在路由器端设置DHCP保留可避免重新刷写所有开发板。
  8. 每块开发板必须生成自身的主题唯一ID(如模板中基于MAC地址生成),而非使用固定客户端ID/主题——两个使用相同MQTT客户端ID的开发板会争夺连接(反复断开),两个使用相同主题的开发板会在仪表盘中覆盖彼此的
    touch
    /
    led/state
    数值。