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
or its DHCP IP.
Read the
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 , ask for the 2.4 GHz band name).
- 공유기 비밀번호.
- mDNS 이름 (optional, default ) — page becomes
. 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
are verified working code — do not rewrite them:
assets/web_collect.ino.tpl
— dataset collector (gallery + delete + ZIP download)
- — live inference viewer (needs the
Edge 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 |
|---|
| 2.4 GHz router SSID | |
| router password | |
| hostname (lowercase, no spaces) | |
| inference app only — Edge Impulse project name, matching the installed library folder | |
The template already falls back to a
hotspot if the router is
unreachable for 15 s, so a wrong password degrades gracefully.
Step 3 — compile, upload
powershell
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>
is mandatory (camera + model need PSRAM). The inference build
needs
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:
powershell
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 (
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
skill for it.
powershell
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 ), re-running clears 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:
Notes
- Collector page: captures go to an in-browser gallery (label badges, per-shot
delete, "전체 ZIP 다운로드" produces files 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).