-
Notifications
You must be signed in to change notification settings - Fork 0
web dashboard
lib/WEB_DASHBOARD_IDF/ serves a single-page dashboard from SPIFFS using the
native esp_http_server (esp-idf httpd, not ESPAsyncWebServer). It only
exists in BootMode::WEB; in STREAM mode the object is never constructed.
- Server starts lazily on the first
update()after WiFi connects, on port 80. - Config: 7 sockets, 15 URI handlers, 8 KB handler stack, LRU purge on.
- Static responses set
Connection: close— with few sockets, keep-alive would starve the next request. - Frontend assets live in SPIFFS at
/www/:index.html,app.js,style.css(sources underdata/www/).
Screenshots below are pixel-accurate renders of the real frontend (data/ www/* served unmodified, populated with mock API responses) — see
tools/dashboard_render to regenerate
them after a UI change.

(Theme follows the browser's prefers-color-scheme — there's no in-app
toggle.)
| Method | Path | Returns |
|---|---|---|
| GET |
/, /index.html, /app.js, /style.css
|
static files (chunked from SPIFFS) |
| GET | /api/stations |
{defaultCount, stations:[{name,url}]} |
| GET | /api/state |
{station, volume (0..1 slider fraction), playing, motorsEnabled} |
| GET | /api/time |
{year,month,day,hour,minute,second} from the RTC |
| GET | /api/led |
{mode, effect, r,g,b, brightness, slots:[{colors:[{r,g,b}×6]}×5]} — mode 0=Off/1=Auto/2=Manual |
| GET | /api/alarm |
{enabled, station, backupSound} |
| POST | /api/cmd |
executes a JSON command (below), returns {"ok":true}
|
| OPTIONS | /api/cmd |
CORS preflight (* allowed) |
GET /api/feeds and the RSS commands below are registered in
NvsStore/data/www history but currently commented out in
web_dashboard_idf.cpp (_h_feeds/rss_add/rss_remove are #if 0'd) —
the News feature isn't wired into this build.
POST /api/cmd bodies are {"cmd": "<name>", ...params}, max 512 B, with a
2 s receive timeout (a stalled client would otherwise block the single httpd
worker task for everyone).
cmd |
Params | Effect |
|---|---|---|
led |
r,g,b |
set static color; clears led_effect so the color is visible immediately (not persisted — fires continuously while dragging) |
led_brightness |
value 0–255 |
global brightness |
led_mode |
value 0/1/2 |
Off / Auto (mood engine) / Manual (Effect pick, saved slots, mixer) — persisted |
led_effect |
value 0–26 |
animated effect / saved slot (persisted) |
led_set_pixel |
index, r, g, b |
direct single-LED write (bypasses blackboard, for live slot painting; only meaningful under LED Mode = Manual) |
led_save_slot |
index 0-4, colors[6] |
persist a per-LED color set to NVS slot |
radio_set_start |
station |
mark which station STREAM mode resumes on next boot (no live playback in WEB mode — I2S conflict) |
radio_volume |
value 0..1 |
≤0.01 → STOP; else mapped to driver 0.4–1.0 and persisted |
station_add / station_update / station_remove
|
name,url / index,name,url / index
|
edit the station list; posts AudioCmd::RELOAD. Built-in (seeded) stations cannot be removed |
alarm_audio_enable |
value bool |
master switch for the alarm playing a webstream/backup clip |
alarm_station |
value |
which saved station streams when the alarm fires |
alarm_backup_sound |
value 0–13 |
local clip played instead if there's no WiFi when the alarm fires |
set_time |
year..second |
write the RTC |
set_ntp |
server |
store NTP server |
ntp_sync |
— | queue an NTP sync now |
set_tz |
tz (POSIX string) |
set timezone |
motor_enable |
value bool |
teleop master switch (RAM-only; disabling stops motors immediately) |
motor_drive |
m1, m2 −100..100 (%) |
direct differential drive (see below) |
Alarm on/off and the alarm time itself are not set here — those stay on
the LCD menu (Menu → Alarm); this card only controls what plays.
There is no WebSocket: driving works over repeated short POSTs.
sequenceDiagram
participant B as Browser (app.js)
participant H as httpd task
participant W as loop() watchdog
B->>H: motor_enable {value:true}
loop while a direction is held (every 500 ms + on every press/release)
B->>H: motor_drive {m1, m2}
H->>H: gate on motor_manual_control,<br/>clamp ±100, direct MotorDriver calls
H->>H: _lastHbMs = millis()
end
Note over W: update() every loop() iteration
W->>W: millis() - lastHb > 2000 ? → force stop
B->>H: motor_drive {0,0} on release / blur / tab hide / focus into text field
-
Watchdog: if no
motor_drivearrives for 2 s while motors were being driven,loop()force-stops them (HB_TIMEOUT_MS; the 500 ms client heartbeat gives ~4 missed packets of margin). -
Boot safety:
motor_manual_controlis never persisted — every boot/reconnect starts with teleop disabled. -
Client-side stops:
stopMotors()fires on tab hide, window blur, and when focus enters a text input (so WASD typing can't drive the robot). - Direct
MotorDrivercalls from the httpd task are safe: all I2C is serialized by the sharedi2c_operationsmutex.
- One shared 4 KB(-ish)
JsonDocument+_jsonBufon the instance, reused by every handler (single-threaded httpd → no races). - Static file serving reads 1 KB chunks; response is chunked; the 8 KB task
stack is deliberately conservative (the SPIFFS
streamFile+ flash-cache-disable interaction needed 16 KB on the old Arduino-server implementation — bump if crashes appear while serving assets).
The page is a single scrolling column of cards: Drive Control is always
open; the rest are collapsible (<details>, remember their state per-page-
load only — closed again on refresh). A status pill top-right reads
Connected/Disconnected based on whether /api/stations answered.
| Option | What it does |
|---|---|
| Motors Enabled | Teleop master switch. Must be ticked before the D-pad/keyboard does anything; unticking force-stops immediately. Always starts unticked after any reload/reconnect (never remembered). |
| Speed | 10–100 %, scales every drive command; shown live as it's dragged. |
| D-pad (▲◀■▶▼) | Press-and-hold (mouse or touch) to drive; the center ■ is a dedicated stop. Differential-drive mixing (m1=fwd+turn, m2=fwd−turn) happens in the browser, then a motor_drive command is sent immediately and re-sent every 500 ms while held. |
| M1 / M2 | Live readout of the last computed motor percentages (left/right). |
| Keyboard | W/A/S/D or arrow keys drive the same way, disabled while a text field has focus so typing doesn't steer the robot. |
Safety: releasing the button/key, hiding the tab, losing window focus, or clicking into any text field all immediately send a stop.
| Option | What it does |
|---|---|
| Mode | Off — LEDs stay dark. Auto — the mood engine picks effects/colors on its own (see Mood Manager). Manual — the Effect picker, saved color slots, and the RGB mixer below take over instead. Persisted; same setting as the LCD menu's Mood → LED Mode. |
| Effect | One of 21 animated effects (only applied while Mode = Manual). Persisted. |
| color bar | Live preview of the current R/G/B mixer color. |
| Red / Green / Blue | 0–255 sliders; only mix a preview color — they don't touch the physical LEDs by themselves (use a slot's "Copy from mixer" button to actually apply one). |
| Brightness | 0–255, global dimmer for animated effects and saved slots. Persisted. |
| Custom Color Slots | 5 slots, each holding one independent color per physical LED (Eye L/R, Ear LR/LL/RL/RR). Only visible while Mode = Manual — otherwise the next effect tick overwrites whatever you set. |
Each slot has, per LED: a color picker (edits live-apply that single LED
immediately via led_set_pixel, so you see it on the robot right away) and a
Copy from mixer button that pulls the current R/G/B mixer color into just
that LED. Slot-level buttons: Apply (show this slot's saved colors now,
discarding unsaved picker edits), Randomize (fill all 6 pickers with
random colors — not saved until you press Save), Save (persist the 6
current picker colors to that slot in NVS). Apply saved slots later from the
LCD menu or Quick Menu (FX → Slot 1–5).
| Option | What it does |
|---|---|
| Volume | 0–100 %; below the floor stops playback. No live audio plays here though — WEB mode and STREAM mode share the same I2S hardware and can't run at once. |
| Stations list | Every saved station (up to 20). Built-in stations carry a Built-in badge and can't be deleted; your own additions get a Del button instead. The radio button marks which station STREAM mode boots into next — it does not start playback now. |
| Add form | Name + stream URL (MP3 only — AAC/HLS aren't supported by the audio pipeline) to append a new station. |
| Option | What it does |
|---|---|
| Current Time | One-shot readout of the RTC at page load — not kept live/ticking. |
| Set Time | Pick a date/time and click Set to write the RTC directly. |
| NTP Sync | Sync Now queues an immediate time sync (also happens automatically once per boot when WiFi connects). |
| NTP Server | Custom NTP hostname (default pool.ntp.org), saved with Save. |
| Timezone | POSIX TZ string presets for common regions — applied immediately, no restart needed. |
Only takes effect while booted in Web Radio mode (STREAM) — this card
controls what plays when the alarm fires, not whether/when it fires;
enabling the alarm and setting its time are still done from the LCD menu
(Menu → Alarm).
| Option | What it does |
|---|---|
| Enabled | Master switch for the alarm playing audio at all (vs. silently just waking the screen). |
| Station | Which saved station streams when the alarm rings. |
| Backup (no WiFi) | One of 14 local sound clips played instead if the station can't be reached at alarm time. |