diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 6f363d7f96..2dc795420b 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -821,26 +821,31 @@ void UITask::loop() { #ifdef AUTO_SHUTDOWN_MILLIVOLTS if (millis() > next_batt_chck) { - 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 defined(THINKNODE_M1) || defined(LILYGO_TECHO) - if (_display != NULL) { - _display->startFrame(); - _display->setTextSize(2); - _display->setColor(DisplayDriver::RED); - _display->drawTextCentered(_display->width() / 2, 20, "Low Battery."); - _display->drawTextCentered(_display->width() / 2, 40, "Shutting Down!"); - _display->endFrame(); - } - #endif + 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 defined(THINKNODE_M1) || defined(LILYGO_TECHO) + if (_display != NULL) { + _display->startFrame(); + _display->setTextSize(2); + _display->setColor(DisplayDriver::RED); + _display->drawTextCentered(_display->width() / 2, 20, "Low Battery."); + _display->drawTextCentered(_display->width() / 2, 40, "Shutting Down!"); + _display->endFrame(); + } + #endif - shutdown(); + shutdown(); - } - next_batt_chck = millis() + 8000; + } + next_batt_chck = millis() + 8000; } #endif } diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 879fcbf026..cf78dc6e96 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -920,6 +920,10 @@ void SensorMesh::loop() { uint32_t curr = getRTCClock()->getCurrentTime(); if (curr >= last_read_time + SENSOR_READ_INTERVAL_SECS) { + // Skip this cycle if TX just completed — the current spike sags battery + // voltage enough to falsely trigger low-battery alerts on weaker cells. + // The loop runs again in milliseconds, so the read is only deferred briefly. + if (!board.isBattReadSafe(millis())) return; telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); // query other sensors -- target specific diff --git a/src/MeshCore.h b/src/MeshCore.h index b4c57faf32..b2da661b20 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -41,8 +41,29 @@ namespace mesh { #define BD_STARTUP_NORMAL 0 // getStartupReason() codes #define BD_STARTUP_RX_PACKET 1 +// Milliseconds to wait after TX completes before trusting battery ADC readings. +// LoRa TX causes a current spike that sags battery terminal voltage; on LiPo +// cells below ~50% SoC the sag is large enough to cross shutdown/alert thresholds. +#ifndef POST_TX_BATT_SETTLE_MS +#define POST_TX_BATT_SETTLE_MS 250 +#endif + class MainBoard { + + bool _tx_active = false; + uint32_t _last_tx_complete_ms = 0; + public: + // Called by the radio layer — not meant to be overridden. + void notifyTxStart() { _tx_active = true; } + void notifyTxComplete(uint32_t now_ms) { _tx_active = false; _last_tx_complete_ms = now_ms; } + + // Returns true when it is safe to read the battery ADC (TX not in progress + // and enough time has elapsed since the last transmission for voltage to recover). + bool isBattReadSafe(uint32_t now_ms, uint32_t settle_ms = POST_TX_BATT_SETTLE_MS) const { + return !_tx_active && (now_ms - _last_tx_complete_ms >= settle_ms); + } + virtual uint16_t getBattMilliVolts() = 0; virtual float getMCUTemperature() { return NAN; } virtual bool setAdcMultiplier(float multiplier) { return false; }; diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index b6519aefa7..eb354c3e67 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -151,6 +151,7 @@ uint32_t RadioLibWrapper::getEstAirtimeFor(int len_bytes) { } bool RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) { + _board->notifyTxStart(); _board->onBeforeTransmit(); int err = _radio->startTransmit((uint8_t *) bytes, len); if (err == RADIOLIB_ERR_NONE) { @@ -160,6 +161,7 @@ bool RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) { MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startTransmit(%d)", err); idle(); // trigger another startRecv() _board->onAfterTransmit(); + _board->notifyTxComplete(millis()); return false; } @@ -175,6 +177,7 @@ bool RadioLibWrapper::isSendComplete() { void RadioLibWrapper::onSendFinished() { _radio->finishTransmit(); _board->onAfterTransmit(); + _board->notifyTxComplete(millis()); state = STATE_IDLE; }