Wi-Fi came back on after a power cycle on the T-Display P4 while every switch
in the UI read off, and the radio really was on: it downloaded a map tile
(#373, wb6zsu, with photographs of both states). The C6 runs its own AT
firmware with auto-connect enabled, so it rejoins the last access point by
itself at power-on, before the firmware ever asks it to. The loop that enforces
the radio pref only acts on a change, and on the first pass it recorded the
state instead of applying it, so "off" never became a transition and the join
the C6 made on its own was never torn down. That is also why toggling Wi-Fi on
and then off fixed it: that made a transition. The state is now applied on the
first pass, once the AT link is actually up. The Tanmatsu carried the same
first-pass logic and got the same fix.
The map's own location marker is a bare white glyph and vanishes on light
basemaps (#366, 100monkeys). The contact markers a few lines below it already
carry a dark border for exactly this reason and self never got one, so it now
gets the same: a dark chip behind the glyph, which reads on any basemap without
spending a colour that already means something else.
Wi-Fi passwords can be revealed while typing (#381, jesshampshire). Entering a
PSK blind on a device keyboard is a real failure mode: the reporter needed about
ten attempts to join their own network. The toggle sits on the password label's
line so it costs no vertical space, and it switches the keyboard mirror too, or
the characters stay masked in the one field being looked at.
The composer shows a character count (#350, jrote1). The 160-character cap was
silent: typing just stops, which reads as a broken keyboard. It appears only in
the last quarter and marks the limit when reached, so an ordinary short message
carries no readout. Counted in codepoints to match how the cap is enforced;
by bytes an emoji would read as four characters.
Built on all nine S3 envs and both ESP32-P4 targets.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follow-up to 3bf56cd, driven by the (correct) challenge that "SD writes upset
the panel electrically" could not be the whole story when tile writes never
flash and other P4 firmwares use their cards freely. The completed on-device
matrix now separates two distinct mechanisms:
M1 An SD write EXECUTING on core 1 (where display.begin() allocated the DSI
frame-restart ISR, and where the Arduino loop task runs) masks that
core's interrupts inside the SDMMC/FATFS critical sections long enough
to drop a display frame. Proven: 80 contact saves to SD from core 1
flash every time; the identical 80 saves hopped to core 0 never flash.
NOT electrical -- the challenge was right.
M2 An SD write CONCURRENT with live UI rendering still flashes even from
core 0 (manual add/delete flows pump LVGL while the hopped write runs;
the write-open trace shows those runs performed only correct core-0
writes and flashed anyway). The same manual flow with the card mounted
but ZERO SD writes (internal LittleFS store) is clean.
Shipped design therefore stays: hot store on internal LittleFS (immune to both
mechanisms, verified across every flow), SD for tiles/bulk. Tiles' long-standing
immunity is consistent: brief core-0 writes vs the store's long ones.
New in this commit, kept for good:
- p4StorageCall (DataStore.cpp): a core-0 storage task; every hot write entry
(saveContacts/saveChannels/savePrefs/blobs, history segs, threads file) hops
onto it, blocking the caller -- semantics unchanged, execution core moved.
With the internal store this is still wanted hygiene: no storage write ever
runs on the render core again.
- WADA_SD_STORE_TEST build gate: puts the store back on the SD card for future
M2 investigation (known to still flash on manual flows; do not ship).
- TDP4_POKE_TRACE now also logs every write-open with its issuing core ([SDW]),
which is what pinned M2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause, established by on-device elimination over one long session: SD-card
WRITE bursts electrically disturb the T-Display P4's AMOLED -- the whole-screen
blue flash of #167. The display stack itself measured clean at every layer while
flashes were visible: framebuffer content (flush tracer: every band black), DSI
bridge underrun interrupt (never fired), DSI host protocol errors incl. the host
payload-buffer underflow (never latched), DCS/brightness traffic and XL9535
expander writes (silent), disp on/off (never). The flash survived every digital
derate -- 4->1-bit SD bus, 20->10 MHz SD clock, 1000->750 Mbps DSI lane rate --
on every card tried, at any brightness (including near-zero, i.e. the panel is
being upset at the hardware level, not showing content), yet vanished the moment
the card was removed or left unwritten. The card's rail is switched off the same
3V3 domain as the panel (XL9535 SD_EN), so its program-current spikes reach the
panel and no firmware setting can filter them. Streaming tile writes never
flashed; rapid open-write-close store bursts always did. Meck-P4 not flashing
proves nothing about the board: it never writes the SD.
Fix: the hot store (contacts/channels/identity/blobs/prefs-file/chat history)
lives on the INTERNAL 'storage' partition; the SD keeps bulk, gentle writers
(map tiles, backups). The internal partition is mounted as LittleFS, not FAT:
the P4's FAT layer has documented broken metadata and the v3 migration proved
its writes are no better (a 102 KB contacts3 "copied" onto it read back as 0 B;
on LittleFS the same copy reads back byte-exact and loads all 689 contacts).
esp_littlefs looks partitions up by label with SUBTYPE_ANY, so the ex-FAT
partition converts in place on first boot (formatOnFail) -- no partition-table
change, OTA-safe.
One-time v4 migration (lessons v1-v3 encoded): substance-gated (only from a
card whose contacts3 beats the internal one -- a fresh test card once donated a
blank store), whitelist-only copy (v2 filled the partition with junk before
contacts3 landed), both historical card layouts probed (root vs /meshcomod),
marker written only after the copy, card never written (it remains the
rollback).
Also in this change, kept as opt-in diagnostics (dormant without their env
gates): WADA_P4_POKE_TRACE (DCS/expander/disp-on-off tracing + a DSI host
error-register poller), WADA_SD_KHZ / WADA_SD_1BIT (SD primary-rung derates),
WADA_DSI_MBPS (lane-rate override; default back at the vendor 1000 -- the 750
experiment changed nothing and is documented in the driver).
Verified on device: no flash on the contact-churn trigger with the store
internal (the previously 100%-reproducing trigger), 689/689 contacts after
migration, tiles still on SD.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
None of the three fixed the flash, and each carried a cost:
- prefs-on-SD (1bc0c7a) also moved main.cpp onto a different set of headers than
it had been compiling against. SdNvsPrefs.h's own comment warns that exact
mismatch previously caused a free()-of-garbage bootloop, and a map tile
regression showed up on these builds. Not worth the risk for no benefit.
- LVGL bands 24 -> 64 (1e6e121): a full-screen repaint went from ~26 transfers
to ~10 with no change on device, so burst count is not the mechanism.
- XIP-off (0695ca6): no change on device, so PSRAM bandwidth starving the DSI is
not the mechanism either.
Back to the configuration where map tiles were confirmed working. Kept the two
changes that stand on their own: the framebuffer is blanked before the panel is
switched on (it was streaming uninitialised PSRAM to the screen), and the
WADA_P4_FLUSH_TRACE build gate.
Standing evidence for whoever picks this up: the tracer shows every large band
LVGL pushes is 0x0000 black, including through the boot window where the flash
occurs, so the colour is never in the framebuffer and the UI layer is excluded.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The T-Display P4 never called SdNvsPrefs::useFile, so although contacts and chat
history correctly moved to the SD card, every settings/thread-state write still
landed in internal-flash NVS. On this board that is not just wear: the DSI panel
streams its framebuffer continuously out of PSRAM, and an internal-flash write
visibly disturbs it -- the whole-screen flash reported as #167. The trigger list
matches exactly: a message arriving, opening a chat with unread messages, and
deleting a contact (which writes repeatedly, hence flickering for the whole
operation). Map panning writes tiles to SD continuously and never flickers,
which is what pinned it down.
Only when a card is mounted. With no card there is nowhere better to put prefs,
and moving them from NVS to the internal FAT partition would trade one flash
write for another while needlessly relocating existing users' settings. Old NVS
values still load and migrate on their next save, so this is transparent.
Fixing it also required an include-path fix. The vendored core component
publishes core/src publicly and still carries OLD copies of the headers whose
.cpp files it deliberately excludes because our src/ ships them. main.cpp's
angle includes therefore resolved to the stale core headers while the linker
bound the new objects, so anything added since the core tag looked like it did
not exist (SdNvsPrefs::useFile, touchPrefsReload). Putting the repo's src/ ahead
of the core dir in main's INCLUDE_DIRS makes main.cpp and the app layer agree.
Verified on device: "[PREFS] backend = FILE /meshcomod".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause of "GPS on the P4 stays on searching forever", reported repeatedly and
chased through pins, baud, wake polarity, antennas and a comparison against the
Meck-P4 port. None of those were wrong. The loop simply never called
sensors.loop().
Every S3 board calls it once per iteration (src/main.cpp: STALL_SCOPE("sensors",
sensors.loop())). That file is excluded from this build and the P4 has its own
main loop, which is where the call went missing during the port: sensors.begin()
ran at boot and nothing ever pumped the manager again.
sensors.loop() is what drains the GPS UART into MicroNMEA. Without it the parser
never receives a byte, so isValid() is false forever and the UI shows "searching"
regardless of signal. It also starves the telemetry sensors and the GPS->RTC time
sync that hang off the same manager.
The evidence lines up exactly:
- a T-Deck with the same L76K fixes almost instantly, the P4 never does;
- 15 minutes under clear sky changed nothing, because no amount of signal helps
a parser that is never run;
- and a raw UART probe showed the MODULE reaching a valid 3D fix
($GNGGA,...,1,05,2.2 at HDOP 2.2, plus $GPTXT ANTENNA OK) that the firmware
had no idea about. That reading came from the probe, not the provider, which
is precisely why it looked like the GPS "worked" while the device disagreed.
Builds clean and boots clean on the P4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to 6430866. Two changes.
Print one example of each distinct NMEA sentence type, not just the first line.
A single GxGSV is not enough to judge satellite visibility: the first boot showed
"$GLGSV,1,1,00" and I read that as zero satellites, but GLGSV is GLONASS ONLY and
says nothing about GPS, Galileo or BeiDou. The full set showed 10 GPS + 5 GLONASS
in view with SNR 33/35/23, plus "$GPTXT,...,ANTENNA OK" from the module's own
antenna check, i.e. a healthy receiver mid-acquisition rather than a fault.
Shorten the drain from 3s to 1.2s. It runs on EVERY boot and it consumes bytes
the NMEA parser would otherwise see, so it only needs to be long enough to catch
one of each sentence type (the L76K repeats the full set about once a second).
1.2s still captured all nine types on the verification run.
Confirmed working on hardware: a later boot reported
$GNGGA,073620.000,5050.80018,N,00452.95932,E,1,05,2.2,36.0,M
$GNGSA,A,3,08,22,32,...,2.2
i.e. a valid 3D fix on 5 satellites at HDOP 2.2, down from no fix / HDOP 25.5 a
few minutes earlier. The GPS path was never broken; the old diagnostic just could
not tell anyone that, because it reported a flag ENV_SKIP_GPS_DETECT forces true.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The line here claimed "[GPS] L76K DETECTED (NMEA on GPIO22)" but never looked at
the UART. It reported whether the "gps" SETTING had been registered, and on this
board ENV_SKIP_GPS_DETECT forces that true unconditionally (we set it because the
P4's heavy boot misses the core's 1 s detect window, and a false negative hides
the GPS toggle entirely). So it printed DETECTED on every boot whether or not a
single byte had ever arrived, and it survived several rounds of "GPS still isn't
working" while proving nothing.
Replaced with a real 3 s drain of Serial1 that reports byte count, complete NMEA
sentence count, the configured baud and RX pin, and the first sentence seen. That
splits the two cases that were being conflated: bytes arriving means pins, rails,
wake and baud are all correct and any stuck "acquiring" is a FIX problem (antenna
or sky view); zero bytes means the link itself is dead.
First run on hardware answered the open question immediately:
[GPS] UART probe: 1985 bytes, 48 lines in 3s @ 9600 baud, module TX -> GPIO22
[GPS] first sentence: $GLGSV,1,1,00,0*79
1985 bytes of well-formed NMEA proves the link is good, and GLGSV ",00," is zero
satellites in view, i.e. an antenna/sky-view issue and not firmware.
For the record, the config was already identical to Meck-P4's working port
(components/meshcore/variant.h: PIN_GPS_TX 22 / PIN_GPS_RX 23, wake on XL9535
IO11), and their L76K driver confirms both our other values: begin() defaults to
9600 baud, and sleep(false) drives the wake pin HIGH.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>