From 1552aa008167384ab3eb06bf486154a326074350 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 30 Sep 2023 21:09:17 -0500 Subject: [PATCH] Tracker role wakeup and sleep cycle when power.is_power_saving true (#2846) * WIP * Sleepy sleepy low power tracker * Sleepy tracker clear * NRF52 PoC * Simplify NRF52 "sleep" * Trackers aren't polite * Remove unnecessary include * Removed accidental commit * Fixed not-so-sleepy T-Beam due to button gpio mask precendence * Added sleepOnNextExecution for allowing fulfillment of pending messages before shutting down * Cleanup * Don't wantResponse for trackers * Heltec wireless tracker doesn't like the button interrupt (maybe all s3 because user button press doubles as bootloader mode trigger?) --- src/Power.cpp | 2 +- src/PowerFSM.cpp | 2 +- src/concurrency/OSThread.h | 1 + src/modules/PositionModule.cpp | 38 +++++++++++++++++++++++++++---- src/modules/PositionModule.h | 5 +++- src/platform/esp32/main-esp32.cpp | 13 +++-------- src/platform/nrf52/main-nrf52.cpp | 14 ++++++++---- src/sleep.cpp | 24 ++++++++++--------- src/sleep.h | 2 +- 9 files changed, 68 insertions(+), 33 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index 460f598b3..57d6513a7 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -414,7 +414,7 @@ void Power::shutdown() #ifdef PIN_LED3 ledOff(PIN_LED2); #endif - doDeepSleep(DELAY_FOREVER); + doDeepSleep(DELAY_FOREVER, false); #endif } diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index e6a2836bb..b8c70292c 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -45,7 +45,7 @@ static void sdsEnter() { LOG_DEBUG("Enter state: SDS\n"); // FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw - doDeepSleep(getConfiguredOrDefaultMs(config.power.sds_secs)); + doDeepSleep(getConfiguredOrDefaultMs(config.power.sds_secs), false); } extern Power *power; diff --git a/src/concurrency/OSThread.h b/src/concurrency/OSThread.h index 7957ee952..0fbfe2e17 100644 --- a/src/concurrency/OSThread.h +++ b/src/concurrency/OSThread.h @@ -67,6 +67,7 @@ class OSThread : public Thread * Returns desired period for next invocation (or RUN_SAME for no change) */ virtual int32_t runOnce() = 0; + bool sleepOnNextExecution = false; // Do not override this virtual void run(); diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 39e467cda..29ddde4ce 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -1,4 +1,5 @@ #include "PositionModule.h" +#include "GPS.h" #include "MeshService.h" #include "NodeDB.h" #include "RTC.h" @@ -7,6 +8,8 @@ #include "airtime.h" #include "configuration.h" #include "gps/GeoCoord.h" +#include "sleep.h" +#include "target_specific.h" PositionModule *positionModule; @@ -14,8 +17,22 @@ PositionModule::PositionModule() : ProtobufModule("position", meshtastic_PortNum_POSITION_APP, &meshtastic_Position_msg), concurrency::OSThread("PositionModule") { - isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others - setIntervalFromNow(60 * 1000); // Send our initial position 60 seconds after we start (to give GPS time to setup) + isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others + if (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER) + setIntervalFromNow(60 * 1000); + + // Power saving trackers should clear their position on startup to avoid waking up and sending a stale position + if (config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER && config.power.is_power_saving) { + clearPosition(); + } +} + +void PositionModule::clearPosition() +{ + LOG_DEBUG("Clearing position on startup for sleepy tracker (ー。ー) zzz\n"); + meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum()); + node->position.latitude_i = 0; + node->position.longitude_i = 0; } bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Position *pptr) @@ -148,7 +165,7 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha } p->to = dest; - p->decoded.want_response = wantReplies; + p->decoded.want_response = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER ? false : wantReplies; if (config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER) p->priority = meshtastic_MeshPacket_Priority_RELIABLE; else @@ -159,10 +176,23 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha p->channel = channel; service.sendToMesh(p, RX_SRC_LOCAL, true); + + if (config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER && config.power.is_power_saving) { + LOG_DEBUG("Starting next execution in 3 seconds and then going to sleep.\n"); + sleepOnNextExecution = true; + setIntervalFromNow(3000); + } } int32_t PositionModule::runOnce() { + if (sleepOnNextExecution == true) { + sleepOnNextExecution = false; + uint32_t nightyNightMs = getConfiguredOrDefaultMs(config.position.position_broadcast_secs); + LOG_DEBUG("Sleeping for %ims, then awaking to send position again.\n", nightyNightMs); + doDeepSleep(nightyNightMs, false); + } + meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum()); // We limit our GPS broadcasts to a max rate @@ -172,7 +202,7 @@ int32_t PositionModule::runOnce() if (lastGpsSend == 0 || msSinceLastSend >= intervalMs) { // Only send packets if the channel is less than 40% utilized. - if (airTime->isTxAllowedChannelUtil()) { + if (airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER)) { if (hasValidPosition(node)) { lastGpsSend = now; diff --git a/src/modules/PositionModule.h b/src/modules/PositionModule.h index 3114f31e1..1b7eca800 100644 --- a/src/modules/PositionModule.h +++ b/src/modules/PositionModule.h @@ -49,6 +49,9 @@ class PositionModule : public ProtobufModule, private concu private: struct SmartPosition getDistanceTraveledSinceLastSend(meshtastic_PositionLite currentPosition); + + /** Only used in power saving trackers for now */ + void clearPosition(); }; struct SmartPosition { @@ -57,4 +60,4 @@ struct SmartPosition { bool hasTraveledOverThreshold; }; -extern PositionModule *positionModule; +extern PositionModule *positionModule; \ No newline at end of file diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 17a312664..d9bc57b02 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -200,22 +200,15 @@ void cpuDeepSleep(uint32_t msecToWake) esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); #ifdef BUTTON_PIN - // Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39. -#if SOC_RTCIO_HOLD_SUPPORTED - uint64_t gpioMask = (1ULL << config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN); + +#if SOC_PM_SUPPORT_EXT_WAKEUP && !defined(HELTEC_WIRELESS_TRACKER) + esp_sleep_enable_ext0_wakeup((gpio_num_t)(1ULL << (config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)), LOW); #endif #ifdef BUTTON_NEED_PULLUP gpio_pullup_en((gpio_num_t)BUTTON_PIN); #endif - // Not needed because both of the current boards have external pullups - // FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead of - // just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN); - -#if SOC_PM_SUPPORT_EXT_WAKEUP - esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW); -#endif #endif esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index fd6fe2cc2..6b986c778 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -184,10 +184,16 @@ void cpuDeepSleep(uint32_t msecToWake) // FIXME, use non-init RAM per // https://devzone.nordicsemi.com/f/nordic-q-a/48919/ram-retention-settings-with-softdevice-enabled - auto ok = sd_power_system_off(); - if (ok != NRF_SUCCESS) { - LOG_ERROR("FIXME: Ignoring soft device (EasyDMA pending?) and forcing system-off!\n"); - NRF_POWER->SYSTEMOFF = 1; + if (config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER && config.power.is_power_saving == true) { + sd_power_mode_set(NRF_POWER_MODE_LOWPWR); + delay(msecToWake); + NVIC_SystemReset(); + } else { + auto ok = sd_power_system_off(); + if (ok != NRF_SUCCESS) { + LOG_ERROR("FIXME: Ignoring soft device (EasyDMA pending?) and forcing system-off!\n"); + NRF_POWER->SYSTEMOFF = 1; + } } // The following code should not be run, because we are off diff --git a/src/sleep.cpp b/src/sleep.cpp index 62ce0064f..d9fb1ba50 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -135,16 +135,18 @@ bool doPreflightSleep() } /// Tell devices we are going to sleep and wait for them to handle things -static void waitEnterSleep() +static void waitEnterSleep(bool skipPreflight = false) { - uint32_t now = millis(); - while (!doPreflightSleep()) { - delay(100); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives) + if (!skipPreflight) { + uint32_t now = millis(); + while (!doPreflightSleep()) { + delay(100); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives) - if (millis() - now > 30 * 1000) { // If we wait too long just report an error and go to sleep - RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_SLEEP_ENTER_WAIT); - assert(0); // FIXME - for now we just restart, need to fix bug #167 - break; + if (millis() - now > 30 * 1000) { // If we wait too long just report an error and go to sleep + RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_SLEEP_ENTER_WAIT); + assert(0); // FIXME - for now we just restart, need to fix bug #167 + break; + } } } @@ -155,7 +157,7 @@ static void waitEnterSleep() notifySleep.notifyObservers(NULL); } -void doDeepSleep(uint32_t msecToWake) +void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) { if (INCLUDE_vTaskSuspend && (msecToWake == portMAX_DELAY)) { LOG_INFO("Entering deep sleep forever\n"); @@ -165,7 +167,7 @@ void doDeepSleep(uint32_t msecToWake) // not using wifi yet, but once we are this is needed to shutoff the radio hw // esp_wifi_stop(); - waitEnterSleep(); + waitEnterSleep(skipPreflight); notifyDeepSleep.notifyObservers(NULL); screen->doDeepSleep(); // datasheet says this will draw only 10ua @@ -227,7 +229,7 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r { // LOG_DEBUG("Enter light sleep\n"); - waitEnterSleep(); + waitEnterSleep(false); uint64_t sleepUsec = sleepMsec * 1000LL; diff --git a/src/sleep.h b/src/sleep.h index 8ff81035c..a592ad2c1 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -4,7 +4,7 @@ #include "Observer.h" #include "configuration.h" -void doDeepSleep(uint32_t msecToWake), cpuDeepSleep(uint32_t msecToWake); +void doDeepSleep(uint32_t msecToWake, bool skipPreflight), cpuDeepSleep(uint32_t msecToWake); #ifdef ARCH_ESP32 #include "esp_sleep.h"