Skip to content

Settings storage

The in-memory settings model, JSON import/export schema, NVS layout, catalogue and application protocol have separate versions. JSON settings remain schema 22; BLE/browser properties and phone workflows are unchanged by this storage migration. AP credentials remain schema 1 in espurr_net/ap; SDK BLE bonds remain independent.

Records and ownership

All normal settings records use the espurr NVS namespace. Each group has two copies, suffixed 0 and 1. Every string is bounded to 4096 bytes including NUL.

Group keys Contents
controls0, controls1 Brightness, flashlight brightness, effect/speed, active palette and preset/cycling, background, overlays, catalogue parameters, sensitivity/filtering, Wi-Fi idle timeout
palettes0, palettes1 Eight saved named palettes
calibration0, calibration1 Mounting basis, gyro bias, validity and target/sensor/bus identity
hardware0, hardware1 Ear counts/directions/wire order, dithering, gamma/RGB trims and current limit

Each group is a JSON object {"schema":1,"values":{"schema":22,...}}. The outer schema versions that group's storage format. The inner schema identifies the existing settings field encodings and migrations. This first group format requires schema 22 values and exact group field membership, with no duplicate keys. Adding an exported field requires an explicit group assignment. Later field/schema changes must provide the appropriate group migration rather than silently dropping data.

The record key becomes a small selection marker:

{"schema":23,"split":1,"banks":0}

Schema 23 identifies this storage layout, and split versions the selection format. The four low bits in banks select copy 0/1 of controls, palettes, calibration and hardware respectively. No additional keys are accepted in the current marker. The loader reads only the selected copies. It never revives an inactive copy when the selected one is missing or corrupt; that copy may belong to an old transaction.

Saves and migration

The render owner retains the existing ten-idle-second coalescing and explicit-save behaviour. It validates one coherent settings snapshot, serialises each group and compares it with the selected copy. Unchanged groups make no NVS writes or commits. Each changed group is written and committed to its inactive copy. Only after every changed group succeeds does the owner write and commit the new selection marker. This makes a multi-group save visible through one atomic NVS key replacement, without rewriting unchanged palette/calibration/hardware payloads.

The first save after a legacy schema 1–22 load follows the same procedure. Reading and migrating a valid legacy record are read-only; persistence remains deferred until an ordinary save. Any legacy AP password is committed to espurr_net/ap first. Four copy-0 groups are then committed before the legacy record is replaced with the marker. An interrupted migration therefore retains the old full record, or selects the complete new groups. Staged orphan copies are ignored and can be overwritten on retry. Missing settings with no marker start from defaults and use the same first-save procedure.

NVS commits are not a transaction across keys. The inactive copies and final marker provide that boundary. A failure before marker replacement leaves the previous selected state intact. A failure reported while replacing/committing the marker is ambiguous: storage may already select the new state. Further settings saves are blocked until reboot/reload, preventing reuse of a possibly active bank. The next boot reads the marker and selects the complete old or new snapshot. AP provisioning retains its own independent record and error semantics.

The two copies cost additional live NVS space. Savings are in rewritten payload bytes, not a promise of fewer calls/erases or a measured flash-lifetime improvement. A changed group normally incurs its own commit plus the marker commit. Initial migration and saves spanning several groups cost more than a single-group save.

Repair and rollback

Supported corrupt group data defaults only the damaged group, retaining other selected groups and independent AP credentials/bonds. Valid fields within a supported structurally complete group are salvaged using the existing validation. Normal startup persists the repair through the same transaction, rewriting only changed groups. A damaged recognised marker has no trustworthy bank selection; normal settings default and can be repaired as a whole.

Recovery reads split settings but defers all split-group repairs to normal firmware. It cannot validate the normal sensor identity and must not rewrite a valid calibration as the recovery build's IMU-free identity. The existing startup repair of a wholly unusable legacy record remains available. Ordinary recovery settings edits remain prohibited; narrow AP provisioning is independent.

A newer layout, selection format, group envelope or inner JSON schema makes normal settings read-only and uses safe defaults. Unrecognised schema-23 records without a split marker are also preserved. NVS initialisation/I/O failures and allocation failures never trigger automatic repair. JSON syntax is checked without allocation before parsing, and salvage propagates allocation errors; low memory is not treated as corrupt data. No load/save path erases the NVS namespace or partition. Full reset remains the explicit USB storage reset.

Older firmware with a schema-22 ceiling sees the schema-23 marker, uses defaults and refuses settings saves. It does not see or overwrite a stale legacy snapshot. Reinstall compatible firmware to restore the saved groups; resetting storage after a deliberate downgrade discards them. Recovery/AP credential compatibility remains as described in the AP provisioning contract.

Host failure injection covers every set/commit boundary during migration and multi-group saves and blank first-save, including errors after a set has persisted, plus allocation failures, selective writes, repair isolation and newer schemas. It establishes the storage algorithm's behaviour with atomic-key stubs; physical brownout, flash wear, runtime heap/stack and real NVS garbage collection remain hardware acceptance.