Skip to content

web dashboard

Wouter Van de Wiele edited this page Aug 6, 2026 · 2 revisions

Web Dashboard (WEB boot mode)

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 under data/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.

Full dashboard, light mode Full dashboard, dark mode

(Theme follows the browser's prefers-color-scheme — there's no in-app toggle.)

Endpoints

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).

Command reference

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.

Motor teleop safety model

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
Loading
  • Watchdog: if no motor_drive arrives 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_control is 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 MotorDriver calls from the httpd task are safe: all I2C is serialized by the shared i2c_operations mutex.

Memory notes

  • One shared 4 KB(-ish) JsonDocument + _jsonBuf on 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).

Dashboard walkthrough

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.

Drive Control

Drive Control card
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.

LEDs

LEDs card
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).

Web Radio

Web Radio card
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.

Time Settings

Time Settings card
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.

Alarm Sound

Alarm Sound card

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.

Clone this wiki locally