From a8d65aa3baf91075093f4c9543cc3fe1201e8b9a Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Thu, 23 Apr 2026 18:23:34 +1000 Subject: [PATCH 1/7] Update README.md Fixed links for discord and Meshcore flasher --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b97471ab..370ddd00 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ On macOS the port is typically `/dev/cu.usbmodem*`. On Windows it will be a COM **Using the MeshCore Flasher (web-based, T-Deck Pro only):** -1. Go to https://flasher.meshcore.co.uk +1. Go to https://flasher.meshcore.io 2. Select **Custom Firmware** 3. Select the **merged** `.bin` file you downloaded 4. Click **Flash**, select your device in the popup, and click **Connect** @@ -1118,7 +1118,7 @@ There are a number of fairly major features in the pipeline, with no particular ## πŸ“ž Get Support -- Join [MeshCore Discord](https://discord.gg/BMwCtwHj5V) to chat with the developers and get help from the community. +- Join [MeshCore Discord](https://discord.gg/KWFeY45sN) to chat with the developers and get help from the community. ## πŸ“œ License From 964606a01807775d9bdad52acf1627c55cb77523 Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Thu, 23 Apr 2026 18:29:00 +1000 Subject: [PATCH 2/7] Update README.md Updated discord channel link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 370ddd00..7b13c20a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A fork created specifically to focus on enabling BLE & WiFi companion firmware for the LilyGo T-Deck Pro & LilyGo T5 E-Paper S3 Pro. Created wholly with Claude AI using Meshcore v1.11 code. 100% vibecoded. -[Check out the Meck discussion channel on the MeshCore Discord](https://discord.com/channels/1343693475589263471/1460136499390447670) +[Check out the Meck discussion channel on the MeshCore Discord](https://discord.com/channels/1495203904898728149/1496789639556501614) IMG_1453 From c2bfc3c98593d4cc238ca4f25c781bb05b80bbba Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Sun, 3 May 2026 20:24:20 +1000 Subject: [PATCH 3/7] Add true power-off via BQ25896 BATFET disconnect 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) --- examples/companion_radio/main.cpp | 9 +- examples/companion_radio/ui-new/UITask.cpp | 168 +++++++++++++++------ examples/companion_radio/ui-new/UITask.h | 4 + 3 files changed, 137 insertions(+), 44 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index d8bd365b..4916b2e4 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -3269,7 +3269,13 @@ void loop() { case 'e': ui_task.gotoTextReader(); break; case 'n': ui_task.gotoNotesScreen(); break; #endif - case 's': ui_task.gotoSettingsScreen(); break; + case 's': + if (ui_task.isHomeOnShutdownPage()) { + ui_task.injectKey(ckb); + } else { + ui_task.gotoSettingsScreen(); + } + break; case 'f': ui_task.gotoDiscoveryScreen(); break; case 'h': ui_task.gotoLastHeardScreen(); break; case (char)0xF3: ui_task.injectKey(KEY_LEFT); break; // Left arrow β†’ prev page @@ -4599,6 +4605,7 @@ void handleKeyboardInput() { #ifdef MECK_AUDIO_VARIANT || ui_task.isOnAlarmScreen() #endif + || ui_task.isHomeOnShutdownPage() ) { ui_task.injectKey('s'); // Pass directly for scrolling } else { diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index f609a602..6d3e46c5 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -46,13 +46,7 @@ #endif #endif -#if UI_HAS_JOYSTICK - #define PRESS_LABEL "press Enter" -#elif defined(LilyGo_T5S3_EPaper_Pro) - #define PRESS_LABEL "long press" -#else - #define PRESS_LABEL "long press" -#endif +#define PRESS_LABEL "long press" #include "icons.h" #include "ChannelScreen.h" @@ -147,6 +141,9 @@ class HomeScreen : public UIScreen { uint8_t _page; bool _shutdown_init; unsigned long _shutdown_at; // earliest time to proceed with shutdown (after e-ink refresh) + bool _poweroff_selected; // true = "power off" highlighted, false = "hibernate" + bool _poweroff_confirm; // true = showing confirmation prompt for power off + bool _poweroff_msg_shown; // true = "powering off..." already displayed once bool _editing_utc; int8_t _saved_utc_offset; // for cancel/undo @@ -310,10 +307,12 @@ void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts, public: HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs) : _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0), - _shutdown_init(false), _shutdown_at(0), _editing_utc(false), _saved_utc_offset(0), sensors_lpp(200) { } + _shutdown_init(false), _shutdown_at(0), _poweroff_selected(false), _poweroff_confirm(false), + _poweroff_msg_shown(false), _editing_utc(false), _saved_utc_offset(0), sensors_lpp(200) { } bool isEditingUTC() const { return _editing_utc; } bool isOnRecentPage() const { return _page == HomePage::RECENT; } + bool isOnShutdownPage() const { return _page == HomePage::SHUTDOWN; } void cancelEditing() { if (_editing_utc) { _node_prefs->utc_offset_hours = _saved_utc_offset; @@ -323,6 +322,7 @@ public: void poll() override { if (_shutdown_init && millis() >= _shutdown_at && !_task->isButtonPressed()) { + if (_poweroff_selected) _task->setFullPowerOff(true); _task->shutdown(); } } @@ -332,6 +332,27 @@ public: #if defined(LilyGo_T5S3_EPaper_Pro) _task->setHomeShowingTiles(false); // Reset β€” only set true on FIRST page #endif + + // Power off: full-screen message, no header + // First render: "powering off..." + wake instruction + // Second render onward: wake instruction only (persists on e-ink) + if (_shutdown_init && _poweroff_selected) { +#if defined(LilyGo_T5S3_EPaper_Pro) + board.setBacklight(false); +#endif + display.setColor(DisplayDriver::GREEN); + display.setTextSize(1); + if (!_poweroff_msg_shown) { + _poweroff_msg_shown = true; + display.drawTextCentered(display.width() / 2, 30, "powering off..."); + display.drawTextCentered(display.width() / 2, 46, "plug in USB-C to turn on"); + return 1500; + } else { + display.drawTextCentered(display.width() / 2, 38, "plug in USB-C to turn on"); + return 5000; + } + } + // node name (tinyfont to avoid overlapping clock) display.setTextSize(_node_prefs->smallTextSize()); display.setColor(DisplayDriver::GREEN); @@ -1000,21 +1021,43 @@ public: display.setTextSize(1); if (_shutdown_init) { #if defined(LilyGo_T5S3_EPaper_Pro) - board.setBacklight(false); // Turn off backlight on hibernate + board.setBacklight(false); #endif display.drawTextCentered(display.width() / 2, 34, "hibernating..."); - } else { + } else if (_poweroff_confirm) { + // Confirmation prompt for power off #if defined(LilyGo_T5S3_EPaper_Pro) display.drawXbm((display.width() - 32) / 2, 28, power_icon, 32, 32); #else - display.drawXbm((display.width() - 32) / 2, 18, power_icon, 32, 32); + display.drawXbm((display.width() - 32) / 2, 10, power_icon, 32, 32); #endif #if defined(LilyGo_T5S3_EPaper_Pro) - display.drawTextCentered(display.width() / 2, 64, "hibernate:" PRESS_LABEL); + display.drawTextCentered(display.width() / 2, 64, "power off device?"); + display.drawTextCentered(display.width() / 2, 76, "usb-c to wake"); #else - display.drawTextCentered(display.width() / 2, 57, "hibernate: " PRESS_LABEL); - display.drawTextCentered(display.width() / 2, 67, "or press Enter key"); + display.drawTextCentered(display.width() / 2, 50, "power off device?"); + display.drawTextCentered(display.width() / 2, 60, "usb-c to wake"); + display.drawTextCentered(display.width() / 2, 76, "Enter:yes q:no"); #endif + } else { + // Menu: hibernate / power off +#if defined(LilyGo_T5S3_EPaper_Pro) + display.drawXbm((display.width() - 32) / 2, 20, power_icon, 32, 32); + const int y1 = 58, y2 = 70; +#else + display.drawXbm((display.width() - 32) / 2, 10, power_icon, 32, 32); + const int y1 = 50, y2 = 62; +#endif + char line1[48], line2[48]; +#if defined(LilyGo_TDeck_Pro) + snprintf(line1, sizeof(line1), "%shibernate: long press/Enter", _poweroff_selected ? " " : ">"); + snprintf(line2, sizeof(line2), "%spower off: long press/Enter", _poweroff_selected ? ">" : " "); +#else + snprintf(line1, sizeof(line1), "%shibernate: " PRESS_LABEL, _poweroff_selected ? " " : ">"); + snprintf(line2, sizeof(line2), "%spower off: " PRESS_LABEL, _poweroff_selected ? ">" : " "); +#endif + display.drawTextCentered(display.width() / 2, y1, line1); + display.drawTextCentered(display.width() / 2, y2, line2); } } return _editing_utc ? 700 : 5000; @@ -1055,6 +1098,39 @@ public: return true; // Consume all other keys while editing } + // SHUTDOWN page -- intercept up/down and Enter before page cycling + if (_page == HomePage::SHUTDOWN) { + if (_poweroff_confirm) { + // Confirmation mode for power off + if (c == KEY_ENTER) { + _shutdown_init = true; + _shutdown_at = millis() + 2500; // extra time for two-phase e-ink update + return true; + } + // Cancel: q, left, prev + if (c == 'q' || c == KEY_LEFT || c == KEY_PREV) { + _poweroff_confirm = false; + return true; + } + return true; // eat all other keys while confirming + } + // Up/down toggles between hibernate and power off + if (c == KEY_NEXT || c == 's' || c == KEY_PREV || c == 'w') { + _poweroff_selected = !_poweroff_selected; + return true; + } + if (c == KEY_ENTER) { + if (_poweroff_selected) { + _poweroff_confirm = true; + } else { + _shutdown_init = true; + _shutdown_at = millis() + 900; + } + return true; + } + // Left/right fall through to page cycling below + } + if (c == KEY_LEFT || c == KEY_PREV) { _page = (_page + HomePage::Count - 1) % HomePage::Count; return true; @@ -1103,11 +1179,6 @@ public: return true; } #endif - if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) { - _shutdown_init = true; - _shutdown_at = millis() + 900; // allow e-ink refresh (644ms) before shutdown - return true; - } return false; } }; @@ -1591,6 +1662,36 @@ void UITask::shutdown(bool restart){ // Power off LoRa radio, display, and board radio_driver.powerOff(); _display->turnOff(); + + // BQ25896 ship mode: disconnect battery from VSYS entirely. + // Must happen BEFORE _board->powerOff() cuts PIN_PERF_POWERON + // (I2C pull-ups need VDD3V3 to complete the transaction). + // TI recommends: set BATFET_DLY=1 first, then BATFET_DIS=1 as + // the last I2C write to avoid bricking the I2C state machine. + // After tSM_DLY (~10-15s) the BATFET opens during deep sleep. + // Wake: USB-C plug-in only (no reset button -- no power to ESP32). + #ifdef I2C_ADDR_BQ25896 + if (_full_poweroff) { + Wire.beginTransmission(I2C_ADDR_BQ25896); + Wire.write(0x09); + Wire.endTransmission(false); + Wire.requestFrom((uint8_t)I2C_ADDR_BQ25896, (uint8_t)1); + uint8_t reg09 = Wire.read(); + + // Step 1: set BATFET_DLY=1 (bit 3) for safe I2C completion + Wire.beginTransmission(I2C_ADDR_BQ25896); + Wire.write(0x09); + Wire.write(reg09 | 0x08); // BATFET_DLY = bit 3 + Wire.endTransmission(); + + // Step 2: set BATFET_DIS=1 (bit 5) -- MUST be the last I2C write + Wire.beginTransmission(I2C_ADDR_BQ25896); + Wire.write(0x09); + Wire.write(reg09 | 0x28); // BATFET_DIS (0x20) | BATFET_DLY (0x08) + Wire.endTransmission(); + } + #endif + _board->powerOff(); } } @@ -1605,30 +1706,7 @@ bool UITask::isButtonPressed() const { void UITask::loop() { char c = 0; -#if UI_HAS_JOYSTICK - int ev = user_btn.check(); - if (ev == BUTTON_EVENT_CLICK) { - c = checkDisplayOn(KEY_ENTER); - } else if (ev == BUTTON_EVENT_LONG_PRESS) { - c = handleLongPress(KEY_ENTER); // REVISIT: could be mapped to different key code - } - ev = joystick_left.check(); - if (ev == BUTTON_EVENT_CLICK) { - c = checkDisplayOn(KEY_LEFT); - } else if (ev == BUTTON_EVENT_LONG_PRESS) { - c = handleLongPress(KEY_LEFT); - } - ev = joystick_right.check(); - if (ev == BUTTON_EVENT_CLICK) { - c = checkDisplayOn(KEY_RIGHT); - } else if (ev == BUTTON_EVENT_LONG_PRESS) { - c = handleLongPress(KEY_RIGHT); - } - ev = back_btn.check(); - if (ev == BUTTON_EVENT_TRIPLE_CLICK) { - c = handleTripleClick(KEY_SELECT); - } -#elif defined(PIN_USER_BTN) +#if defined(PIN_USER_BTN) int ev = user_btn.check(); if (ev == BUTTON_EVENT_CLICK) { #if defined(LilyGo_T5S3_EPaper_Pro) @@ -2586,6 +2664,10 @@ bool UITask::isHomeOnRecentPage() const { return curr == home && ((HomeScreen *) home)->isOnRecentPage(); } +bool UITask::isHomeOnShutdownPage() const { + return curr == home && ((HomeScreen *) home)->isOnShutdownPage(); +} + void UITask::gotoChannelScreen(bool resetDmView) { ChannelScreen* cs = (ChannelScreen*)channel_screen; // If currently showing DM view, reset to channel 0 (unless caller opts out) diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index ea240436..8d40a822 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -66,6 +66,7 @@ class UITask : public AbstractUITask { int _msgcount; unsigned long ui_started_at, next_batt_chck; uint8_t _low_batt_count = 0; // Consecutive low-voltage readings for debounce + bool _full_poweroff = false; // True = BQ25896 BATFET disconnect (USB-C wake only) int next_backlight_btn_check = 0; #ifdef PIN_STATUS_LED int led_state = 0; @@ -215,6 +216,8 @@ public: void showBootHint(bool immediate = false); // Show navigation hint overlay on first boot void dismissBootHint(); // Dismiss hint and save preference bool isHintActive() const { return _hintActive; } + // BQ25896 BATFET disconnect -- true power off, USB-C required to wake + void setFullPowerOff(bool v) { _full_poweroff = v; } // Wake display and extend auto-off timer. Call this when handling keys // outside of injectKey() to prevent display auto-off during direct input. void keepAlive() { @@ -291,6 +294,7 @@ public: bool isEditingHomeScreen() const; // Check if home screen is showing the Recent Adverts page bool isHomeOnRecentPage() const; + bool isHomeOnShutdownPage() const; // Inject a key press from external source (e.g., keyboard) void injectKey(char c); From 4ba130ccfa68ef9aa41c9ac89fbbf2e28eb40a1e Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Sun, 3 May 2026 21:22:07 +1000 Subject: [PATCH 4/7] update readme for v1.8; added fix: robust SPIFFS recovery when auto-format fails 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 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. --- README.md | 24 ++++++++++++++++++++---- examples/companion_radio/main.cpp | 27 +++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7b13c20a..35d01d75 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ A fork created specifically to focus on enabling BLE & WiFi companion firmware f - [Alarm Clock (Audio only)](#alarm-clock-audio-only) - [Voice Notes Over LoRa (Audio only)](#voice-notes-over-lora-audio-only) - [Lock Screen (T-Deck Pro)](#lock-screen-t-deck-pro) + - [Shutdown (T-Deck Pro)](#shutdown-t-deck-pro) - [Remote Repeater (T-Deck Pro 4G)](#remote-repeater-t-deck-pro-4g) - [WiFi Repeater](#wifi-repeater) - [T5S3 E-Paper Pro](#t5s3-e-paper-pro) @@ -140,6 +141,8 @@ esptool.py --chip esp32s3 --port /dev/ttyACM0 --baud 921600 \ > **Tip:** If you're unsure whether the device already has a bootloader, it's always safe to use the merged file and flash at `0x0` β€” it will overwrite everything cleanly. +> **First boot:** On a fresh flash, the device will format its internal storage partition. The display shows "Formatting storage... First boot - please wait" β€” this takes 1-2 minutes and only happens once. If the device was previously running different firmware (e.g. stock LilyGo or Meshtastic), the partition is automatically erased and reformatted to ensure a clean start. + ### Launcher If you're loading firmware from an SD card via the LilyGo Launcher firmware, use the **non-merged** `.bin` file. The Launcher provides its own bootloader and only needs the application image. @@ -343,6 +346,7 @@ Flood-based hop estimates (`~D`, `~N`) are drawn from a cache of up to 1,000 rec | Key | Action | |-----|--------| | W / S | Scroll up / down through contacts | +| Shift+W / Shift+S | Page up / page down | | A / D | Cycle filter: All β†’ Chat β†’ Rptr β†’ Room β†’ Sens β†’ Fav | | Enter | Enter select mode (highlights current contact, enables batch operations) | | P | Open path editor for the highlighted contact | @@ -433,6 +437,7 @@ Press **S** from the home screen to open settings. On first boot (when the devic | Key | Action | |-----|--------| | W / S | Navigate up / down through settings | +| Shift+W / Shift+S | Page up / page down | | Enter | Edit selected setting, or enter a sub-screen | | Q | Back one level (sub-screen β†’ top level β†’ home screen) | @@ -491,6 +496,8 @@ Change the font in **Settings β†’ Font** β€” use A/D to cycle with a live previe Font styles are available in both Tiny and Larger text size modes. Custom fonts at Tiny size use 7pt glyphs; at Larger size, 9pt β€” matching the existing FreeSans layout. The font preference is saved and persists across reboots. +**Accented character support (v1.8+):** All three font styles now display accented and diacritical characters (Czech, Polish, French, German, etc.) instead of dropping them. **Noto Sans at Larger text size** renders diacritical marks natively (carons, accents, cedillas). All other font and size combinations fold accented characters to their ASCII base letter (Δ›β†’e, ΕΎβ†’z, Γ±β†’n) β€” the letter is always visible, just without the diacritic mark. + ### Compose Mode | Key | Action | @@ -527,7 +534,7 @@ Press the **Sym** key then the letter key to enter numbers and symbols: ### Emoji Picker -While in compose mode, press the **$** key to open the emoji picker. A scrollable grid of 76 emoji is displayed in a 5-column layout, with faces and emotions grouped first. Scrolling wraps around β€” pressing W on the first row goes to the last row and vice versa. +While in compose mode, press the **$** key to open the emoji picker. A scrollable grid of 77 emoji is displayed in a 5-column layout, with faces and emotions grouped first. Scrolling wraps around β€” pressing W on the first row goes to the last row and vice versa. | Key | Action | |-----|--------| @@ -641,6 +648,10 @@ Double-click the Boot button again to unlock and return to whatever screen you w An auto-lock timer can be configured in **Settings β†’ Auto Lock** (None / 2 / 5 / 10 / 15 / 30 minutes of idle time). +### Shutdown (T-Deck Pro) + +The home screen includes a **Shutdown** page. Selecting it powers the device off completely β€” the ESP32-S3 enters deep sleep with no wake sources, peripheral power is cut, and the LoRa module is powered down. Only a hardware reset (reset button) or USB power-on will wake the device. This is distinct from the auto-lock hibernate, which maintains wake-on-LoRa capability. + --- ## Remote Repeater (T-Deck Pro 4G) @@ -762,7 +773,7 @@ The virtual keyboard supports: - QWERTY letter layout with a symbol/number layer (tap the **123** key to switch) - Shift toggle for uppercase - Backspace (UTF-8 aware β€” correctly deletes multi-byte emoji) and Enter keys -- **Emoji picker** β€” tap the **$** key to open a scrollable 8-column grid of 76 emoji sprites with page indicators. Tap an emoji to insert it inline in your message. Tap **Back** to return to the keyboard. Faces and emotions are grouped first for quick access. +- **Emoji picker** β€” tap the **$** key to open a scrollable 8-column grid of 77 emoji sprites with page indicators. Tap an emoji to insert it inline in your message. Tap **Back** to return to the keyboard. Faces and emotions are grouped first for quick access. - Inline emoji rendering β€” emoji appear as pixel sprites in the text field as you type - Phantom keystroke prevention (a brief cooldown after the keyboard opens prevents accidental taps) @@ -1021,7 +1032,7 @@ The companion firmware can be connected to via BLE (T-Deck Pro and T5S3 BLE vari ## πŸ›  Hardware Compatibility -MeshCore is designed for devices listed in the [MeshCore Flasher](https://flasher.meshcore.co.uk). Meck specifically targets the LilyGo T-Deck Pro, LilyGo T5S3 E-Paper Pro, Heltec V3 (remote repeater only), and Heltec V4 (remote repeater only). +MeshCore is designed for devices listed in the [MeshCore Flasher](https://flasher.meshcore.io). Meck specifically targets the LilyGo T-Deck Pro, LilyGo T5S3 E-Paper Pro, Heltec V3 (remote repeater only), and Heltec V4 (remote repeater only). ## Contributing @@ -1068,8 +1079,12 @@ There are a number of fairly major features in the pipeline, with no particular - [X] Channel picker screen with unread badges - [X] Region scope (MeshCore v1.15+ compatibility) - [X] Selectable font styles (Classic, Noto Sans, Montserrat) -- [X] Expanded emoji picker (76 emoji, reordered, wrap scrolling) +- [X] Expanded emoji picker (77 emoji, reordered, wrap scrolling) - [X] 1,000-entry advert path cache (PSRAM) +- [X] Accented character / diacritics support (Czech, Polish, French, German, Latin Extended) +- [X] Page scroll (Shift+W/S) on all list screens +- [X] True power off (deep sleep, no wake sources) +- [X] BLE 2M PHY, DLE, and faster write interval - [ ] Fix M4B rendering to enable chaptered audiobook playback - [ ] Better JPEG and PNG decoding - [ ] Improve EPUB rendering and EPUB format handling @@ -1103,6 +1118,7 @@ There are a number of fairly major features in the pipeline, with no particular - [X] Region scope (MeshCore v1.15+ compatibility) - [X] Selectable font styles (Classic, Noto Sans, Montserrat) - [X] Virtual keyboard emoji grid with scrollable pages +- [X] Accented character / diacritics support (Czech, Polish, French, German, Latin Extended) - [ ] Improve EPUB rendering and EPUB format handling **Heltec V4:** diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 4916b2e4..acf694db 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -5,6 +5,9 @@ #if defined(ESP32) && defined(MECK_OTA_UPDATE) #include #endif +#ifdef ESP32 + #include +#endif #include #include "MyMesh.h" #include "variant.h" // Board-specific defines (HAS_GPS, etc.) @@ -1870,7 +1873,7 @@ void setup() { #elif defined(ESP32) MESH_DEBUG_PRINTLN("setup() - ESP32 filesystem init - calling SPIFFS.begin()"); if (!SPIFFS.begin(false)) { - // First boot or corrupted partition β€” format required (can take 1-2 minutes) + // First boot or corrupted partition -- format required (can take 1-2 minutes) Serial.println("SPIFFS mount failed - formatting (this may take 1-2 minutes)..."); if (disp) { disp->startFrame(); @@ -1882,7 +1885,27 @@ void setup() { disp->endFrame(); } if (!SPIFFS.begin(true)) { - Serial.println("SPIFFS format FAILED!"); + // Auto-format failed -- partition likely contains non-SPIFFS data from + // a previous firmware. Erase the entire partition and retry. + Serial.println("SPIFFS auto-format failed -- erasing partition and retrying..."); + const esp_partition_t* spiffsPart = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, NULL); + if (spiffsPart) { + Serial.printf("SPIFFS partition: offset=0x%x size=0x%x -- erasing...\n", + spiffsPart->address, spiffsPart->size); + esp_partition_erase_range(spiffsPart, 0, spiffsPart->size); + } + bool spiffsMounted = false; + for (int attempt = 0; attempt < 3 && !spiffsMounted; attempt++) { + if (attempt > 0) { Serial.printf("SPIFFS retry %d/3...\n", attempt + 1); delay(500); } + SPIFFS.format(); + spiffsMounted = SPIFFS.begin(false); + } + if (spiffsMounted) { + Serial.println("SPIFFS mounted after explicit format"); + } else { + Serial.println("ERROR: SPIFFS mount failed after all retries"); + } } else { Serial.println("SPIFFS format complete"); } From 44353fcf9ea49447f5ff7faa1a80d94c1b4b4d2b Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Sun, 3 May 2026 23:15:32 +1000 Subject: [PATCH 5/7] hibernation page navigation bugfix --- examples/companion_radio/main.cpp | 6 +++++- examples/companion_radio/ui-new/UITask.cpp | 8 +++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index acf694db..14c44b46 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -1495,7 +1495,11 @@ static void lastHeardToggleContact() { if (horizontal) { return (dx < 0) ? (char)KEY_NEXT : (char)KEY_PREV; } - return (char)KEY_NEXT; // vertical swipe = next (default) + // Shutdown page: vertical swipe toggles hibernate/power off + if (ui_task.isHomeOnShutdownPage()) { + return 'w'; // toggle (direction doesn't matter) + } + return (char)KEY_NEXT; // vertical swipe = next page (default) } // Settings: horizontal swipe β†’ a/d for picker/number editing diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 6d3e46c5..3490fe34 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -1115,7 +1115,9 @@ public: return true; // eat all other keys while confirming } // Up/down toggles between hibernate and power off - if (c == KEY_NEXT || c == 's' || c == KEY_PREV || c == 'w') { + // Only 'w'/'s' (keyboard) β€” KEY_NEXT/KEY_PREV fall through to page cycling + // so touch swipes and taps can still navigate away from this page. + if (c == 'w' || c == 's') { _poweroff_selected = !_poweroff_selected; return true; } @@ -1131,11 +1133,11 @@ public: // Left/right fall through to page cycling below } - if (c == KEY_LEFT || c == KEY_PREV) { + if (c == KEY_LEFT || c == KEY_PREV || c == 'a') { _page = (_page + HomePage::Count - 1) % HomePage::Count; return true; } - if (c == KEY_NEXT || c == KEY_RIGHT) { + if (c == KEY_NEXT || c == KEY_RIGHT || c == 'd') { _page = (_page + 1) % HomePage::Count; if (_page == HomePage::RECENT) { _task->showAlert("Recent adverts", 800); From 468a5ad3249636aa18842ee49fd0ffeedd55ed4c Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Tue, 5 May 2026 11:39:38 +1000 Subject: [PATCH 6/7] update tdpro firmware versions --- variants/lilygo_tdeck_pro/platformio.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/variants/lilygo_tdeck_pro/platformio.ini b/variants/lilygo_tdeck_pro/platformio.ini index 83e46fe2..25fd1bb7 100644 --- a/variants/lilygo_tdeck_pro/platformio.ini +++ b/variants/lilygo_tdeck_pro/platformio.ini @@ -158,7 +158,7 @@ build_flags = -D MECK_AUDIO_VARIANT -D MECK_WEB_READER=1 -D MECK_OTA_UPDATE=1 - -D FIRMWARE_VERSION='"Meck v1.7.WiFi"' + -D FIRMWARE_VERSION='"Meck v1.8.WiFi"' build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter} + - @@ -225,7 +225,7 @@ build_flags = -D HAS_4G_MODEM=1 -D MECK_WEB_READER=1 -D MECK_OTA_UPDATE=1 - -D FIRMWARE_VERSION='"Meck v1.7.4G"' + -D FIRMWARE_VERSION='"Meck v1.8.4G"' build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter} + + @@ -261,7 +261,7 @@ build_flags = -D HAS_4G_MODEM=1 -D MECK_WEB_READER=1 -D MECK_OTA_UPDATE=1 - -D FIRMWARE_VERSION='"Meck v1.7.4G.WiFi"' + -D FIRMWARE_VERSION='"Meck v1.8.4G.WiFi"' build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter} + - @@ -295,7 +295,7 @@ build_flags = -D HAS_4G_MODEM=1 -D MECK_WEB_READER=1 -D MECK_OTA_UPDATE=1 - -D FIRMWARE_VERSION='"Meck v1.7.4G.SA"' + -D FIRMWARE_VERSION='"Meck v1.8.4G.SA"' build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter} + - From 7adc0283b282b79485dccd245defebb83feeff1f Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Tue, 5 May 2026 11:56:13 +1000 Subject: [PATCH 7/7] add sponsor link --- .github/FUNDING.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..5481eecd --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: pelgraine +