SPIFFS.begin(true) auto-formats on mount failure, but the auto-format
itself can fail if the partition contains residual data from a previous
firmware (e.g. stock LilyGo, Meshtastic, or MeshCore with a different
partition layout). When that happens the firmware previously printed
"SPIFFS format FAILED!" and continued in a broken state with no
persistence.
Now on auto-format failure:
1. Find the SPIFFS partition via esp_partition_find_first()
2. Erase it completely with esp_partition_erase_range()
3. Call SPIFFS.format() + SPIFFS.begin(false) with up to 3 retries
Added #include <esp_partition.h> under ESP32 guard.
The existing first-boot display feedback ("Formatting storage...
First boot - please wait") is unchanged -- it fires on the initial
mount failure. The new partition erase code only triggers if the
auto-format also fails.
Hibernate (deep sleep) leaves the BQ25896 charger IC powered, drawing
~30-60uA quiescent from BAT. This adds a second option on the shutdown
page -- "power off" -- that writes the BATFET_DIS bit in BQ25896 REG09
to fully disconnect the battery from VSYS. Leakage drops to ~12-23uA
(IC internal only). Wake requires USB-C plug-in.
Shutdown page now shows two options with a cursor (up/down to toggle):
> hibernate: long press/Enter (T-Deck Pro)
power off: long press/Enter
> hibernate: long press (T5S3 / other)
power off: long press
Selecting "hibernate" triggers immediately (unchanged behaviour).
Selecting "power off" shows a confirmation prompt:
power off device?
usb-c to wake
Enter:yes q:no
Power-off display suppresses the header (node name, clock, battery)
and shows only "powering off..." and "plug in USB-C to turn on".
The 's' key shortcut to settings is gated on the shutdown page so it
passes through to the hibernate/power-off selection toggle instead.
Both the TCA8418 handler (loop) and the broader handler
(handleKeyboardInput) are gated via isHomeOnShutdownPage().
PRESS_LABEL macro: removed dead UI_HAS_JOYSTICK branch (no Meck
device has a joystick), collapsed to a single #define "long press".
Joystick input polling block in loop() also removed (dead code behind
#if UI_HAS_JOYSTICK, never compiled for any Meck build).
BQ25896 I2C sequence follows TI recommendation (E2E forum, Jeff/TI):
1. Read REG09
2. Write BATFET_DLY=1 (bit 3) -- delays disconnect so I2C completes
3. Write BATFET_DIS=1 | BATFET_DLY=1 (bits 5+3) -- last I2C write
The write happens after display turnOff but before board powerOff, so
I2C pull-ups on VDD3V3 are still alive. Board enters deep sleep, then
BATFET opens after tSM_DLY (~10-15s). Skipping the delay risks leaving
the BQ25896 I2C engine in an undefined state that can prevent wake on
USB-C plug-in (device soft-brick requiring battery disconnect).
REG09 bit map (confirmed from Linux kernel bq25890_charger.c):
Bit 7: FORCE_ICO
Bit 6: TMR2X_EN
Bit 5: BATFET_DIS (0x20) -- disconnect battery
Bit 4: JEITA_VSET
Bit 3: BATFET_DLY (0x08) -- delay before disconnect
Bit 2: BATFET_RST_EN (0x04) -- QON wake (not wired on T-Deck Pro)
Bit 1: PUMPX_UP
Bit 0: PUMPX_DN
Schematic confirms QON (pin 12) has R4 10K pull-up to REGN with no
user-accessible button -- USB-C is the only wake path from ship mode.
Guarded by #ifdef I2C_ADDR_BQ25896 so it compiles on all platforms
but only activates on boards with the charger (T-Deck Pro, T5S3).
Files changed:
UITask.h -- _full_poweroff, setFullPowerOff(), isHomeOnShutdownPage()
UITask.cpp -- shutdown page UI, input handling, BATFET write,
PRESS_LABEL cleanup, joystick removal
main.cpp -- 's' key gated on shutdown page (both handlers)
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.
SerialBLEInterface.cpp — added esp_bt.h include and three esp_ble_tx_power_set calls at +9 dBm after BLEDevice::init(), covering default, advertising, and scan power types.
MyMesh.h — FIRMWARE_VER_CODE bumped from 10 → 11.
MyMesh.cpp — The RESP_CODE_DEVICE_INFO frame construction now:
Byte 2: sends 0xFF (sentinel) when MAX_CONTACTS > 510, otherwise the normal MAX_CONTACTS / 2. Older apps interpret 0xFF as 510 contacts — completely harmless.
Bytes 80-81 (new, appended after the version string): uint16_t little-endian with the true MAX_CONTACTS value. Apps that understand v11+ read it here. Apps < v11 ignore trailing bytes — the BLE/serial frame protocol is length-delimited, so extra bytes at the tail are safe.
platformio.ini — Both BLE builds (meck_audio_ble, meck_4g_ble) bumped from 510 → 2000.
mymesh.cpp: writeContactRespFrame return type change (return _serial->writeFrame() result)
checkSerialInterface() batch-fill loop.