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.