Skip to content

feat(companion): add the CompanionSettings module (part 1 of 3) - #30

Open
Elektr0Vodka wants to merge 11 commits into
mainfrom
Feat/companion-settings-module
Open

Elektr0Vodka wants to merge 11 commits into
mainfrom
Feat/companion-settings-module

Conversation

@Elektr0Vodka

Copy link
Copy Markdown
Owner

Part 1 of 3 of the companion settings module (design, plan). Candidate 1 of the companion architecture review.

What changed

  • PrefsCodec (new): the /new_prefs byte layout and its legacy variants (13-byte meshcomod tail, 14-byte upstream tail, no CAD byte), as pure code driven by two field tables. DataStore now only reads and writes the file.
  • SettingsTable (new): one row per companion setting (plus lat/lon). Each row has the CLI key, type, offset, default, range, clamp-or-reject, apply kind and boot-sanitise flag. Build-dependent values (radio defaults, TX limit, name and repeat-frequency checks) come from SettingsBuild.
  • CompanionSettings (new), with two ports:
    • SettingsApplier: radio, TX, RX boost, CAD, GPS, change hook.
    • PrefsStore: load and save.
    • set*() or Edit::commit() validates every staged value, checks that the board can apply it, applies only what changed in a fixed order, notifies, and saves once.
    • A remote caller's radio change is saved but not applied.
    • setByKey / getByKey give the CLIs a text front end, using today's reply texts.
  • MyMesh:
    • keeps the NodePrefs storage;
    • owns a CompanionSettings over it, and implements SettingsApplier;
    • uses the module for defaults, the boot range check, savePrefs() and the boot radio apply (applyRadioFromPrefs() -> applyAll()).
  • Tests: 49 native tests in test/test_companion_settings, including byte-for-byte checks against copies of the old loader and saver.
  • Docs: changelog entry, plus the design and plan docs.

No setting writer moves in this PR: the app frames, CLIs, backup import and UIs still write _prefs directly. Part 2 moves the frames and CLIs, part 3 the UIs.

Why

Every NodePrefs field is written from about 15 places, and each writer decides on its own whether to validate, apply and save. That caused the get cad "unsupported" gap, the touch UI's missing applies, and bounds that differ per path. This PR lands the module and its tests without changing how the device behaves, so parts 2 and 3 are mostly mechanical call-site changes.

Behaviour changes

At boot:

  • A stored frequency between 150 and 400 MHz is kept. It used to be raised to 400.
  • A NaN or infinite float in the file loads as its default.
  • The node name is always NUL-terminated.

applyRadioFromPrefs() (boot and the touch radio page) now also re-applies the stored CAD value. This has no observable effect, because Dispatcher::loop already re-asserts CAD on its first pass.

Nothing else changes: app frames, CLI replies and UI texts are untouched, and the prefs file layout is identical, so a downgrade is safe.

How it was tested

  • pio test -e native: 151/151 pass, 49 of them new.

  • DISABLE_DEBUG=1 build.sh build-firmware builds for these envs:

    • heltec_v4_companion_radio_usb_tcp
    • Heltec_v3_companion_radio_usb_tcp
    • WioTrackerL1_companion_radio_ble
    • LilyGo_T-Echo_Card_companion_radio_ble
    • Heltec_v2_companion_radio_ble
  • Flash, against main at 2bb91cb, with the same flags:

    • Heltec V4: +2,924 B
    • Wio Tracker L1 BLE: +2,288 B
    • T-Echo Card BLE: +2,304 B

    All three are within the spec's 3 KB budget. The first version was over budget, and the fix is in its own commit.

  • A fresh reviewer went over the whole branch: no critical or important findings. Eight minor follow-ups are noted for parts 2 and 3. They include static_asserts on the enum sizes, tying the codec's capacity check to its field tables, and tightening the store port's return values.

  • Not tested on hardware. Please check on a V4 or V3:

    • settings survive a reboot;
    • a radio change from the app still applies live;
    • flashing the previous release afterwards still loads the settings.

Firmware or build impact

Companion only. The native test env compiles four more files (PrefsCodec, SettingsTable, CompanionSettings, TxtDataHelpers).

Candidate 1 of the companion architecture review: one module owns each NodePrefs setting's default, range, persistence and apply step, so the app frames, CLIs, backup import and UIs stop writing fields directly. Design only, nothing built.
PrefsCodec encodes and decodes the companion prefs file with the same layout and legacy-tail handling DataStore uses today, in pure code so the native tests can check it against a copy of the old loader and saver.
DataStore keeps the file I/O and hands the bytes to PrefsCodec, so the layout and its legacy variants live in one tested place. The two load_prefs_tail helpers and the stale offset comments go with it.
One row per NodePrefs setting (plus lat/lon): CLI key, type, offset, default, range, clamp or reject, apply kind, and the reply texts today's CLI uses. Build-dependent values (radio defaults, TX limit, name and repeat checks) come from SettingsBuild.
One module owns the companion settings: defaults from the table, the boot-time range check, and set or Edit, which validates every staged value, checks the board can apply it, applies only what changed in a fixed order, notifies and saves once. A remote caller's radio change is saved, not applied.
The CLIs will reach every CLI-visible setting through setByKey and getByKey: the value is trimmed and parsed by type, and the reply uses the texts today's handleCommand sends, so wiring it in later keeps replies identical.
MyMesh owns a CompanionSettings, keeps _prefs as a reference into it, and implements SettingsApplier. Defaults, the boot range check, savePrefs and the boot radio apply now go through the module. No setting writer moves yet.
The first build added 3.8 KB on the T-Echo Card and 3.9 KB on the Heltec V4, over the 3 KB budget. MyMesh now keeps the NodePrefs storage and the module works over it, because a reference member cost a load on every one of its reads. The table rows are packed, with the few custom reply texts in a side table and the unused label and get-format columns dropped until the UIs need them. The prefs codec walks field tables instead of one call per field.
TxtDataHelpers.cpp, which the native env now compiles for the settings tests, calls ltoa. MinGW and the Arduino cores have it, glibc does not, so every native suite failed to build on the Linux CI runner while passing on Windows.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant