Commit Graph
152 Commits
Author SHA1 Message Date
torlando-agent[bot] d0e1ce273c feat(ui): add app launcher and NomadNet MVP 2026-08-05 00:22:59 +00:00
torlando-agent[bot] 6ec39f1ca1 [verified] fix: reset radio activity on reconfiguration 2026-08-04 20:41:15 +00:00
torlando-agent[bot] 7daac8471e fix: make radio activity snapshots task-safe 2026-08-04 16:46:55 +00:00
torlando-agent[bot] a03a6279eb fix: preserve elapsed time across radio sampling gaps 2026-08-04 16:31:30 +00:00
torlando-agent[bot] d773202b13 fix: bucket radio events on the sampling cadence 2026-08-04 16:19:45 +00:00
torlando-agent[bot] 7508129c88 fix: preserve activity markers and supported glyphs 2026-08-04 16:09:22 +00:00
torlando-agent[bot] 93760af5d5 feat: add current-channel radio activity view 2026-08-04 15:56:56 +00:00
torlando-agent[bot] 9832131db6 [verified] fix: correct LoRa bitrate units 2026-08-04 01:52:37 +00:00
torlando-agent[bot] c869842088 [verified] feat: add SF5 LoRa support 2026-08-04 00:06:46 +00:00
torlando-agent[bot] ae91ccdd92 [verified] fix: restore watchdog panic with bounded polling 2026-08-03 20:48:12 +00:00
Torlando 8a72f40198 address transport opt-in review findings 2026-08-03 18:14:44 +00:00
Torlando df2fe4542f make Reticulum transport mode opt-in 2026-08-03 17:50:13 +00:00
torlando-agent[bot] 4a960035a1 [verified] fix: align LXST signal queue capacity 2026-08-03 02:30:22 +00:00
torlando-agent[bot] dcf863e581 [verified] fix: accept LXST profile and mode signals 2026-08-03 01:20:59 +00:00
torlando-agent[bot] 61872480c9 fix: enforce ULBW-only LXST voice profile 2026-08-02 00:39:45 +00:00
torlando-agent[bot] 80ed4357ad fix(voice): prevent stale liveness timeout sample 2026-08-01 23:00:01 +00:00
torlando-agent[bot] c2c6ae5641 fix(voice): synchronize call termination and TCP announces 2026-08-01 21:32:01 +00:00
torlando-agent[bot] cfa6fa3afa test: harden LXST contention harness 2026-08-01 14:14:56 +00:00
torlando-agent[bot] bc0f95b100 fix: serialize LXST callbacks across interface tasks 2026-08-01 14:14:56 +00:00
torlando-agent[bot] 5ba41b7c59 fix: serialize LXST call start ownership 2026-08-01 14:14:56 +00:00
torlando-agent[bot] 69f16ca2bb fix: bind LXST callbacks to exact link identity 2026-08-01 14:14:56 +00:00
torlando-agent[bot] 8e33fb15ab fix: ignore stale LXST link callbacks 2026-08-01 14:14:56 +00:00
torlando-agent[bot] 360010f004 fix: reserve incoming calls during identification 2026-08-01 14:14:56 +00:00
torlando-agent[bot] 5a1f1c33b4 refactor: make call generation ownership explicit 2026-08-01 14:14:56 +00:00
torlando-agent[bot] 3351cab398 test: strengthen call admission contention coverage 2026-08-01 14:14:56 +00:00
torlando-agent[bot] 63e0fd0380 test: cover generation-scoped call admission 2026-08-01 14:14:56 +00:00
torlando-agent[bot] 9d5cc32b82 fix: avoid duplicate delivery-state persistence 2026-07-27 04:24:58 +00:00
torlando-agent[bot] 0506452f77 docs: record message-store stack contract 2026-07-26 15:43:54 +00:00
torlando-agent[bot] e885f27c77 fix: prefer opportunistic delivery for short UI messages 2026-07-26 15:30:45 +00:00
torlando-agent[bot] dcc80e14d7 fix: report active firmware and LittleFS storage 2026-07-25 02:01:05 +00:00
torlando-agent[bot] 0ea2be3fda fix: confirm successful OTA boot before restart 2026-07-24 19:21:56 +00:00
torlando-agent[bot] 12d25b2acc fix: preserve retryable messages on storage failure 2026-07-24 19:02:06 +00:00
torlando-agent[bot] 4e2e74296b fix: fail closed when message persistence fails 2026-07-24 18:22:35 +00:00
torlando-agent[bot] 0f3b39bc19 fix: serialize LXST audio teardown on loopTask 2026-07-23 02:11:39 +00:00
torlando-agent[bot] 8a54ba457e fix: remove invalid PSRAM address heuristic 2026-07-22 19:53:15 -04:00
torlando-agent[bot] efd3b0bb8f fix: address Greptile safety findings 2026-07-22 19:28:32 -04:00
torlando-agent[bot] 31631b0afd fix: make full-duplex voice fit live device memory 2026-07-22 13:28:23 -04:00
torlando-agent[bot] ae1f474491 fix(input): debounce phantom trackball button events 2026-07-22 11:46:10 -04:00
torlando-agent[bot] 9e151877cc fix(voice): stabilize LXST call lifecycle and add E2E harness 2026-07-22 10:25:45 -04:00
torlando-agent[bot]andClaude Opus 4.8 0281cb5b8e fix(audio): mute loopback speaker during raw-mic recording — the actual "garble" was acoustic feedback
The dominant garble was NOT the mic: T:RAWMIC/recorder ran full-duplex (start_loopback), so
the T-Deck played the decoded loopback round-trip out its OWN speaker, inches from the mic ->
acoustic feedback that built into "oscillating static that gets slowly louder". Gate the
playback ring-write off while the PSRAM recorder is active (i2s_playback.cpp) so raw-mic
captures are feedback-free -> speech becomes intelligible (Whisper recovers ~85% of words).

Also in this change:
- Recorder stores BOTH TDM channels (main.cpp) for offboard de-interleave; confirmed CH0 is
  the mic, CH1 dead -> de-interleave was always correct.
- MCLK raised to an exact APLL-locked 12.288MHz (es7210.cpp MCLK_DIV_FRE=768 + the
  {12288000,16000} coeff; i2s_capture.cpp use_apll=true/fixed_mclk) -- cleaner than the
  fractional-N 4.096MHz, though the feedback (not jitter) was the real cause.

Residual (under investigation): signal-correlated distortion ("static reacts to speech") +
50Hz mains hum (USB-ground pickup, removed by the existing 300Hz HPF in the call path).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
2026-06-24 21:46:57 -04:00
torlando-agent[bot]andClaude Opus 4.8 bd7b357b96 fix(audio): ES7210 mic gain saturation + heap-starvation reboots + raw-mic diagnostic harness
Confirmed fixes:
- LVGL hybrid-allocator threshold 1024->256 (lib/lv_mem_hybrid.h): moves ~45KB of
  small LVGL objects to PSRAM, fixing internal-heap starvation that made the
  call/loopback pipeline intermittently fail to allocate and reboot mid-call
  (internal free 71->116KB, largest contiguous block 61->106KB).
- ES7210 mic PGA gain 21dB->12dB (lxst_audio.cpp): 21dB saturated the ADC -- an
  rms-7003 noise floor in silence + 0x8000 negative-rail spikes + a spectral-peak
  shift that masqueraded as a "+17% pitch warp". At 12dB idle silence is clean (rms 48).

Diagnostic harness (PYXIS_TEST_HOOKS, test-only): T:RAWMIC[stage], T:REG, and
T:RECORD/T:DUMPREC add a raw-mic UDP tap plus a reliable PSRAM recorder with a
checksummed serial transfer, and T:LOOPBACK wiring, for objective mic-capture
analysis (offboard Whisper STT scoring).

Exploratory / still unresolved: captured speech remains garbled (oscillating static,
likely an ES7210 sigma-delta capture instability -- under investigation). Includes a
16kHz/main-PLL clock path + MICBIAS exploration; the "warp" was the gain artifact, not
a clock issue, so the 16kHz path can later be simplified back to 8kHz.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
2026-06-24 17:21:25 -04:00
torlando-agent[bot]andClaude Opus 4.8 b2236d5901 fix(settings): correct announce/sync prefs defaults + stale unit comments
Addresses Greptile review on #37. SettingsScreen has its OWN settings load
(separate from main.cpp's app_settings), which still defaulted announce to 60s and
sync to 3600s and overrode the intended defaults -- so the UI still showed the old
values. Set them to 3600s (1h) and 14400s (4h) to match. Also updated two stale
block comments in update_ui_from_settings / update_settings_from_ui that still said
"minutes" after the sync field was promoted to hours.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
2026-06-22 23:37:07 -04:00
torlando-agent[bot]andClaude Opus 4.8 36d93f965f fix: announce/sync interval units+defaults, live Settings clock, OTA repair
Settings/clock (user-reported):
- Announce interval: default 1h (was 60s); the field is now MINUTES (label
  "Announce Interval (min):", populate /60, read *60).
- Prop sync interval: default 4h (was 1h); the field is now HOURS (label
  "Prop Sync Interval (hrs):", populate /3600, read *3600); log prints hours.
- Settings clock now ticks live: SettingsScreen::tick() (throttled ~1s) is hooked
  into UIManager::update() like the announce-list tick, so the Time/GPS/system
  readouts refresh while the screen is open instead of being a static snapshot.

OTA repair (was broken two ways; diagnosed via an ultracode workflow):
- patch_nimble.py hardcoded the `tdeck` libdeps path, so building any OTHER env
  (tdeck-ota gets its own libdeps tree) never received the NimBLE patch that
  DEFINES nimble_host_reset_reason -> undefined-reference link error. Made it
  env-aware (env.get("PIOENV",...)), matching the four sibling pre-scripts. Also
  restores 3 NimBLE stability patches that were silently missing from OTA builds.
- ArduinoOTA onStart synchronously called TCPClientInterface::stop() (blocks up to
  30s), overrunning espota's ~10s connect-back window -> "No response from device"
  (phase 2), and its un-fed watchdog tripped reboots. Made onStart non-blocking.
- [env:tdeck-ota] upload_command invoked a non-existent tools/espota.py -> switched
  to upload_protocol=espota.
- Do NOT WiFi.setSleep(false): the ESP32 requires WiFi modem-sleep when WiFi+BT
  coexist (aborts at boot otherwise) -- documented inline.

Verified on device: tdeck-ota links; OTA over WiFi completes 100% (Result: OK) and
the device reboots clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
2026-06-22 23:28:48 -04:00
torlando-agent[bot]andClaude Opus 4.8 27ff17b2ad fix(gps): Arizona no-DST carve-out + raise unsynced-display threshold to 2024
Addresses Greptile review on #36:
- Arizona is inside the Mountain longitude band but doesn't observe DST (and it
  straddles the Mountain/Pacific boundary), so MST7MDT wrongly showed MDT in
  summer. It now gets an explicit MST7 (no-DST) carve-out by lat/lon box before
  the longitude bands. (The DST-observing Navajo Nation is not separately handled.)
- The Settings "Time:" display used a >= 2016 threshold, which let the ESP32's
  ~2016-01-01 first-boot default render as a real date instead of "not set".
  Raised to >= 2024 so an unsynced clock is visibly unsynced.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
2026-06-22 17:08:22 -04:00
torlando-agent[bot]andClaude Opus 4.8 c208e9aaec feat(settings): show current time in GPS status + fix HDOP readout
- Add a live "Time:" line to Settings -> GPS showing the system clock (e.g.
  "Time: 2026-06-22 16:32:12", or "not set" when unsynced), so the GPS time-sync
  can be verified on-device at a glance -- a far-future year or "not set" flags a
  rollover/unsynced clock without needing serial.
- Fix the HDOP readout: it divided by 100 a second time (hdop.hdop() already
  returns the true value), so it always showed ~0.0 (Ideal). Now shows the real
  geometry -- which matters since HDOP is the metric the new sync-gate keys off.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
2026-06-22 16:44:20 -04:00
torlando-agent[bot]andClaude Opus 4.8 834f047200 address greptile: gather all before sort + re-add containers to focus group
- AnnounceListScreen::tick(): drop the pre-sort 64-item gather cap so the sort
  always sees the true newest destinations (the path table is bounded by
  USTORE_DEFAULT_MAX_RECS=400 and items allocate in PSRAM, so the gather is
  bounded); only the render stays capped at MAX_DISPLAY.
- tick() now re-adds the freshly rendered item containers to the focus group and
  focuses the first. The deferred gather meant show() (which adds widgets to the
  group) ran before the containers existed and lv_obj_clean dropped the old ones,
  so trackball navigation couldn't reach the list items.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
2026-06-21 03:11:14 -04:00
torlando-agent[bot]andClaude Opus 4.8 142ff464e8 fix: populate announces end-to-end (ratchet name, blob leak, path-table, safe refresh)
Pins microReticulum to the fork's pyxis-fixes-on-0.4.1 @ b582986 (three 0.4.1-graft
regression fixes) and adds the pyxis-side pieces.

microReticulum fixes (via the pin):
- Identity::recall now accounts for the announce ratchet (matches validate_announce)
  — it read app_data 32 bytes early for ratchet-bearing announces (Sideband/Columba),
  garbling peer display names.
- DestinationEntry caps persisted random_blobs to PERSIST_RANDOM_BLOBS (16); the set
  was never trimmed, growing path entries ~12B/re-announce past microStore's 1024B
  ceiling so put() rejected them (empty path table) — and under a raised ceiling
  bloated the FS until compaction crashed.
- Transport mirrors learned paths into _path_table so path_table() (the UI announce
  list + T:PATHS) reflects them; the microStore migration stopped populating it.

pyxis pieces:
- -DUSTORE_DEFAULT_MAX_RECS=400 caps the live path-store record count (defense-in-depth).
- AnnounceListScreen::refresh() defers to a main-loop tick(): the gather (path-table
  iteration + per-entry recall/app_data) runs off the LVGL lock, serialized with the
  main-loop path-table writes, instead of racing them on the LVGL task and blocking
  past the 5s lock timeout — which hung/crashed on open once the table was non-empty.

Verified on device: display names correct, T:PATHS 0 -> 11, no rejections, no
crashes, announce list opens cleanly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
2026-06-21 02:58:45 -04:00
torlando-agent[bot]andClaude Opus 4.8 e1760291f5 fix(chat): use lv_obj_del_async to close the full-message view (greptile)
on_full_message_close() runs from the Close button's own callback, and the button
is a descendant of the modal — so lv_obj_del(modal) freed the button mid-dispatch
(use-after-free). lv_obj_del_async defers the delete until the event completes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
2026-06-20 17:22:47 -04:00
torlando-agent[bot]andClaude Opus 4.8 22e985087a feat(chat): long-press a message to view its full text (and copy)
Long-press a bubble to open a scrollable full-message view with Copy/Close.
Bubbles render truncated for scroll performance, so the handler recovers the FULL
stored content (row -> hash -> item) for the view -- which also fixes Copy, which
had regressed to copying the truncated label text after the render cap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
2026-06-20 14:42:02 -04:00
torlando-agent[bot]andClaude Opus 4.8 28c40de3a1 fix(chat): cap rendered bubble text so large messages don't crawl on scroll
A message with multi-KB content (e.g. a large bz2-delivered payload) rendered
untruncated, so LVGL laid it out as a 50+ line wrapped bubble and re-drew the
whole thing while scrolling past it -- crawling the UI. Cap the *displayed* text
to MAX_DISPLAY_CHARS; the full content stays stored. (Decompression is unrelated:
it happens once in Resource::assemble() at receive, content is saved already
decompressed, and load_message_metadata never re-decompresses.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
2026-06-20 01:24:14 -04:00