Persistent settings live in NVS (namespace te) under src/settings/. They are exposed on GET/POST /settings, the Config web UI, and must stay in sync with API docs.
Existing keys: sleep_timeout, hostname, volume, welcome, serial_log, continuous_timeout, loading, access_token, wifi_ssid, wifi_password, sranges (packed servo min/max blob), rgb_ord (WS2812 byte-order string), oled_rot (OLED 180° rotation bool). Follow the same pattern for a new one.
| Choice | Convention |
|---|---|
| Storage | NVS under namespace te; short key name (≤15 chars for Preferences) |
| RAM | Cached in a module-static after initSettings(); callers use getters |
| Update API | Nullable pointer args to saveSettings(...) — only non-null fields change |
| HTTP | Query params on POST /settings; at least one param required |
| Apply timing | Prefer immediate apply. If boot-only (like hostname), set reboot_required and document it. WiFi credentials are setup-AP-only and tested before save. Servo min/max (servo_mins / servo_maxs), RGB LED mapping (rgb_order), and OLED rotation (oled_rotate_180) are setup-AP-only and apply immediately |
| Types | Prefer small integers / short strings; validate before any NVS write |
Work through these layers in order. Mirror an existing setting (volume is the simplest integer example; hostname shows reboot + string validation).
1. Core — src/settings/
Public API: settings.h. RAM cache: internal.h / cache.cpp. NVS keys: nvs.h / nvs.cpp. Load: load.cpp. Save: save.cpp. Reset: reset.cpp. Getters: getters.cpp. Validation: validate.cpp.
- Add
SETTINGS_DEFAULT_*, min/max (or length) constants insettings.h. - Add NVS key string in
nvs.h(e.g.kKeyFoo = "foo") and persist it inwriteAllToNvs/settingsNvsPutAllinnvs.cpp. - Add RAM cache variable in
internal.h/cache.cpp; load + validate ininitSettings()(load.cpp); fall back to default on bad/missing data. - Add getter
settingsFoo()ingetters.cppandsettingsValidateFoo(...)invalidate.cpp. - Extend
saveSettings(...)insave.cppwith a nullableconst T* foo:- Reject the whole save if validation fails.
- Require at least one non-null arg among all settings.
- Write all persisted fields together (current pattern rewrites sleep/host/volume/welcome/serial_log/continuous_timeout/loading/access_token each save; wifi/servo/rgb/oled too).
- Log the new value on load and save via
logSettingsSnapshotincache.cpp(for secrets likeaccess_token, log set/unset only — never the raw value).
Call the getter wherever the value affects behavior (e.g. settingsVolume() in audio). Prefer reading the getter at use time so POST /settings applies without reboot.
3. HTTP — src/http/settings_handlers.cpp
- Include the field in both
snprintfbranches ofsendSettingsJson(with and withoutreboot_required). - Grow the JSON buffer if the payload no longer fits.
- In
handleSettingsPost:server.hasArg("foo")- Parse (same style as
sleep_timeout/volume:strtoul+ end-pointer check for integers) - Validate → 400 with a clear
errorstring - Pass pointer into
saveSettings
- Update the “missing …” 400 message to list the new param.
4. Web UI — src/http/index_page.cpp
- Config form control (
#config-foo). Use.range-row+ range/number for 0–N scales (see volume). loadSettings()readsj.foo; save POST includes&foo=….- API reference table under POST
/settings: param, type, range. - Home Config card blurb if the setting is user-facing.
Per .cursor/rules/sync-api-endpoints.mdc:
docs/api.md— GET example JSON, field table, POST curl + param table, apply timing.README.md— short mention if the Config/settings summary lists settings.docs/hardware/testing.md— settings rows in the route table if present.
pio runFlash only when you want to try it on hardware (pio run -t upload).
| Layer | What landed |
|---|---|
| Settings | SETTINGS_DEFAULT_VOLUME = 70, key vol, settingsVolume(), range 0–100 |
| Audio | Tone amplitude and WAV PCM scaled by settingsVolume() / 100 |
| API | "volume":70 on GET; POST /settings?volume=40 |
| UI | Slider + number on Config; live N% label |
| Docs | api.md + README |
- Partial writes: validate first; never write NVS then fail validation mid-way.
- JSON buffer:
sendSettingsJsonuses a fixedcharbuffer — bump size when adding fields. - Factory reset:
factoryResetSettings()inreset.cppclears NVS namespaceteand writes defaults, including WiFi credentials. Servo min/max (sranges), RGB LED mapping (rgb_ord), and OLED rotation (oled_rot) are written back unchanged. Exposed asPOST /settings/reset. After reset, power-cycle into setup AP mode to configure WiFi again (WiFi is not editable on the normal Config page). Servo ranges, LED mapping, and screen rotation can be retuned in that wizard. - Hostname-style settings: freeze the boot value separately if live change cannot apply (see
settingsBootHostname()/reboot_required). - HTML string size: the panel is a big string literal in
index_page.cpp; keep controls compact. - Doc drift: HTML param tables must match
api.mdexactly.
- Runtime API:
api.md(GET/POST /settings,POST /settings/reset) - Endpoint sync rule:
.cursor/rules/sync-api-endpoints.mdc