Repository navigation
Conversation
No fork setting could survive a reboot on any companion build. Root cause:
ConfigSerializer's tokenizer accepts only [A-Za-z_] in keys (is_key_char,
src/helpers/ConfigSerializer.cpp) while the writer emits whatever key it is
given. The fork's ui group defined "clk12", so every /prefs.json we wrote was a
file we could not read back: loadSerial() hits the '1', returns TOK_ERROR and
abandons the REST of the file. Everything after that point silently stayed at
its default -- the rest of ui, and the whole wifi, mqtt, batt, ota and audio
groups.
"cfgver" is defined last at root, so it always landed in the truncated region and
always read back as 0. rescueAppendedPrefs() then concluded the file was foreign
(upstream, older fork, or corrupt), and with no /new_prefs to recover from it
called applyAppendedPrefsDefaults() and re-applied defaults -- on EVERY boot.
Observed on a 2.8: set screen orientation, reboot, back to default. The value was
written correctly and even parsed back correctly ("rot" precedes "clk12"); the
reset then discarded it.
This also explains WiFi never connecting: wifi{ssid,pass} sit after "clk12", so
stored credentials were never loaded.
Not a 2.8 issue -- it affects every fork build that writes prefs, the 3.5 and
T-Deck included. Pre-0.5.0 it was partially masked: the tail was still lost, but
the old value-sniffing migration did not wipe everything. Stamping the file with
cfgver (f01acf7) made a partial failure total, because cfgver is in the
unreadable tail by construction.
Fix, kept at the example boundary per the golden rule (ConfigSerializer is
upstream and untouched):
- def("clk12", ...) -> def("clk_ampm", ...).
- A prominent warning above UiPrefs documenting the key charset and this failure
mode, so the next key with a digit does not silently do this again.
- loadPrefs() now checks loadSerial()'s return and logs a warning. It was
discarded, which is why truncation was invisible.
Audited all 126 def() keys in the tree: clk12 was the only one outside [A-Za-z_].
Migration: one more reset on the first boot after upgrading (the old file still
contains clk12), then the rewritten file parses fully and settings persist. Users
lose nothing they actually had, since none of it was persisting.
Verified on 2.8: cfgver reads 1 and no parse warning across repeated boots.
Built: 2.8 + 3.5 LVGL, plus LilyGo_TDeck_companion_radio_usb.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Relationship to #2 — merge order does not matter
This is the standalone version of one commit that also appears in #2. #2 is opened from
my fork's
mainbranch, and this fix landed there too, so #2 currently carries it as itslast commit. That is bookkeeping on my side, not a dependency.
What matters for you:
mainand touchesonly
examples/companion_radio/{NodePrefs.h,DataStore.cpp}— nothing CrowPanel-specific.second contributes nothing new (the other PR's file list simply overlaps on these two
files). There is no ordering requirement.
I split it out because, unlike #2, this is not CrowPanel-specific and I did not want a
fix to your own boards gated behind AI-developed variant work you may not want.
It affects every companion build that writes prefs, including the 3.5 and T-Deck.
The bug: no fork setting survives a reboot
ConfigSerializer's tokenizer accepts only[A-Za-z_]in keys:…while the writer emits whatever key it is handed.
UiPrefsdefinesclk12. So every/prefs.jsonwritten is a file that cannot be read back:loadSerial()reaches the1,returns
TOK_ERROR, and abandons the remainder of the file. Everything after that pointsilently stays at its default — the rest of
ui, and the whole ofwifi,mqtt,batt,otaandaudio.cfgveris defined last at root, so it is always inside the truncated region and alwaysreads back as 0.
rescueAppendedPrefs()then concludes the file is foreign (upstreamfirmware, an older fork build, or corrupt) and, with no
/new_prefsto recover from, callsapplyAppendedPrefsDefaults()and re-applies defaults — on every boot.Here is the actual
/prefs.jsonoff my device, dumped at boot. Note it is well-formed andends in
cfgver:1; the reader simply never gets there:And the corresponding read-back, instrumented in
loadPrefs():freq=910.525loads correctly (radioprecedesui), which is what makes this soconfusing to diagnose — the file is clearly being read, just not all of it.
Two consequences worth calling out
parsed back correctly —
rotprecedesclk12— and is then discarded by the defaultsreset. That was my original symptom.
wifi{ssid,pass}sit afterclk12, so WiFi couldnever connect using saved settings. I had assumed that was a separate problem.
Why this may look new
Pre-0.5.0 it was partially masked. The tail was still lost, but the older value-sniffing
migration did not wipe everything, so the fields before
clk12(brightness, rotation,contact order/filter, tz) did persist. Stamping the file with
cfgverin f01acf7 turned apartial failure into a total one, because
cfgverlands in the unreadable tail byconstruction.
The fix
Kept entirely at the example boundary —
ConfigSerializeris untouched, since theoffending key is the fork's, not upstream's:
def("clk12", ...)→def("clk_ampm", ...).UiPrefsdocumenting the key charset and this exact failuremode, so the next key containing a digit does not silently do this again.
loadPrefs()now checksloadSerial()'s return value and logs a warning. It was beingdiscarded, which is precisely why the truncation was invisible.
I audited all 126
def()keys in the tree;clk12was the only one outside[A-Za-z_].You may also want to fix the tokenizer itself
I did not touch
src/helpers/ConfigSerializer.cpp— that is your upstream-tracked code andnot mine to change. But the underlying hazard remains: the writer accepts keys the reader
cannot parse, and fails silently and destructively. Worth considering, entirely your call:
is_key_char(they are valid JSON keys and the writer already emits them), and/orloadSerial()'sfalsereturn is easy to drop — asit was here.
Either would turn this class of bug from silent data loss into something immediately obvious.
Migration
One further reset on the first boot after upgrading, since the old file still contains
clk12; the rewritten file then parses fully and settings persist. Users lose nothing theyactually had, because none of it was persisting.
Testing
Verified on a CrowPanel Advance 2.8":
cfgverreads 1 and no parse warning across repeatedboots; settings survive a reboot. Builds:
elecrow_crowpanel_advance_35_companion_radio_lvgland
LilyGo_TDeck_companion_radio_usb, both from this branch's base rather than mydevelopment branch.
I have not tested on a 3.5 or T-Deck — I don't own either — though the change is
board-independent.
🤖 Generated with Claude Code