Commit Graph
105 Commits
Author SHA1 Message Date
Kaj SchittecatandClaude Opus 5.5 b85eef6fec touch: beta_95, MeshCore 1.18 via meshcomod v1.18.0.1
About (Build) and the boot log name the meshcomod release the core is:
"on meshcomod v1.18.0.1". release.sh reads it from the pinned core's own
examples/companion_radio/MyMesh.h, keeps the in-tree defaults in step and checks
every staged image carries it. Language store v26 (this build's new texts),
release notes for beta_95.

Bench (T-Deck, 1.18): device and self info, 404-contact sync, the companion CLI
(ver, board, get name/radio/tx/freq, unknown command), 4 packets received and a
zero-hop advert sent in 90 s, correct device time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 18:07:34 +02:00
Kaj SchittecatandClaude Opus 5.5 d3a9cf1453 touch: radio tools redesign, Music app, Store update badge, triage fixes
Spectrum: stat tiles (peak, floor, our channel, busy), plot and waterfall drawn
at screen size (the lv_chart and zoomed waterfall were the page's render cost),
dark-to-yellow waterfall, peak hold, our-channel marker, Band/Channel and
Live/1 min/10 min chips. RF Monitor 1.4 (tiles, filled chart, typed feed, packet
detail card; the old type table did not match MeshCore payload types) and
Airtime 1.6 (big figure, tiles, duty line, rx/tx split).

Lua SDK: ui.panel, bold labels and a size-24 figure font, colors.warn/border,
app.on_audio (fires with the screen off), read-only wada.sd.list and sd: audio on
the SD_MMC boards (P4, Tanmatsu, Wio L2), caps panel/audio_events/epaper, a
"music" icon and an "audio" Store requirement. button:set() no longer writes
label fields into the button object (heap corruption, every earlier firmware).
The SDK page's input table described events the firmware never sent.

Music 1.0 (Store app): plays MP3/WAV from the card's Music folder, shuffle,
repeat, keeps going through the list with the screen off. Verified on the
T-Display P4 end to end.

Store: the update check also fetches the app catalog and badges the Store tile
with the number of installed apps that have a newer version. Baked Lua apps are
raw-deflated and inflated by the S3 ROM (14.6 KB back on the V4); the V4 language
table keeps only texts of apps it can run.

Fixes: the SD-card notice no longer fires when the card mounts fine (#667); the
Control Center clock keeps time while open (#666); M9 keyboard brightness (#664);
the battery widget's number no longer sits on a ring too small for it (Discord);
M9 shortcuts doc follows #669's composer order. New texts in all 14 languages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 17:07:11 +02:00
Kaj Schittecat 507511fab6 Merge PR #628 (oumike): Touch UI follow-ups 5a: quick fixes (#603, #605, #614, #617, #620, #625, #627, M9, Tanmatsu, Wio)
# Conflicts:
#	src/ui-touch/UITask.cpp
2026-10-14 10:17:19 +02:00
Kaj Schittecat 806ecb0f71 Merge PR #655 (oumike): Add the Elecrow CrowPanel Advance 5.0 (#651) 2026-10-14 10:16:48 +02:00
Michael A. Cojocari 2964ff02e3 fix(time): cold-boot Wi-Fi time sync gets past a slow first join (#620)
The first attempt on the active network brings the radio up from cold and
searches every channel for the AP, inside the same 4 s as every other
attempt. A T-Deck that joins in about 7 s by hand never made it, and the
scan retry then skipped that network as already tried, so a home with one
saved network always ended "no association".

The first attempt now gets 8 s. The scan retry keeps the active network
and joins each candidate on the channel and BSSID the scan found it on,
which skips the all-channel search. The whole session is capped at 16 s
(was 12 s) so the retry and SNTP still fit.

Fixes #620

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yt7fNCKmafm9LtRjyuUmS
(cherry picked from commit 477e0163ccf64d65706737a9558f1ef9ab439539)
2026-10-13 09:31:37 -04:00
Kaj SchittecatandClaude Opus 5.5 d0b1f58024 storage: a failed SD card no longer costs the node its key; the boot says what happened
Reported on Discord: a T-Deck that kept its data on a card that died booted
into first-time setup with a NEW private key, and the next good card was then
given the new key, so the old one was gone for good.

- DataStore keeps a copy of the main identity in NVS (namespace mc_id, 96 B):
  every save writes it, a load with no identity file falls back to it and puts
  it back into the active store, and a successful load seeds it when missing. A
  different copy is never replaced silently; only an explicit save changes it.
  Verified on a T-Deck: one boot with the card seeded mc_id.
- When the SD store was chosen but the card did not mount, the boot is flagged
  (g_sd_fallback_boot) and, instead of the setup wizard (whose done-mark is on
  the card too), a dialog says the card could not be read, that the key is kept
  (or, on a device that never had a copy, that it started with a new one while
  the card keeps the old), and offers Try the card again.
The retry ladder at lower SPI clocks already existed. Not yet done: copying the
data off a card that mounts but refuses writes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 11:44:58 +02:00
Michael A. CojocariandCopilot 3ce6b065e4 Add Elecrow CrowPanel Advance 5.0 build target
New env crowpanel_50_companion_radio_touch for the 800x480 ST7262 RGB panel,
GT911 touch and an SX1262 in the wireless slot. Board quirks follow Camillia-MT:
14 MHz pixel clock, internal-RAM draw band, PCA9557 (V1.0) / MCU (V1.1+)
backlight controller detection with GT911 reset at 0x5D or 0x14, NSS on GPIO8
(V1.2+), no SD/GPS/battery. Large-screen layouts and UI-size fonts; browser
file transfer to the internal tiles partition. Added to the upload helper,
release script, flasher manifests, site, DEVICES.md, README and NOTICE.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 15:07:32 -04:00
Kaj SchittecatandClaude Opus 5.5 9d0301b80a touch: beta_92, faster screens on most boards, Arabic fixes, GitHub fixes
Frame rate: full-width PSRAM draw band (6 bands a frame instead of 14 on the
T-Deck), the still aurora drawn once and copied, LVGL at -O2 with its blend and
label loops in IRAM (scripts/build/perf_lvgl.py, 1 KB more loop stack), circle
and gradient caches, newlib memcpy. Rolled out to the M9, Attaky, RAK, Wio,
CrowPanel and V4-R8; the Pager gets the band only, the V4 the caches only.
Frame benchmark tour (DOC_PERF_BENCH) for measuring it.

Fixes: hop counts read from the packed path length (#621); Arabic shaping
rows patched in LVGL (patch_lvgl_arabic.py), Arabic 101 shift layer, kaf,
symbols, text sent as standard letters (#582); info popup colour runs that
never wrap (#602); keypad nav reaches the chat header (#623); the browser
opens the changelog (#622, gen-changelog.py at every site deploy); the
Theme background colour picker no longer edits the accent; flasher entries
for P4 units with factory ESP-AT Wi-Fi chips (#612, #619).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:53:33 +02:00
Michael A. CojocariandCopilot 6c85948002 fix(boot): boot logo follows the saved orientation
The logo is painted before SPIFFS/SD mount, so it read rotation from the
legacy NVS settings copy, not the real file-backed settings. Any device
whose two copies disagreed (the Attaky after its v68 landscape reset, or
anyone who changed orientation since settings moved to files) showed the
logo in the old orientation until the UI took over.

Keep a raw-NVS boot copy of the rotation (touch/boot_rot): written by
touchPrefsSetUiRotation() and re-synced after touchPrefsReload() every
boot, so schema resets and restored backups catch up too. The logo reads
it, falling back to the old read when absent (first boot after update,
or Launcher installs without NVS). On the Attaky any landscape value is
treated as rotation 1, as UI init does.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-04 19:34:01 -04:00
Michael A. CojocariandClaude Opus 5.5 dbecef0b6f feat(crowpanel-35): add Elecrow CrowPanel 3.5" (ESP32-S3, ILI9488 + GT911) touch target
- New crowpanel_35 variant: display, SD, board and target files, plus board JSON
- Portrait mode is drawn rotated 180°; landscape stays at rotation 3
- Share the GT911 touch driver between Wio Tracker L2 and CrowPanel
  (WioTrackerL2Touch.cpp -> src/helpers/input/LgfxGt911Touch.cpp)
- platformio envs, flasher/site entries, release and merge-bin support
- Tests for the CrowPanel SD codec and merge-bin

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132MkJ7RUV32mV9F7o4PJMm
2026-10-02 20:16:06 -04:00
Michael A. Cojocari 31a6a9798e fix(tdeck-pro): make console readable on e-paper
Use white paper and black ink from the first boot frame, commit sprite-backed input updates, and keep the e-paper cursor solid.

Fixes #562
2026-09-23 10:23:45 -04:00
Michael A. Cojocari 33839f87f2 fix(wio-l2): retry SD mount after power cycle 2026-09-18 19:31:27 -04:00
Kaj Schittecat 02734a4642 Merge PR #523 from oumike: SD recovery, offline map upload, glance emoji, T-Deck Pro and V4 kit input
- Pager: the SD card mounts again after the card was used on a computer
  (#522): one SD-rail power cycle and retry, and power-off unmounts and powers
  the card down
- At a glance: emoji render instead of squares (#521)
- Transfer page: upload an offline OSM tile folder to /tiles on the SD card
  (#515), with strict path checks and resumable, atomic writes
- T-Deck Pro: touch release debounce, hardware-timed chat-row holds, and a
  typed space no longer starts the screen lock
- Heltec V4 with the original Expansion Kit: the IO button goes back (tap)
  or home (hold), larger keys with a magnified preview, and larger status-bar
  targets (#525)
2026-09-17 19:55:51 +02:00
Kaj SchittecatandClaude Opus 5 793a673c64 touch: Bluetooth LE keyboards on every S3 board
Bluetooth serves either the phone app or a keyboard (Settings > Bluetooth).
Pairing lists keyboards in pairing mode and pairs with or without a code;
the paired keyboard reconnects by itself.

Keys are read the way phones and computers read them: the keyboard's Report
Map says which report carries the keys and how, so media keys and touchpads
are left alone, and the boot protocol is only the fallback. Key positions are
translated with the chosen layout (US, UK, German, French, Belgian), with
AltGr and dead keys.

While a keyboard is connected:
- text goes into the focused field, and touchscreen-only boards keep the
  on-screen keyboard down (a second tap on a field brings it up anyway)
- the arrows, Tab, Enter and Page Up/Down drive the focus highlight
- Esc is the back button; a "Back key" setting picks another key for
  keyboards whose Esc key types a character
- tab hotkeys and their hints over the tab bar are switched on
- Command tapped alone opens the emoji picker in a chat
- the status bar shows a keyboard instead of the Bluetooth glyph

The Bluetooth page is now a flex column with the choice on top, and the
settings page refit keeps it scrollable when its content grows.

Settings: prefs v61 (mode, layout, paired keyboard) and v62 (Back key).
Tests: test/test_hid_report_map.cpp, test_touch_prefs_schema.cpp.
Not on the P4 boards (Tanmatsu, T-Display P4).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 19:52:21 +02:00
Michael A. Cojocari 9542756f91 fix(pager): recover SD after host use 2026-09-16 19:45:27 -04:00
Michael A. Cojocari ed69b56ed6 Merge remote-tracking branch 'refs/remotes/pr/514' into integration/all-open-prs-20260913 2026-09-13 18:58:23 -04:00
Michael A. Cojocari db11fcf2c5 feat: add universal microSD diagnostics 2026-09-13 18:40:11 -04:00
Michael A. Cojocari f3136ea951 Merge branch 'main' into feat/tdeck-pro-target
# Conflicts:
#	deploy/apps/lang/bg.lang
#	deploy/apps/lang/de.lang
#	deploy/apps/lang/el.lang
#	deploy/apps/lang/es.lang
#	deploy/apps/lang/fr.lang
#	deploy/apps/lang/hu.lang
#	deploy/apps/lang/it.lang
#	deploy/apps/lang/nl.lang
#	deploy/apps/lang/pt-br.lang
#	deploy/apps/lang/ro.lang
#	deploy/apps/lang/ru.lang
#	deploy/apps/lang/sr.lang
#	deploy/apps/lang/uk.lang
#	src/ui-touch/UITask.cpp
2026-09-08 15:41:45 -04:00
Kaj SchittecatandClaude Opus 5 f931fdfeda touch: Known regions back-out (#449), idle power-save gate (#450), review follow-ups
Known regions (#449, DenRalle on Tanmatsu): s_regions_modal was in no popup
registry at all. Without a row the Back ladder never peels it, so the page
stays on screen, and the open-popup COUNT stays zero, so whatever you open
next opens underneath it. Both halves of the report ("screen remains
visible", "pages open only in the background"), one missing line.

Idle power-save (#450, jrote1): the Battery page in the report shows the
gate blocked on "Wi-Fi on" with Wi-Fi disabled and cycles stuck at 0.
tsWifiOff() ANDed the wifiConfigGetRadioEnabled() pref with the real radio
state, and that pref DEFAULTS TO ENABLED when its NVS key was never written
-- so anyone who had not explicitly toggled Wi-Fi in this firmware read as
"Wi-Fi on" permanently. The gate is asking whether the modem draws power,
and WiFi.getMode() == WIFI_OFF answers exactly that on its own.

The other half of the same bug is in main.cpp: the power-down ran only if
wifi_started, but a scan or the version check can bring STA up without
going through that state machine, and then disabling Wi-Fi skipped
mode(WIFI_OFF) and left the modem running. getMode() != WIFI_OFF proves the
driver is inited, which is what the guard actually wanted to test, so it
closes the leak without reintroducing the low-heap esp_wifi_init crash the
original comment guards against. The block is one-shot behind
wifiConfigConsumeApplyRequest(), not per-loop.

Follow-ups from reviewing the merged PRs:

- #457 moved the Lua app page to the slim bar, which exposed that bar_h was
  measured BEFORE appPageBegin* installs the chrome -- so the body was sized
  against the OUTGOING page's bar. Install first, then measure (#236 again,
  one step earlier).

- #453 makes /meshcore-*.json downloadable, and those carry "private_key"
  plus every channel secret. The transfer is opt-in, LAN-only and rate
  limited (3 tries, 30 s lockout), so the surface is sound -- but it is
  plain HTTP, so say so on the page and flag backup rows in the list.

Reported-by: DenRalle, jrote1
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 14:06:59 +02:00
Kaj SchittecatandClaude Opus 5 96ba457984 Fix a Wi-Fi callback race that crashed at boot, and make the GPS privacy setting reachable
Two things, both found by looking rather than by being told.

A Heltec V4-R8 crash dump from beta_77 decodes to a data race in the Arduino
core's Wi-Fi event list. Correcting for the unwinder mangling Xtensa return
addresses, the event task was inside _eventCallback copying a WiFiEventCbList
entry, iterating the callback vector, while loopTask was still in setup() inside
_M_realloc_insert growing that same vector through WiFi.onEvent(). The
reallocation frees the buffer the event task is walking, so it then calls
through a dangling function pointer, which is the garbage program counter in the
dump.

Neither side locks, and the vector is the core's, not ours. What is ours is the
ordering: both handlers were registered AFTER the stack was started, one of them
after WiFi.begin(), so a STA_DISCONNECTED arriving during boot association lands
in the window. A failed first association is ordinary, which is why this happens
at all. Both now register before WiFi.mode(WIFI_STA), so nothing mutates the
vector once events can flow and the race is impossible rather than unlikely.
This is a crash during setup(), so on an unlucky unit it is a bootloop, which
makes it worth more than its single report.

The moved handler drops its WIFI_DEBUG_PRINTLN tracing, which comes from a
header not in scope that early. The prints compile to nothing in a release build
and the reconnect flag they accompanied is unchanged.

Separately: the location-privacy setting shipped in beta_77 on the wrong page.
It was inside the Sensors section rather than GPS, because the insertion was
anchored to a comment belonging to the Sensors block, and that section is
described in its own code as V4-with-kit only. So on most boards it was not
reachable at all, which is exactly what two people reported: they went looking
and correctly concluded it was not there. It now sits in the GPS section, is
named "Location privacy" rather than "Position in adverts" (nobody searching for
a privacy control scans for the word "adverts"), and offers four buttons instead
of a cycling one, so every option and the current choice are visible without
touching anything. Given the failure here was discoverability, showing the most
without interaction is the point.

All nine S3 envs and both ESP32-P4 targets green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 14:17:50 +02:00
Michael A. Cojocari 3a2781bb6e Work 2026-09-04 21:04:44 -04:00
Kaj SchittecatandClaude Opus 5 e374031056 Merge PR #391: Seeed Wio Tracker L2 support
oumike. ESP32-S3, 16 MB flash, 8 MB octal PSRAM, quad-SPI 320x240 panel via
LovyanGFX, GT911 touch, SX1262 at 22 dBm, GPS, microSD on SD_MMC. New board
file, new variant, one new env. Seeed start selling it next week and the
maintainers have the hardware; we do not.

Merged on their hardware bring-up rather than ours: display, touch, GPS,
microSD, battery and buttons are confirmed on-device by the author. What this
side can prove is that it does not cost anyone else anything, so the whole
matrix was built: all nine S3 envs including the new one, plus both ESP32-P4
targets.

The PR also carries accumulated touch-UI work from the author's development
line, which he flagged himself and offered to split out. Merged as-is because
the three-way merge came through with zero conflicts and nothing from today was
reverted: the prefs schema is still v54, and kb_force_legacy, boot_wifi_open,
the P4 gpsEnsureBigRxRing fallback and the chat-store migration logging are all
still present. Verified explicitly rather than assumed, since the diff against
main showed 1706 deletions purely because the branch predates today's merges.

Two bring-up findings worth keeping: Arduino's Serial1.setPins() takes (rx, tx),
so PIN_GPS_TX is the pin we receive on -- the same naming trap the V4-R8 and
Pager envs already carry, and it left this board's GPS listening on a silent
pin. And the panel is fixed landscape (CAP_ROTATABLE 0), so the keyboard's
rotate arrows are not built there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 23:55:30 +02:00
Kaj Schittecat 3a23d65b88 Merge PR #386 2026-09-03 23:08:48 +02:00
Michael A. Cojocari 19d557e70a Work 2026-09-03 12:52:03 -04:00
Michael A. Cojocari 092528a8fb Fix RTC retention and cold-boot time sync (#383) 2026-09-02 21:37:35 -04:00
Michael A. Cojocari 7cf9621fec Merge remote-tracking branch 'upstream/main' into square 2026-09-02 21:08:41 -04:00
Christopher Van HooseandClaude Fable 5 d59ce2c634 M9 battery pass: reachable saver, GPS-off leakage, mid-session UART ring
Three battery fixes plus the record in M9_PORT.md:

- The touchSleep battery saver was unreachable on the M9: the settings
  switch and its callback were compiled only for the T-Deck and V4-R8,
  and the pref defaults OFF with no other setter. Both #ifs now include
  HAS_THINKNODE_M9 (the M9 is in batteryIsCharging's #else branch, so
  the "USB powered" gate can never wrongly block there).

- WadaNmeaLocationProvider parked GPS_RESET asserted while the GPS was
  stopped, per the core's convention. On the M9 that polarity is
  inverted through R46 into NPN Q16, so "asserted" sourced base current
  the entire time the GPS was OFF. Park the line LOW instead — the
  zero-current level for both circuit styles; every start path still
  pulses reset() explicitly, so the clean-reset guarantee holds.

- A mid-session GPS enable ran the whole session on the stock 256 B
  Serial1 RX ring (the 4 KB TTFF ring is boot-gated on the persisted
  pref, and setRxBufferSize is a no-op on a running UART) — and with
  the saver newly reachable, one 50 ms park overflows that ring at the
  M9's 115200 baud. gpsEnsureBigRxRing() cycles the UART to 4 KB from
  both "gps on" funnels (toggleGPS, CMD_SET_CUSTOM_VAR) right after a
  successful start, so a refused enable never spends the DRAM.

Also corrects both stale "keyboard S2 on the switched rail" comments
(schematic truth: always-on 3V3 via R3 — it decides the deep-sleep
drain budget). All 8 envs compile; on-device VERIFY list in M9_PORT.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MYDmpc53KpdmTLKzUNsX2s
2026-09-02 15:32:27 -04:00
Michael A. Cojocari de778ff09d Working on fantastical square. 2026-08-29 14:42:49 -04:00
Christopher Van Hoose a19bb42c4b Record the SPIFFS repair catch-22 that strands the identity on SD
An invalid SPIFFS forces want_full_sd above ("move the WHOLE store to SD when
the device has no usable SPIFFS"), and being on SD then SKIPS the repair -
so the partition stays unformatted forever, every boot logs
"E SPIFFS: mount failed, -10025", and internal storage reads 0 bytes.

It is not cosmetic. The migration path documents that it "never deletes its
SPIFFS sources, so a device that already adopted the card still has a
byte-identical copy here" - the design assumes internal flash holds a SECOND
copy of the identity. A device that went SD-native BECAUSE SPIFFS was broken
never had that copy, so /meshcomod/identity/_main.id on the card is the only
thing making it its node. On the ThinkNode M9 that card is soldered.

Reproduced on M9 hardware, and the obvious one-line fix is NOT applied:
changing the condition does repair the partition, but that boot then panicked
and looped in the Arduino Wi-Fi event task. Once the partition was formatted,
the same build with the line back as-is boots cleanly and repeatedly - so the
fault is the 3.375 MB format itself running at this point in setup(), racing
the event task, not a mounted SPIFFS. The real fix has to repair the
partition somewhere that can afford a multi-second blocking format.

Comment only; no behaviour change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Christopher Van Hoose <cvhvisuals@icloud.com>
(cherry picked from commit ab86caa9385b6d63acce977d63e2b1a1731b7236)
2026-08-28 20:36:00 -04:00
Michael A. Cojocari ed29da087a Work 2026-08-27 13:53:55 -04:00
Kaj SchittecatandClaude Opus 5 b15d422bdc touch: beta_68 — Lua SDK gains the map, lists, packet delivery and discovery
The four capabilities that kept third-party apps a sketch of a built-in one:

* wada.map      the firmware's own tiles, projection and cache inside an app
                page, with its own capped pool so it never evicts the Map tab's
* wada.ui.list  the missing "pick one of N" widget; rows are real buttons, so
                keyboard and trackball nav walk them for free
* on_packet     each frame delivered once instead of polling a 16-deep ring,
                which sampled rather than observed
* wada.mesh.discover  the active zero-hop probe, behind its own permission
                because it spends every neighbour's airtime, not just ours

Plus the surface those need to be useful: packet identity reported only where
the frame actually carries it, exact micro-degree coordinates (Lua is built
LUA_32BITS, so its floats were quietly costing a metre), altitude and satellite
time, wada.geo, wada.ui.input, named and one-shot timers, http_post, windowed
fs.read, and wada.sys.env on a hardware gate rather than the memory one.

Fixes:
* Both ESP32-P4 targets could not link. g_wifi_last_disc_reason was defined in
  src/main.cpp, which the IDF builds never compile, so all nine S3 envs stayed
  green while Tanmatsu and T-Display P4 were dead.
* Map zoom level was invisible in +/- buttons mode; the readout was hidden with
  the slider it was anchored to.
* Hungarian and Dutch held each other's "No SD card" translation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 21:50:01 +02:00
Christopher Van HooseandClaude Opus 5 7060a38dba Fix V4-R8 crash on Wi-Fi association with BLE on; widen companion ATT MTU
Wi-Fi power save: remove the preference, always use DTIM modem sleep.

An R8 with saved credentials and Bluetooth enabled aborted every time Wi-Fi
associated. Coredump: crashing task "wifi", abort() inside pm_set_sleep_type,
with nimble_host and btController both live.

WIFI_PS_NONE is not a legal sleep mode while the BT controller is running --
modem sleep is precisely what yields airtime to BLE -- and the Wi-Fi PM blob
aborts rather than refusing. 844d8c3 made power save a preference defaulting
OFF on the V4-R8, which is why only that board crashed; the comment above the
apply site already stated the invariant it broke ("gives BLE coexistence
airtime").

The latency it was buying does not exist either. Profiling the companion link
on hardware: an app sync is ~40 CMD_GET_CHANNEL round trips (the device reports
MAX_GROUP_CHANNELS=40 and the app reads every slot individually), 165ms each,
with this firmware accounting for 28ms of 8264ms -- 0.3%. Sync cost is
round-trip COUNT, not link latency, so there was nothing to trade away.

Guarding the setting was considered and rejected: it would have to be silently
inert whenever Bluetooth is on, which is a confusing thing to leave in a
Settings screen. Removed instead -- toggle, prefs accessors and R8 default.
The stored wifi_ps NVS key is simply never read again; no migration needed.

Companion BLE: ATT MTU MAX_FRAME_SIZE -> MAX_FRAME_SIZE + 3.

A notification payload is ATT_MTU-3, so SerialBLEInterface::begin()'s
setMTU(MAX_FRAME_SIZE) left a full-length frame 3 bytes too big and NimBLE
silently dropped the overflow (ble_att_truncate_to_mtu). MyMesh::queueMessage()
clamps message text to exactly MAX_FRAME_SIZE, so maximum-length messages lost
their last 3 bytes -- over BLE only; USB/TCP/WS were unaffected. Applied from
the vendored transport rather than the core: setMTU() is a public static, so
MeshCore needs no change. Verified on an M9, which now negotiates 179.

All 8 environments build. R8 flashed and confirmed to be the booting slot
(otadata seq 1 -> ota_0); its boot log is not observable over USB because
opening the port drops that board into ROM download mode.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 00:53:41 -04:00
Christopher Van HooseandClaude Fable 5 844d8c3357 V4-R8 perf pass + M9 UI polish (prefs v49/v50)
Heltec V4-R8 — "are we getting full performance?" audit. Compute side was already
at spec; six gaps closed (record: variants/heltec_v4/R8_AUDIT.md "Perf pass"):
- FEM LNA defaulted OFF on a KCT8103L board: prefs v49 defaults it ON on the R8
  (one-time flip of existing installs, no new field). Toggle stays in Radio & Mesh.
- Display SPI 40 -> 80 MHz (LGFX_SPI_WRITE_HZ; -D to 40000000 steps back).
- Async DMA band flush (LGFXDisplay::flushBandRGB565): swap into a 12 KB internal
  DMA buffer, one no-convert DMA per band, lv_disp_flush_ready at once so LVGL
  renders band N+1 while band N drains; lv_disp_flush_is_last closes the frame
  transaction so the shared micro-SD gets the bus between frames. Sync fallback.
- Wi-Fi modem power save is a pref (wifi_ps; toggle in Wi-Fi settings, live once
  associated). Default ON everywhere (unchanged), OFF on the R8.
- micro-SD 4 -> 20 MHz after the proven mount, read-verified (probe-file head
  captured at 4 MHz and byte-compared after the raise; falls back). Wired at all
  three mount sites; no-op without SD_SPI_FAST_HZ. Note: SDFS::begin returns true
  if already mounted, so every clock change goes through SD.end() first.
- R8 env boots at ESP32_CPU_FREQ=240 (setup() no longer runs at the V4's 80 MHz).
- Settings -> About "Perf:" row on the R8 (no serial console on that board):
  "CPU 240 · TFT 80 MHz DMA · SD 20 MHz · LNA on" is the all-green reading.

ThinkNode M9 UI:
- Spectrum page root gets NAV_SKIP_FLAG: nothing on it is actionable, so the
  d-pad no longer highlights the chart / scale box; Back closes it as before.
- Map "Show tile z/x/y" line defaults OFF (prefs v50, one-time flip; toggle kept).
- The tab bar is bypassed entirely on the M9: the accent "glow" indicator lived on
  the screen, not in the bar, so hiding the zero-height bar left it glowing over
  the content's bottom edge. No bar styling / gestures / key hints / indicator
  are built on the M9 now.

Cross-compiled: R8, V4, M9, T-Deck. Flashed + booted on the user's R8 and M9.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 13:51:11 -04:00
Christopher Van HooseandClaude Fable 5 68e3b45932 M9 + V4-R8: 2026-08-19 audit passes
ThinkNode M9 (see M9_PORT.md audit section): restore nav-mode HW Back,
Lua-app key forwarding + d-pad support, popup-registry gates widened,
SD-backed data store + chat history, ADC2 battery sample filter,
keyboard poll throttle + backlight, map tile cache to SD, spectrum
sweep LR1110 fix, power-menu cleanup for buttonless hardware.

Heltec V4 R8 (see R8_AUDIT.md): chat history/battery log follow SD
adoption, reinsert watch no longer unmounts live store, tile cache
prefers SD, CAP_ROTATABLE reboot trap defused, SD CS parked at boot.

Shared: atomic threads-index write (tmp+rename), recursive
scroll-into-view during keypad nav, textarea focus highlight.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 07:25:18 -04:00
Kaj SchittecatandClaude Opus 5 fee132425c fix: recover from "LoRa radio not detected" on the first boot after an update
Reported on a T-Deck straight after an OTA: the boot dead-ended on the fatal
"LoRa radio not detected" screen, and a manual reboot cleared it.

On boards with a peripheral power gate the LoRa module has no power until that
pin is driven high in Board::begin() — GPIO10 on the T-Deck — a few milliseconds
before the radio is probed. A cold start survives that because the rail was
already settled. A SOFTWARE reset does not, and an OTA ends in exactly one:
ESP.restart() releases the pin, the rail collapses, the app re-drives it and
probes the SX1262 while it is still powering up. The existing 150 ms retry does
not help because it never touches the rail — the module is latched in a bad
state, not merely slow, which is why only a real power cycle cleared it.

So do what the error screen would otherwise ask the user to do: bounce the rail
(LOW, drain, HIGH, wait out the power-on reset and crystal startup) and probe
once more. Guarded on PIN_PERF_POWERON, so boards without a gate are untouched,
and only reached when the alternative is halting on the fatal screen — a board
that probes normally never executes any of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 18:46:28 +02:00
Kaj SchittecatandClaude Fable 5 e74cdcb3fe touch: pre-LVGL screens draw explicit colors (fix white boot-logo border on 1.17)
startFrame()'s default background became UIColor::window_bkg in the 1.17
core, and boards whose DISPLAY_CLASS is a core driver link upstream's
palette — window_bkg=WHITE, primary_txt=BLACK. The boot splash grew a
white frame around the logo, and the radio-halt text would have drawn
black-on-black. Boot splash, halt screen, REMOTE placeholder and the
shutdown screen now pass explicit black/white/red instead of palette
entries whose values depend on which display driver got linked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 17:51:26 +02:00
Kaj SchittecatandClaude Fable 5 e3c5c4237b core-117-prep: 1.17 core compile fixes across all targets (TEST PIN — not for main)
- UIColor migration: DisplayDriver::Color enum -> ColorVal/UIColor statics in the
  6 driver headers + 3 driver .cpps + all main.cpp/UITask.cpp call sites.
- ONE palette definition: src/helpers/ui/UIColorPalette.cpp. Envs whose
  DISPLAY_CLASS is a core driver (v4_tft, attaky, tdeck, M9) take the palette
  the core driver .o carries; envs on our drivers (r8, rak via src_filter;
  pagers via helpers/ui glob; both IDF apps via the whole-archive src glob)
  compile ours. Never both — same-name core objects collide (core LGFXDisplay).
- 1.17 ESP32Board.cpp includes <target.h>: hand the core lib scope the libdeps
  include paths it can't chain to (MicroNMEA everywhere, SensorLib on pagers).
- CustomLR1121Wrapper: setRxBoostedGainMode void->bool (1.17 base).
- IDF: fetch-deps.sh gains durable vendor-time patches (ESP32Board minimal
  deep-sleep — no target.h on IDF; SerialBLEInterface esp-nimble-cpp 2.x port
  incl. esp_mac.h). helpers/ethernet/ excluded in both meshcore CMakeLists.
- platformio.ini pins point at a LOCAL test tag (mc117-clean#core-v1.17.0-test);
  swap to the real core-v1.17.0 before merging to main.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 16:13:09 +02:00
Kaj SchittecatandClaude Fable 5 e32dc83553 touch: visible on-screen error when the LoRa radio fails init (#244)
A dead or absent LoRa module halted boot behind the frozen logo with the only
clue on serial. Retry init once (transient SPI/reset flakes), then paint the
failure on the panel — 'LoRa radio not detected / wadamesh needs the LoRa
module' — before halting, so users aren't left staring at the wordmark.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 14:48:00 +02:00
Kaj ec2f941851 Merge pull request #210 from PixPMusic/pixpmusic/tpager-wifi-ble-coexistence
fix(tpager): stabilize Wi-Fi and BLE coexistence
2026-08-09 14:31:00 +02:00
Pixel Perfect 8cceda408a fix(tpager): preserve BLE bonds across Wi-Fi handoff 2026-08-05 17:56:11 -07:00
Pixel Perfect 0175cebcd8 fix(tpager): enable Wi-Fi without reboot 2026-08-05 17:56:11 -07:00
Pixel Perfect 3f222ceecd fix(tpager): close Wi-Fi BLE handoff bypasses
Signed-off-by: Pixel Perfect <me@pixp.cc>
2026-08-05 17:56:11 -07:00
Pixel Perfect 0fdd1d5374 fix(tpager): fall back to BLE when Wi-Fi is unavailable
Track Pager Wi-Fi/BLE ownership explicitly instead of treating Wi-Fi intent as an active association. Allow BLE with Wi-Fi off or unconfigured, and restore it after a bounded failed association while pausing automatic retry churn until Wi-Fi is explicitly retried.

Signed-off-by: Pixel Perfect <me@pixp.cc>
2026-08-05 17:56:10 -07:00
Pixel Perfect 8c1b611be1 fix(tpager): leave a pending marker when the boot BLE guard refuses
Swapping beginBle() for enableBle() at boot introduced a refusal branch
that only logs. The association-timeout branch beside it sets
s_pager_ble_after_wifi, so the two exits left different state for the
retry sites to read. The GOT_IP handler's ble_waiting also ORs
(wifiConfigGetBleEnabled() && !isBleStackBegun()) and so happened to
cover the want_wifi case, but relying on that made the marker's meaning
inconsistent.

Set it in both branches. The residual gap is unchanged and inherent: with
want_wifi false there is no association to retry after, so a BLE-only
boot that loses the heap guard stays deferred until the user toggles
Bluetooth, which takes UITask::enableBle()'s ordered-restart path.

Signed-off-by: Pixel Perfect <me@pixp.cc>
2026-08-05 17:56:10 -07:00
Pixel Perfect c84d6a43dd fix(tpager): keep deferred BLE state coherent
Signed-off-by: Pixel Perfect <me@pixp.cc>
2026-08-05 17:56:10 -07:00
Pixel Perfect a620f38e04 fix(tpager): recover WiFi without rebooting
Signed-off-by: Pixel Perfect <me@pixp.cc>
2026-08-05 17:56:10 -07:00
Pixel Perfect 333081d2cf fix(tpager): resume deferred BLE requests
Signed-off-by: Pixel Perfect <me@pixp.cc>
2026-08-05 17:56:10 -07:00
Pixel Perfect 4ededd97f7 fix(tpager): keep Wi-Fi-only applies live
Signed-off-by: Pixel Perfect <me@pixp.cc>
2026-08-05 17:56:10 -07:00
Pixel Perfect 37e2706018 fix(tpager): handle deferred radio recovery safely
Signed-off-by: Pixel Perfect <me@pixp.cc>
2026-08-05 17:56:10 -07:00
Pixel Perfect 1a26b85d87 fix(tpager): preserve Wi-Fi-first reconnect ordering
Signed-off-by: Pixel Perfect <me@pixp.cc>
2026-08-05 17:56:10 -07:00