UITask shutdown (already occurs/unchanged):
1. BLE disabled (_serial->disable()).
2. WiFi disconnected + WIFI_OFF.
3. 4G modem shutdown().
4. GPS power cut (PIN_GPS_EN LOW).
5. LoRa radio powerOff() (standby mode).
6. Display turnOff().
TDeckBoard::powerOff() (new):
7. btStop() -- BLE controller stop.
8. Peripheral power OFF (PIN_PERF_POWERON LOW) -- keyboard, BQ27220, sensors.
9. LoRa module power OFF (P_LORA_EN LOW) -- cuts power entirely.
10. Hold LoRa NSS high (prevents SX1262 drawing current from floating CS).
11. esp_deep_sleep_start() -- CPU halts, ~10-40uA.
Wake (reset button or USB power-on):
12. ESP32-S3 cold boots.
13. TDeckBoard::begin() runs: peripheral power ON, I2C init, LoRa power ON, NSS hold released.
14. App starts fresh -- prefs/contacts/messages load from flash.
No LoRa wake during hibernate -- the device is truly off. Only a hardware
reset (reset button) or USB power-on wakes the device.
Incorporates hardware-specific learnings from caveman99's Meshtastic
T-Echo Card support PR (meshtastic/firmware#10267), cross-referenced
against LilyGo's official t_echo_card_config.h pinmap.
Battery (critical):
- Add BATTERY_MEASUREMENT_CONTROL pin P0.31 — gates the resistive
voltage divider feeding AIN0. Without toggling this pin, battery
ADC reads are invalid (floating divider input).
- TechoCardBoard::getBatteryVoltage() now drives P0.31 HIGH before
SAADC read and LOW after to avoid parasitic drain.
Display (critical):
- Add OLED_DISPLAY_OFFSET = 24 for the SSD1315 72×40 panel. The
physical display is mapped at GDDRAM pages 3–7 (rows 24–63);
SETDISPLAYOFFSET and SETMULTIPLEX commands are sent after
display.begin() in target.cpp to shift the visible window.
Power rail (high priority):
- RT9080 3V3 LDO now gets a clean HIGH→LOW→HIGH reset cycle with
100ms dwell in board.begin(), preventing brown-out when LoRa TX
fires at +22 dBm after a soft reset.
The markChannelReadFromBLE() calls in the CMD_SYNC_NEXT_MESSAGE handler were marking channels and DMs as read the moment the BLE companion app synced them from the offline queue. Since the app drains the entire queue automatically on connect, this had the effect of clearing all unread indicators on the device as soon as BLE connected — before the user had actually read anything in either the app or on the device.
The MeshCore BLE protocol has no "user opened this channel" command from the app side; CMD_SYNC_NEXT_MESSAGE is an automatic bulk pull, so "synced to app" ≠ "read by user." Removed the channel and DM mark-read calls so unread counts only clear when the user navigates to that channel on the device itself. The msgRead() progress counter (syncing X messages) is unaffected.
TDPro - Update firmware build date
Contactsscreen.h — five changes:
- EPOCH_2026 = 1735689600UL constant added (Jan 1 2026 UTC), used in sort
and formatAge.
- typeChar replaced by typeStr returning const char*, with "RS" for room
servers (previously "S", easily confused with sensors). prefix buffer
bumped to [5], all three snprintf calls updated to %s.
- Hop display: out_path_len == 0xFF branch now performs a live lookup
against the 12 most recently heard advert paths (via
getRecentlyHeard). Matches on first 7 bytes of pub_key, extracts hop
count with a bph-aware sanity cap (64/bph max) to reject impossible
values. Shows "~D" for direct flood neighbours, "~N" for N-hop flood
path, "?" if not in the recent-heard cache. Resets to "?" on reboot
until each contact re-advertises — intentional, ensures hop count is
always fresh.
- Sort: _filteredTs now stores contact.lastmod (our local receive time)
instead of contact.last_advert_timestamp (sender's claimed time).
lastmod values below EPOCH_2026 are stored as 0 so stale repeaters
with unsynced clocks and contacts received before our own timesync
sink to the bottom of the list.
- formatAge rewritten: rejects timestamp == 0, timestamp < EPOCH_2026,
and now < timestamp (all show "--" instead of wrapping or displaying
garbage). Arithmetic changed from int to uint32_t, eliminating the
signed overflow path that produced negative hour values. Age display
call site switched from last_advert_timestamp to lastmod, so display
self-corrects after a GPS or 4G timesync.