diff --git a/examples/companion_radio/AbstractUITask.h b/examples/companion_radio/AbstractUITask.h index 95feef4e..d6d0b7a4 100644 --- a/examples/companion_radio/AbstractUITask.h +++ b/examples/companion_radio/AbstractUITask.h @@ -36,6 +36,7 @@ public: void setHasConnection(bool connected) { _connected = connected; } bool hasConnection() const { return _connected; } uint16_t getBattMilliVolts() const { return _board->getBattMilliVolts(); } + uint8_t getBatteryPercent() const { return _board->getBatteryPercent(); } bool isSerialEnabled() const { return _serial->isEnabled(); } void enableSerial() { _serial->enable(); } void disableSerial() { _serial->disable(); } diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 9c4d5512..743ea2a2 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -906,24 +906,26 @@ static CardKBKeyboard cardkb; static unsigned long lastCardKBProbe = 0; #define CARDKB_PROBE_INTERVAL_MS 5000 - - // CardKB compose mode state - static bool ckbComposeMode = false; - static char ckbComposeBuf[138]; // 137 bytes max + null - static int ckbComposePos = 0; - static uint8_t ckbComposeChIdx = 0; - static bool ckbComposeDM = false; - static int ckbComposeDMIdx = -1; - static char ckbComposeDMName[32]; - static unsigned long ckbLastKeystroke = 0; - static bool ckbComposeRefresh = false; - #define CKB_COMPOSE_DEBOUNCE 600 - - void drawCardKBCompose(); - void sendCardKBMessage(); #endif #endif +// CardKB compose mode state — standalone so ANY variant with MECK_CARDKB gets these +#ifdef MECK_CARDKB + static bool ckbComposeMode = false; + static char ckbComposeBuf[138]; // 137 bytes max + null + static int ckbComposePos = 0; + static uint8_t ckbComposeChIdx = 0; + static bool ckbComposeDM = false; + static int ckbComposeDMIdx = -1; + static char ckbComposeDMName[32]; + static unsigned long ckbLastKeystroke = 0; + static bool ckbComposeRefresh = false; + #define CKB_COMPOSE_DEBOUNCE 600 + + void drawCardKBCompose(); + void sendCardKBMessage(); +#endif + // Board-agnostic: CPU frequency scaling and AGC reset CPUPowerManager cpuPower; #define AGC_RESET_INTERVAL_MS 500 diff --git a/examples/companion_radio/ui-new/Settingsscreen.h b/examples/companion_radio/ui-new/Settingsscreen.h index 5b949491..b323d49e 100644 --- a/examples/companion_radio/ui-new/Settingsscreen.h +++ b/examples/companion_radio/ui-new/Settingsscreen.h @@ -2424,6 +2424,7 @@ public: display.print(r); } #else + if (_editMode == EDIT_TEXT) { display.print("Type, Enter:Ok Q:Cancel"); #ifdef MECK_WIFI_COMPANION } else if (_editMode == EDIT_WIFI) { diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 7b4809f6..3003b991 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -154,8 +154,16 @@ class HomeScreen : public UIScreen { void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts, int* outIconX = nullptr) { - // Use voltage-based estimation to match BLE app readings + // Use BQ27220 fuel gauge State of Charge when available — it tracks + // the real LiPo discharge curve, impedance, and temperature. The old + // linear voltage mapping (3.0–4.2V) over-reports while charging + // (voltage inflated by charger current) and is non-linear across the + // flat middle of the discharge curve. uint8_t batteryPercentage = 0; +#if HAS_BQ27220 + batteryPercentage = _task->getBatteryPercent(); +#else + // Fallback for boards without a fuel gauge if (batteryMilliVolts > 0) { const int minMilliVolts = 3000; const int maxMilliVolts = 4200; @@ -164,6 +172,7 @@ void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts, if (pct > 100) pct = 100; batteryPercentage = (uint8_t)pct; } +#endif display.setColor(DisplayDriver::GREEN); display.setTextSize(_node_prefs->smallTextSize()); @@ -1143,13 +1152,17 @@ public: // ---- Battery + unread on one line ---- display.setTextSize(1); { - uint16_t mv = _task->getBattMilliVolts(); int pct = 0; +#if HAS_BQ27220 + pct = _task->getBatteryPercent(); +#else + uint16_t mv = _task->getBattMilliVolts(); if (mv > 0) { pct = ((mv - 3000) * 100) / (4200 - 3000); if (pct < 0) pct = 0; if (pct > 100) pct = 100; } +#endif int unread = _task->getUnreadMsgCount(); char infoBuf[32]; @@ -1972,11 +1985,27 @@ if (curr) curr->poll(); } } - // Lock screen clock refresh — update time display every 15 minutes. - // Runs outside the _display->isOn() gate so it works even after auto-off. - // Wakes the display briefly to render, then lets auto-off turn it back off. + // Lock screen clock refresh — keeps the displayed time current. + // T-Deck Pro: every 1 minute. T5S3: every 2 minutes. + // Wakes the display driver briefly to render, then auto-off handles it. + // T5S3 standalone: no refreshes once powersaving begins — the device + // shows "hibernating..." and enters light sleep instead. +#if defined(LilyGo_T5S3_EPaper_Pro) && !defined(BLE_PIN_CODE) && !defined(MECK_WIFI_COMPANION) + // T5S3 standalone: only refresh while still active (before powersaving kicks in) + if (_locked && _display != NULL && _display->isOn()) { + const unsigned long LOCK_REFRESH_INTERVAL = 2UL * 60UL * 1000UL; // 2 minutes +#elif defined(LilyGo_T5S3_EPaper_Pro) + // T5S3 BLE/WiFi: refresh every 2 minutes if (_locked && _display != NULL) { - const unsigned long LOCK_REFRESH_INTERVAL = 15UL * 60UL * 1000UL; // 15 minutes + const unsigned long LOCK_REFRESH_INTERVAL = 2UL * 60UL * 1000UL; // 2 minutes +#elif defined(LilyGo_TDeck_Pro) + // T-Deck Pro: refresh every 1 minute + if (_locked && _display != NULL) { + const unsigned long LOCK_REFRESH_INTERVAL = 1UL * 60UL * 1000UL; // 1 minute +#else + if (_locked && _display != NULL) { + const unsigned long LOCK_REFRESH_INTERVAL = 2UL * 60UL * 1000UL; // 2 minutes +#endif if (millis() - _lastLockRefresh >= LOCK_REFRESH_INTERVAL) { _lastLockRefresh = millis(); if (!_display->isOn()) { @@ -1998,6 +2027,20 @@ if (curr) curr->poll(); if (_locked && _display != NULL && !_display->isOn()) { unsigned long now = millis(); if (now - _psLastActive >= _psNextSleepSecs * 1000UL) { + // First sleep entry: render a static "hibernating..." frame on the + // e-ink. Since e-ink retains its image indefinitely without power, + // this tells the user the device is in low-power mode until they + // wake it with the boot button. + if (_psNextSleepSecs == 60) { + _display->turnOn(); + _display->startFrame(); + _display->setTextSize(1); + _display->setColor(DisplayDriver::GREEN); + _display->drawTextCentered(_display->width() / 2, 34, "hibernating..."); + _display->endFrame(); + delay(700); // Allow e-ink refresh to complete + _display->turnOff(); + } Serial.println("[POWERSAVE] Entering light sleep (locked+idle)"); board.sleep(1800); // Light sleep up to 30 min // ── CPU resumes here on wake ── @@ -2149,7 +2192,7 @@ void UITask::lockScreen() { #endif _next_refresh = 0; // Draw lock screen immediately _auto_off = millis() + 60000; // 60s before display off while locked - _lastLockRefresh = millis(); // Start 15-min clock refresh cycle + _lastLockRefresh = millis(); // Start lock screen clock refresh cycle #if defined(LilyGo_T5S3_EPaper_Pro) && !defined(BLE_PIN_CODE) && !defined(MECK_WIFI_COMPANION) _psLastActive = millis(); // Start powersaving countdown (60s to first sleep) _psNextSleepSecs = 60; diff --git a/src/MeshCore.h b/src/MeshCore.h index f194cdeb..4eaaa69f 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -43,6 +43,7 @@ namespace mesh { class MainBoard { public: virtual uint16_t getBattMilliVolts() = 0; + virtual uint8_t getBatteryPercent() { return 0; } virtual float getMCUTemperature() { return NAN; } virtual bool setAdcMultiplier(float multiplier) { return false; }; virtual float getAdcMultiplier() const { return 0.0f; } diff --git a/src/helpers/BaseSerialInterface.h b/src/helpers/BaseSerialInterface.h index e6092765..8f5cb61c 100644 --- a/src/helpers/BaseSerialInterface.h +++ b/src/helpers/BaseSerialInterface.h @@ -2,7 +2,7 @@ #include -#define MAX_FRAME_SIZE 172 +#define MAX_FRAME_SIZE 255 class BaseSerialInterface { protected: @@ -18,4 +18,4 @@ public: virtual bool isWriteBusy() const = 0; virtual size_t writeFrame(const uint8_t src[], size_t len) = 0; virtual size_t checkRecvFrame(uint8_t dest[]) = 0; -}; +}; \ No newline at end of file