The Format button (and its busy overlay) is compiled for T-Deck, M9 and
V4-R8, and Copy/Paste for every Files-app board, but both deferred workers
in UITask::loop were gated T-Deck-only. Elsewhere the tap set a pending
counter nothing consumed: the notice stayed up forever and the format
latch blocked SD remounting until reboot. Widen the format worker to the
button's gate (via the per-board sdSharedSPI accessor), ungate paste.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A repeater name with an emoji (multi-byte UTF-8 cut at a byte cap) or a
literal '#' desynced the LVGL recolor runs in the Discover feed, visually
killing every row after it; same class in the message-info repeat list.
Names are now UTF-8-boundary-cleaned and '#'-escaped at both sites, and
copyUtf8ReplacingMissingGlyphs no longer emits a split glyph at its cap.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
'Retry sends until heard' switch under Radio & Mesh gates mikecarper's retry
train (#207): OFF stops new retry trains (an in-flight one finishes its
schedule), applied live and persisted (cfg v44 retry_echo, default ON).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_wdtHeavyDepth() is a plain int, but WdtHeavyGuard is taken from both the
UI/loop task and the core-1 history worker (histFlushTaskFn ->
uiSegRunArmedJob -> uiSegAppendRecords/uiSegCompactWrite, each of which holds
one). Both run at priority 1 on core 1, so ordinary tick round-robin can
preempt between the load and the store of d++ / --d.
That race predates this PR, but this PR makes it likely rather than
theoretical: the new guard in rebootDevice() is constructed immediately
before uiHistWaitWorkerIdle(), a deliberate wait of up to 9 s on the very
worker that also takes the guard. Interleave the two and either
- the depth strands above zero, so enableCore0WDT() is never called again
and IDLE0 stays unsubscribed from the task watchdog for the session, or
- the depth drops to zero while a guard is still held, re-arming the
watchdog during the flash burst it exists to cover -- which panics
mid-save and loses exactly the chat history rebootDevice() runs to
persist.
Use std::atomic with an acq_rel fetch_add and a compare-exchange decrement.
The CAS preserves the original `d > 0` underflow guard, which still matters:
doFactoryReset() calls wdtHeavyBegin() with no matching end because it
reboots, so the counter can legitimately stay elevated and an unbalanced end
must not drive it negative.
Signed-off-by: Pixel Perfect <me@pixp.cc>
MeshCore's airtime_factor is the Dispatcher's TX pacing multiplier: duty =
1/(1+af), so a 10% duty limit needs af = 9. The presets wrote af = pct/100
(0.10), which configured every preset user's radio to ~91% duty -- the Home
meter's 'always 91%' was truthfully reporting the misconfiguration, on every
region preset including UK/EU.
- applyMeshRadioPresetFields converts duty% -> factor correctly (clamped to
the core's 0..9).
- findMatchingMeshRadioPreset accepts both the correct and the old buggy value
so previously-configured devices still show their preset in the dropdown.
- healPresetAirtimeFactor (run once at UI begin) rewrites the factor for
devices whose params exactly match a preset and whose stored factor matches
the OLD buggy write -- the exact-match requirement is the guard against
touching deliberate user-set values.
NOTE for the web client: Meshcomod-client/shared/radio_presets.ts documents
the same pct/100 convention and needs the matching fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pager's SD support shipped in pieces (mount ladder with XL9555 card-detect,
tile-cache fallback, shared TFT_eSPI SPIClass) but CAP_SD/CAP_FILESYSTEM were
never flipped from the pre-wiring 0, so the Files app gates and the whole
'Store data on SD' machinery stayed compiled out -- an inserted, mounted,
browsable card that nothing would store to. Flipped the caps, extended the
boot storage selection + SPIFFS->SD migration + SdNvsPrefs routing gates to
TLORA_PAGER, and exported tloraPagerSharedSPI() from the variant so main.cpp
can mount the card at boot on the shared bus like the T-Deck.
Both pager envs + T-Deck + V4 + R8 build.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- #165: the animated post-unlock overlay still said MESHCOMOD; now WADAMESH.
- #191: Discover app rows cap the name and use single-letter node types so the
trailing last-heard age always fits the line.
- #157: the Info popup only read the transport scope for FLOOD messages, so a
region-scoped DIRECT message showed no scope; and senders that carry the
region in transport_codes[1] with codes[0]=0 showed Scope 0000. The scope is
now read for any RX and falls back to codes[1] when codes[0] is zero.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A card whose power-up handshake only completed at 400 kHz kept 400 kHz as its
SESSION clock — SD.begin's frequency is the operating clock, and neither the
boot ladder (main.cpp) nor the runtime ladder (fmSdTryMount) ever raised it
after a slow success. With the SD as the T-Deck's primary store that meant
loading contacts + history at ~25 KB/s: the reported ~3-minute boot, f_getfree
timeouts making an inserted card show as 0 KB/empty in the file manager, and
backups wedging. Launcher installs hit it most (the Launcher's own SD use
leaves the card needing the slow wake path more often).
Standard SD bring-up is init-slow-then-raise: after any sub-4MHz success both
ladders now re-begin at 4 MHz, falling back to the clock that just worked if
the fast re-begin fails. A healthy card is unaffected (mounts at 4 MHz on the
first rung). T-Deck + V4-R8 + M9 (the shared-SPI SD boards) build.
Likely also behind the long-standing Launcher-mode weirdness cluster together
with the torn-prefs fix (#195/#196): slow-clock sessions made every write
window enormous.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stale CAT_RADIO from before the settings reorg gave GPS its own category —
every other tile already pointed at its own page. All boards.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The R8 env shipped HELTEC_V4_BUZZER_PIN=-1 because the Kit V2's PAM8904 wiring
was unconfirmed, so R8 users have never heard a notification. Heltec's
Expansion_board_V2.03.pdf schematic settles it: DIN = GPIO4, and EN1/EN2 are
hardware-strapped high (10K pull-ups, 3x boost) with DIP switch SW1 as a
hardware mute -- no enable code needed, the stock V4 tone() chime drives DIN
directly. GPIO4 is unclaimed on the R8 (board I2C moved to 17/18).
Tester-verify: chime on message. If still silent, SW1 on the expansion board
is the hardware mute -- check it is not switched to ground.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>