Commit Graph
27 Commits
Author SHA1 Message Date
Torlando 2527c6d96b fix(settings): Greploop remediation — three Greptile P1s
Address the three P1 findings from the Greptile review of PR #96:

1. Network LoRa toggle reverted on save. on_interface_switch_changed
   saved without propagating the Network switch state, so the save path
   read the stale Radio-page twin and wrote it back over the mirror.
   The handler now mirrors the flipped switch to its twin (and toggles
   the LoRa params visibility) before saving, mirroring
   on_lora_enabled_changed in the other direction.

2. Immediate controls committed unsaved drafts from other sub-views.
   Every immediate handler ran the full-screen update_settings_from_ui,
   so typing a TCP host and then adjusting brightness silently persisted
   the host. update_settings_from_ui is now view-scoped: save_settings
   reads only the sub-view that just changed (LVGL events only fire for
   visible widgets, so _view is always the touched view). The lora
   handle merges as Radio-page switch wins, Network mirror when the
   Radio page is unbuilt.

3. Hidden controls retained focus. hide() removed only the header
   buttons and hub cards, and the focus_group_for cleanup list was
   missing the three Radio dropdowns — so hidden sub-view controls
   stayed in the default group (and Radio dropdowns lingered after
   leaving the Radio view). Extracted the full removal list into
   remove_all_from_focus_group (now including _dropdown_lora_bandwidth/
   _sf/_cr) and use it from both focus_group_for and hide.

Verification: tdeck build SUCCESS (RAM 23.1%, Flash 92.7%),
tests/build_scripts 170/170. All save_settings() call sites confirmed
event-driven so _view always matches the touched control.

Signed-off-by: Torlando <tyler@torlando.tech>
2026-09-04 23:42:15 +00:00
Torlando 4fffed3c1b fix(settings): Network view polish — slider overlap, row order, button clarity
- Move the LoRa TX Power slider from x=75 to x=86: the 'TX Power:' label
  in Montserrat 14 is ~78px wide, so the slider track started on top of
  the colon.
- Network sub-view: the Reticulum interface toggles (TCP/AUTO/LoRa/BLE)
  are now row 1, relabeled 'Reticulum Interfaces:', with WiFi SSID,
  WiFi password, TCP host and port following in their original order.
- Rename the ambiguous 'Reconnect' button to 'WiFi Reconnect' — it
  re-applies the WiFi SSID/password fields to the running stack and has
  nothing to do with the TCP host. Sized 110px to fit the label.

Signed-off-by: Torlando <tyler@torlando.tech>
2026-09-04 22:06:24 +00:00
Torlando e6ff73ec30 fix(settings): move LoRa TX power slider clear of the label
'TX Power:' in Montserrat 14 is ~78px wide but the slider track started
at x=75, tucking the colon under the track. Start the track at x=86
(8px clear; still 51px from the right-aligned value label).

Signed-off-by: Torlando <tyler@torlando.tech>
2026-09-04 21:57:42 +00:00
Torlando 72130e07c6 fix(settings): build sub-views lazily to fix LVGL task create at boot
Pre-building all seven settings sub-views at construction created
~100 LVGL objects that (being <256B) allocated into internal DRAM via
the hybrid allocator. On the T-Deck that fragmented internal heap to
an 8180B largest block at LVGL task-creation time, and the 8KB task
(needs ~8.5KB with TCB) failed: xTaskCreatePinnedToCore returned
errQUEUE_FULL, the device parked on the boot splash, and
confirm_running_firmware never ran.

Root cause proven with an on-device heap probe (largest internal
block 8180B, 8192-stack task FAIL, 4096-stack OK), then fixed by
building each sub-view on first navigation. Boot-time internal DRAM
rises to a 47KB largest block and the task starts cleanly.

Signed-off-by: Torlando <tyler@torlando.tech>
2026-09-04 21:15:04 +00:00
Torlando aef7b7f39d fix(settings): create sec_label with lv_label_create, not lv_obj_create
Boot-loop regression from the hub-and-spoke rewrite: lv_label_set_text()
on a plain lv_obj object wrote label internals into a non-label and
freed a bogus pointer (heap_caps_free assert 'outside heap areas') during
UIManager::init -> SettingsScreen::create_advanced_view, crashing every
boot at 'Initializing UIManager'. Verified by decoding the on-device
backtrace (ELF 62cf1d0e...) against the exact flashed firmware.
2026-09-04 18:52:43 +00:00
Torlando dfb2a73876 feat(settings): hub-and-spoke card layout (Columba-inspired)
Replace the flat Settings screen with a card hub + dedicated sub-views.
- Hub: 8 navigation cards (Status, Network, Identity, Radio, Delivery,
  Appearance, Advanced, Transport) reusing the Network-screen card widget;
  Transport stays last (danger invariant preserved).
- Tap a card -> dedicated sub-view holding that area's controls; no accordion.
- Network sub-view gains a LoRa interface toggle alongside TCP/Auto/BLE;
  it two-way-mirrors the Radio page's canonical lora_enabled switch.
- Save model: simple controls apply immediately; a Save button appears only
  on the form sub-views (Network, Radio, Identity).
- Identity sub-view adds a View Identity row routing to the existing lxma://
  QR screen (the Status Share button already reached it).
- Focus group rebuilt per view (only-visible objects) so the auto-scroll-to-
  bottom class of bug cannot recur; each sub-view entry scrolls to top.

Contracts updated for the new structure; 170/170 build-script tests pass.
tdeck build green: RAM 23.1% (75,848 B, unchanged), Flash 92.7%
(2,914,713 B, +2,640 B). Not flashed, not PR'd.
2026-09-04 18:26:39 +00:00
Torlando 9ccadba207 refactor(settings): strip SCROLLDIAG instrumentation after physical confirmation
The explicit scroll-to-top in show() was confirmed working on the device
(open-at-top on every entry, including repeat entries after scrolling to
the bottom). Remove the temporary Serial logging and the 30ms deviation
watcher.
2026-09-04 14:41:37 +00:00
Torlando 5a32b9c0a3 diag(settings): instrument scroll position at entry and watch for post-entry movement
Temporary [SCROLLDIAG] build: show() logs _content scroll_y before and
after the explicit reset, and a 30ms LVGL timer logs the first deviation
from 0 after entry. Captures the actual mechanism behind the bottom-open
instead of guessing. Remove before PR.
2026-09-04 14:16:15 +00:00
Torlando d33ae5bb6f fix(settings): recreate entry scroll-reset timer after one-shot delete
The one-shot LVGL timer's callback deleted the timer but left
_entry_scroll_reset_timer dangling. On the 2nd+ screen entry show()
took the 'else' path and called lv_timer_reset() on freed memory, so
the view was never reset to the top — it stayed wherever the group
re-focus had auto-scrolled it (the bottom). Null the pointer before
lv_timer_del so each entry recreates a fresh timer.
2026-09-04 13:40:45 +00:00
Torlando f62f465958 fix(settings): don't auto-scroll to bottom on screen entry
LVGL re-focuses the default input group when the screen is shown; the
focused member is the last focusable object (the transport switch), so
the view landed at the bottom of the list. A one-shot 50ms LVGL timer
after show() scrolls the content back to the top without touching the
focus state, and leaves subsequent user scrolling alone.
2026-09-04 05:52:56 +00:00
Torlando 28ea218a18 refactor(settings): move status readouts to Status screen, reorganize by use
Settings held live status (GPS fix, storage/RAM/identity) that belongs
on the Status screen, plus a per-second tick() doing SPI flash stat
reads and label churn mid-scroll — the main cause of laggy scrolling.

- GPS section (sats/location/altitude/HDOP/time) -> StatusScreen
- System Info (firmware build, storage, RAM) -> StatusScreen, with
  storage/RAM stat reads throttled to ~5s and stack-buffer snprintfs
  instead of Arduino String concatenation
- Settings gains a Status link row (trackball-reachable) that opens
  Route::STATUS; the per-second SettingsScreen tick/refresh is deleted
- Reordered sections by frequency of use: General (name/brightness/
  timeout/kb-light), Notifications, Network (now includes the
  TCP/Auto/BLE interface switches), Radio (LoRa + params), Delivery,
  Advanced, DANGER: Transport Mode (still final)
- Identity/LXMF hashes shown in Settings were truncated duplicates of
  the Status screen's full display; removed
- main.cpp publishes firmware build + GPS to the Status screen
- Contract test for the storage readout follows the code to StatusScreen
2026-09-04 04:56:07 +00:00
torlando-agent[bot] 117d3894bd feat: make maps SD-pack only 2026-08-07 05:26:34 +00:00
torlando-agent[bot] 354dda9cda fix: close final power-cut review 2026-08-07 01:37:13 +00:00
torlando-agent[bot] b3f38b1b8c fix: close final transactional review 2026-08-07 01:36:21 +00:00
torlando-agent[bot] 340c9c240f fix: close map integration safety review 2026-08-07 01:35:35 +00:00
torlando-agent[bot] f55b54eec0 feat: wire safe opt-in map downloads 2026-08-07 01:34:30 +00:00
torlando-agent[bot] c869842088 [verified] feat: add SF5 LoRa support 2026-08-04 00:06:46 +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] 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]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 70d4aa6be9 feat: graft pyxis onto upstream microReticulum 0.4.1
Repins microReticulum + microLXMF onto the upstream-0.4.1 graft and adapts
pyxis to the new src/microReticulum/ layout and 0.4.x APIs. The far-diverged
0.3.0 fork's Resource/Transport/Identity work is subsumed by upstream's
reimplementation; only the still-needed fixes ride on the pinned branches
(PKCS7/HMAC/X25519 crypto -- proven byte-identical to python RNS 1.3.1 --
Packet link-proof callback, Identity short-sig guard, and the bz2 layer +
decompress-on-receive in Resource::assemble()).

Consumer-side changes:
- platformio.ini: pin microReticulum @2f21fee (pyxis-fixes-on-0.4.1) and
  microLXMF @33760d0 (chore/microreticulum-0.4.1-layout); bump microStore
  ceea8f5 -> c5fb69d (0.4.x requires the new BasicFileStore::init API);
  -std=gnu++11 -> gnu++17 (upstream requires C++17).
- Namespace all microReticulum includes (angle + quote) to <microReticulum/...>
  for the relocated layout; shim-local Utilities/Stream.h|Print.h preserved.
- Interface::send_outgoing now returns bool: update TCP/BLE/SX1262/Auto
  overrides with correct success/failure returns.
- SDArchiveFileSystem::init(bool reformatOnFail=true) to match new microStore.
- Static Transport::get_path_table() -> path_table(); instance getter unchanged.
- Remove duplicate shim Cryptography/BZ2 (microReticulum provides it now; keep
  lib/libbz2 as the ESP32 bzlib provider).
- patch_littlefs_paths.py: normalize microStore's LittleFS adapter paths to a
  leading "/" -- ESP32 Arduino LittleFS rejects "./"-prefixed paths, which
  silently broke the path store (no peer paths learned, all messaging blocked).

Validated on T-Deck Plus: builds (RAM 27.5% / Flash 77.7%), boots stable
(no WDT/panic), and a full on-device LXMF e2e (DIRECT + OPPORTUNISTIC +
bz2-compressed-Resource receive) passes 5/5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
2026-06-19 15:49:44 -04:00
torlando-techandClaude Opus 4.6 ac6ceca9f8 Initial commit: standalone Pyxis T-Deck firmware
Split T-Deck firmware from microReticulum examples/lxmf_tdeck/ into its
own repo. microReticulum is consumed as a git submodule dependency pinned
to feat/t-deck. All include paths updated from relative symlinks to bare
includes resolved via library build flags.

Both tdeck (NimBLE) and tdeck-bluedroid environments compile successfully.
Licensed under AGPLv3.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 19:48:33 -05:00