From f6e2bc9fbeac82f2ab8a7cfc0bf0b54419380243 Mon Sep 17 00:00:00 2001 From: Nick Dunklee Date: Tue, 2 Jun 2026 20:33:44 -0600 Subject: [PATCH] fix: contact sync across simple_repeater and companion_radio MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - hasPendingWork() extended so simple_repeater and companion_radio both gate sleep and shutdown for pending contact writes - shutdown loops using hasPendingWork instead of ShutdownHandler idea in previous PR - UITask shutdown behavior - all three variants (ui-new, ui-tiny, ui-orig) now set a _wants_shutdown flag instead of calling powerOff() directly - "Power off in 5 seconds…" messages added everywhere a shutdown is announced - "all" (hopefully) eInk displays are now tagged together using isEInk() constant - "all" eInk displays will send a final "Shut Down" message so the displays show "Shut Down" after a successful shutdown --- examples/companion_radio/AbstractUITask.h | 4 +++ examples/companion_radio/main.cpp | 17 +++++++++++ examples/companion_radio/ui-new/UITask.cpp | 33 +++++++++++++++++---- examples/companion_radio/ui-orig/UITask.cpp | 10 +++++-- examples/companion_radio/ui-tiny/UITask.cpp | 13 +++++--- examples/simple_repeater/MyMesh.cpp | 2 +- examples/simple_repeater/main.cpp | 11 +++++-- src/helpers/ui/DisplayDriver.h | 1 + src/helpers/ui/E213Display.h | 1 + src/helpers/ui/E290Display.h | 1 + src/helpers/ui/GxEPDDisplay.h | 2 +- 11 files changed, 80 insertions(+), 15 deletions(-) diff --git a/examples/companion_radio/AbstractUITask.h b/examples/companion_radio/AbstractUITask.h index 0eee45aef3..5413b8017e 100644 --- a/examples/companion_radio/AbstractUITask.h +++ b/examples/companion_radio/AbstractUITask.h @@ -27,12 +27,16 @@ class AbstractUITask { mesh::MainBoard* _board; BaseSerialInterface* _serial; bool _connected; + bool _wants_shutdown = false; + bool _restart_on_shutdown = false; AbstractUITask(mesh::MainBoard* board, BaseSerialInterface* serial) : _board(board), _serial(serial) { _connected = false; } public: + bool wantsShutdown() const { return _wants_shutdown; } + bool isRestart() const { return _restart_on_shutdown; } void setHasConnection(bool connected) { _connected = connected; } bool hasConnection() const { return _connected; } uint16_t getBattMilliVolts() const { return _board->getBattMilliVolts(); } diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index ef9b6bfca4..5af76df6c1 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -256,6 +256,23 @@ void loop() { #endif } +#ifdef DISPLAY_CLASS + if (ui_task.wantsShutdown() && !the_mesh.hasPendingWork()) { + if (display.isEInk()) { + display.startFrame(); + display.setTextSize(2); + display.setColor(DisplayDriver::LIGHT); + display.drawTextCentered(display.width() / 2, 28, "Powered off."); + display.endFrame(); + } else { + display.turnOff(); + } + radio_driver.powerOff(); + if (ui_task.isRestart()) board.reboot(); + else board.powerOff(); + } +#endif + #if defined(ESP32) && defined(WIFI_SSID) // Safely attempt to reconnect every 10 seconds if flagged if (wifi_needs_reconnect && (millis() - last_wifi_reconnect_attempt > 10000)) { diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index ee12ca740d..848b1c162c 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -697,9 +697,14 @@ void UITask::shutdown(bool restart){ if (restart) { _board->reboot(); } else { - _display->turnOff(); - radio_driver.powerOff(); - _board->powerOff(); + if (_display != NULL) { + _display->startFrame(); + _display->setTextSize(1); + _display->setColor(DisplayDriver::LIGHT); + _display->drawTextCentered(_display->width() / 2, 20, "Power off in 5 seconds..."); + _display->endFrame(); + } + _wants_shutdown = true; } } @@ -830,8 +835,26 @@ void UITask::loop() { #ifdef AUTO_SHUTDOWN_MILLIVOLTS if (millis() > next_batt_chck) { - uint16_t milliVolts = getBattMilliVolts(); - if (milliVolts > 0 && milliVolts < AUTO_SHUTDOWN_MILLIVOLTS) { + uint32_t now = millis(); + if (!_board->isBattReadSafe(now)) { + // TX just completed — voltage hasn't recovered yet; retry after settle window. + next_batt_chck = now + POST_TX_BATT_SETTLE_MS + 50; + } else { + uint16_t milliVolts = getBattMilliVolts(); + if (milliVolts > 0 && milliVolts < AUTO_SHUTDOWN_MILLIVOLTS) { + + // show low battery shutdown alert + // we should only do this for eink displays, which will persist after power loss + if (_display != NULL && _display->isEInk()) { + _display->startFrame(); + _display->setTextSize(2); + _display->setColor(DisplayDriver::RED); + _display->drawTextCentered(_display->width() / 2, 20, "Low Battery."); + _display->drawTextCentered(_display->width() / 2, 40, "Power off in 5 sec."); + _display->endFrame(); + } + + shutdown(); // show low battery shutdown alert // we should only do this for eink displays, which will persist after power loss diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 5529046775..87e60b5f16 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -307,8 +307,14 @@ void UITask::shutdown(bool restart){ if (restart) { _board->reboot(); } else { - radio_driver.powerOff(); - _board->powerOff(); + if (_display != NULL) { + _display->startFrame(); + _display->setTextSize(1); + _display->setColor(DisplayDriver::LIGHT); + _display->drawTextCentered(_display->width() / 2, 20, "Power off in 5 seconds..."); + _display->endFrame(); + } + _wants_shutdown = true; } } diff --git a/examples/companion_radio/ui-tiny/UITask.cpp b/examples/companion_radio/ui-tiny/UITask.cpp index 45a07a02ef..d282284307 100644 --- a/examples/companion_radio/ui-tiny/UITask.cpp +++ b/examples/companion_radio/ui-tiny/UITask.cpp @@ -364,7 +364,7 @@ class HomeScreen : public UIScreen { display.setColor(DisplayDriver::GREEN); display.setTextSize(1); if (_shutdown_init) { - display.drawTextCentered(display.width() / 2, 20, "hibernating..."); + display.drawTextCentered(display.width() / 2, 20, "Power off in 5 sec."); } else { display.drawXbm((display.width() - 32) / 2, 8, power_icon, 32, 32); // display.drawTextCentered(display.width() / 2, 40 - 11, "hibernate:" PRESS_LABEL); @@ -566,9 +566,14 @@ void UITask::shutdown(bool restart){ if (restart) { _board->reboot(); } else { - _display->turnOff(); - radio_driver.powerOff(); - _board->powerOff(); + if (_display != NULL) { + _display->startFrame(); + _display->setTextSize(1); + _display->setColor(DisplayDriver::LIGHT); + _display->drawTextCentered(_display->width() / 2, 20, "Power off in 5 sec."); + _display->endFrame(); + } + _wants_shutdown = true; } } diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index c24e297cd3..473e58dd7e 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1310,5 +1310,5 @@ bool MyMesh::hasPendingWork() const { #if defined(WITH_BRIDGE) if (bridge.isRunning()) return true; // bridge needs WiFi radio, can't sleep #endif - return _mgr->getOutboundTotal() > 0; + return _mgr->getOutboundTotal() > 0 || dirty_contacts_expiry != 0; } diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 297337ab5c..286f6d0186 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -141,8 +141,15 @@ void loop() { if (userBtnDownAt == 0) { userBtnDownAt = millis(); } else if ((unsigned long)(millis() - userBtnDownAt) >= USER_BTN_HOLD_OFF_MILLIS) { - Serial.println("Powering off..."); - board.powerOff(); // does not return + if (!the_mesh.hasPendingWork()) { + board.powerOff(); // does not return + } else { + static bool notified = false; + if (!notified) { + Serial.println("Power off in 5 seconds..."); + notified = true; + } + } } } else { userBtnDownAt = 0; diff --git a/src/helpers/ui/DisplayDriver.h b/src/helpers/ui/DisplayDriver.h index ec63c19123..b55243cff1 100644 --- a/src/helpers/ui/DisplayDriver.h +++ b/src/helpers/ui/DisplayDriver.h @@ -13,6 +13,7 @@ class DisplayDriver { int width() const { return _w; } int height() const { return _h; } + virtual bool isEInk() const { return false; } virtual bool isOn() = 0; virtual void turnOn() = 0; virtual void turnOff() = 0; diff --git a/src/helpers/ui/E213Display.h b/src/helpers/ui/E213Display.h index 420792c8a3..d646d55bb7 100644 --- a/src/helpers/ui/E213Display.h +++ b/src/helpers/ui/E213Display.h @@ -25,6 +25,7 @@ class E213Display : public DisplayDriver { } } bool begin(); + bool isEInk() const override { return true; } bool isOn() override { return _isOn; } void turnOn() override; void turnOff() override; diff --git a/src/helpers/ui/E290Display.h b/src/helpers/ui/E290Display.h index 2ca50225d1..12ecc0abd7 100644 --- a/src/helpers/ui/E290Display.h +++ b/src/helpers/ui/E290Display.h @@ -21,6 +21,7 @@ class E290Display : public DisplayDriver { E290Display(RefCountedDigitalPin* periph_power = NULL) : DisplayDriver(296, 128), _periph_power(periph_power) {} bool begin(); + bool isEInk() const override { return true; } bool isOn() override { return _isOn; } void turnOn() override; void turnOff() override; diff --git a/src/helpers/ui/GxEPDDisplay.h b/src/helpers/ui/GxEPDDisplay.h index 1a04cc2464..aab685d4f9 100644 --- a/src/helpers/ui/GxEPDDisplay.h +++ b/src/helpers/ui/GxEPDDisplay.h @@ -45,7 +45,7 @@ class GxEPDDisplay : public DisplayDriver { #endif bool begin(); - + bool isEInk() const override { return true; } bool isOn() override {return _isOn;}; void turnOn() override; void turnOff() override;