XIAO ESP32S3 camera web apps — STA (router) mode
The board joins the user's WiFi router. The PC/phone stays on its normal
network (internet keeps working) and reaches the board at
http://<mdns-name>.local or its DHCP IP.
Read the xiao-esp32s3 skill (same repo) first for base workflow and pitfalls
(especially: PSRAM flag mandatory, DTR reset-stuck recovery, no serial reads
while a demo must keep running).
Step 1 — ALWAYS ask the user first
Never invent, guess, or silently reuse WiFi credentials. Ask the user for:
- 공유기 SSID — must be a 2.4 GHz network (ESP32 cannot see 5 GHz;
if the user gives
something_5G, ask for the 2.4 GHz band name). - 공유기 비밀번호.
- mDNS 이름 (optional, default
xiao) — page becomeshttp://<name>.local. Boards on the same LAN must each get a UNIQUE name. - Which app: collector, inference viewer, or both.
Step 2 — generate the sketch from the template
Templates in assets/ are verified working code — do not rewrite them:
assets/web_collect.ino.tpl— dataset collector (gallery + delete + ZIP download)assets/web_infer.ino.tpl— live inference viewer (needs the<project>_inferencingEdge Impulse library installed in the sketchbook)
Copy the template to <workspace>\<NN>_<name>\<NN>_<name>.ino (folder = ino
name), then replace the placeholders literally:
| Placeholder | Meaning | Example |
|---|---|---|
__WIFI_SSID__ |
2.4 GHz router SSID | myhome |
__WIFI_PASS__ |
router password | pass1234 |
__MDNS_NAME__ |
hostname (lowercase, no spaces) | xiao |
__EI_PROJECT__ |
inference app only — Edge Impulse project name, matching the installed <name>_inferencing library folder |
clfc |
The template already falls back to a XIAO_CAM hotspot if the router is
unreachable for 15 s, so a wrong password degrades gracefully.
Step 3 — compile, upload
arduino-cli compile --fqbn esp32:esp32:XIAO_ESP32S3 --board-options PSRAM=opi <SKETCH_DIR>
arduino-cli upload -p COM4 --fqbn esp32:esp32:XIAO_ESP32S3 --board-options PSRAM=opi <SKETCH_DIR>
PSRAM=opi is mandatory (camera + model need PSRAM). The inference build
needs --clean if the Edge Impulse library was just swapped.
Step 4 — verify end-to-end
The PC is on the same LAN, so HTTP is the fastest proof the whole path works:
Start-Sleep -Seconds 20 # boot + WiFi join
curl.exe -s -m 15 http://<mdns-name>.local/ -o "$env:TEMP\page.html"
(Get-Item "$env:TEMP\page.html").Length # > 1000 bytes = page served
# inference app only:
curl.exe -s -m 15 http://<mdns-name>.local/classify # JSON with scores
If mDNS resolution is flaky, find the IP once (ping <name>.local or router
DHCP table) and use it directly — report BOTH addresses to the user.
Watching the board over serial
HTTP proves the page is up, but it cannot show you why a board failed to
join the router. Serial can: the sketch prints the SSID it tried, the IP it
got, and the mDNS name. Use the xiao-serial-monitor skill for it.
mon # interactive, port auto-detected
.\scripts\mon.ps1 -Seconds 15 # bounded, for your own debugging
Expected on success:
Connecting to WiFi ... IP: 192.168.0.179
mDNS: http://<mdns-name>.local
Collector ready
Two things worth knowing rather than avoiding:
- Opening the port resets the board via DTR, so the log restarts from boot. That is usually what you want here — the WiFi join messages only print once.
- If a board ends up stuck showing only the ROM banner afterwards
(pitfall 8 of
xiao-esp32s3), re-runningarduino-cli uploadclears it.
When the user wants to watch it themselves, hand them the command rather than
launching the interactive monitor from a tool call — it never returns. In
Claude Code the ! prefix runs it in their session:
! <path>\mon.ps1
Notes
- Collector page: captures go to an in-browser gallery (label badges, per-shot
delete, "전체 ZIP 다운로드" produces
label.N.jpgfiles ready for Edge Impulse). Refreshing the page clears the gallery — download the ZIP first. - Inference page: whole-frame colored box + top label + per-class confidence bars (classification model — per-object location boxes need a FOMO model).
- The camera renders rotated 90°; the inference template already compensates (CW feature rotation, verified empirically).