diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index d3000c500af..0fe42d23365 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -892,7 +892,7 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.has_detection_sensor = true; moduleConfig.detection_sensor.enabled = false; moduleConfig.detection_sensor.detection_trigger_type = meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_HIGH; - moduleConfig.detection_sensor.minimum_broadcast_secs = 45; + moduleConfig.detection_sensor.message_rate_limit = 45; moduleConfig.has_ambient_lighting = true; moduleConfig.ambient_lighting.current = 10; diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index 47d3b5baab6..30103acf75c 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -174,12 +174,12 @@ typedef struct _meshtastic_ModuleConfig_DetectionSensorConfig { bool enabled; /* Interval in seconds of how often we can send a message to the mesh when a trigger event is detected */ - uint32_t minimum_broadcast_secs; + uint32_t message_rate_limit; /* Interval in seconds of how often we should send a message to the mesh with the current state regardless of trigger events When set to 0, only trigger events will be broadcasted Works as a sort of status heartbeat for peace of mind */ - uint32_t state_broadcast_secs; + uint32_t state_broadcast_interval; /* Send ASCII bell with alert message Useful for triggering ext. notification on bell */ bool send_bell; @@ -738,8 +738,8 @@ X(a, STATIC, SINGULAR, BOOL, transmit_over_lora, 3) #define meshtastic_ModuleConfig_DetectionSensorConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ -X(a, STATIC, SINGULAR, UINT32, minimum_broadcast_secs, 2) \ -X(a, STATIC, SINGULAR, UINT32, state_broadcast_secs, 3) \ +X(a, STATIC, SINGULAR, UINT32, message_rate_limit, 2) \ +X(a, STATIC, SINGULAR, UINT32, state_broadcast_interval, 3) \ X(a, STATIC, SINGULAR, BOOL, send_bell, 4) \ X(a, STATIC, SINGULAR, STRING, name, 5) \ X(a, STATIC, SINGULAR, UINT32, monitor_pin, 6) \ diff --git a/src/modules/DetectionSensorModule.cpp b/src/modules/DetectionSensorModule.cpp index ca682b77208..cc2879a272d 100644 --- a/src/modules/DetectionSensorModule.cpp +++ b/src/modules/DetectionSensorModule.cpp @@ -6,6 +6,27 @@ #include "configuration.h" #include "main.h" #include + +// When using this module with power-save enabled and in role sensor you can benefit from very low power consumption on nRF52 and +// ESP32/S/S2 Mind setting a valid, non-used GPIO pin. On ESP it is manditory to choose a 'RTC GPIO'. + +#ifdef ARCH_NRF52 +#include "sleep.h" +// Reserved values for GPREGRET are: 0xF5 (NRF52_MAGIC_LFS_IS_CORRUPT) +// and for the bootloader 0xB1, 0xA8, 0x4E, 0x57, 0x6D +// although the bootloader shouldn't be called in the following soft reset. +constexpr uint32_t BOOT_FROM_COLD = 0x00; +constexpr uint32_t BOOT_FROM_TIMEOUT = 0xC0; +constexpr uint32_t BOOT_FROM_GPIOEVENT = 0xFE; +#elif defined(ESP32_WITH_EXT0) +#include "esp32/clk.h" +#include "sleep.h" +#include "soc/rtc.h" +#include +RTC_DATA_ATTR static uint32_t time_acc_s = 0; +RTC_DATA_ATTR static uint32_t sleepTime_s; +#endif + DetectionSensorModule *detectionSensorModule; #define GPIO_POLLING_INTERVAL 100 @@ -55,8 +76,8 @@ int32_t DetectionSensorModule::runOnce() // moduleConfig.detection_sensor.enabled = true; // moduleConfig.detection_sensor.monitor_pin = 10; // WisBlock PIR IO6 // moduleConfig.detection_sensor.monitor_pin = 21; // WisBlock RAK12013 Radar IO6 - // moduleConfig.detection_sensor.minimum_broadcast_secs = 30; - // moduleConfig.detection_sensor.state_broadcast_secs = 120; + // moduleConfig.detection_sensor.message_rate_limit = 30; + // moduleConfig.detection_sensor.state_broadcast_interval = 120; // moduleConfig.detection_sensor.detection_trigger_type = // meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_HIGH; // strcpy(moduleConfig.detection_sensor.name, "Motion"); @@ -65,29 +86,151 @@ int32_t DetectionSensorModule::runOnce() return disable(); if (firstTime) { - #ifdef DETECTION_SENSOR_EN pinMode(DETECTION_SENSOR_EN, OUTPUT); digitalWrite(DETECTION_SENSOR_EN, HIGH); #endif - // This is the first time the OSThread library has called this function, so do some setup firstTime = false; + if (moduleConfig.detection_sensor.monitor_pin > 0) { - pinMode(moduleConfig.detection_sensor.monitor_pin, moduleConfig.detection_sensor.use_pullup ? INPUT_PULLUP : INPUT); +#ifdef ARCH_NRF52 + if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) { + nrf_gpio_pin_sense_t toSense = + (moduleConfig.detection_sensor.detection_trigger_type & 1) ? NRF_GPIO_PIN_SENSE_HIGH : NRF_GPIO_PIN_SENSE_LOW; + nrf_gpio_cfg_input(moduleConfig.detection_sensor.monitor_pin, + moduleConfig.detection_sensor.use_pullup ? NRF_GPIO_PIN_PULLUP : NRF_GPIO_PIN_NOPULL); + nrf_gpio_cfg_sense_set(moduleConfig.detection_sensor.monitor_pin, toSense); + + uint32_t regret = BOOT_FROM_COLD; + if (sd_power_gpregret_get(0, ®ret) != NRF_SUCCESS) { + // necessary if softdevice is not enabled yet + regret = NRF_POWER->GPREGRET; + } + if (NRF_P0->LATCH || NRF_P1->LATCH) { + LOG_INFO("Woke up from eternal sleep by GPIO."); + NRF_P1->LATCH = 0xFFFFFFFF; + NRF_P0->LATCH = 0xFFFFFFFF; + sendDetectionMessage(); + } else if (regret == BOOT_FROM_TIMEOUT) { + LOG_INFO("Woke up by timeout."); + // When encountering a timeout without setting the timer ourself the source must be another module running + // concurrently + if (moduleConfig.detection_sensor.state_broadcast_interval) + sendCurrentStateMessage(getState()); + } else if (regret == BOOT_FROM_GPIOEVENT) { + LOG_INFO("Woke up from interval sleep by GPIO."); + sendDetectionMessage(); + } else { + // We booted fresh. Enforce sending on first detection event. + lastSentToMesh = -Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.message_rate_limit); + } + if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS)) { + NRF_POWER->GPREGRET = BOOT_FROM_COLD; + } + + LOG_INFO("Cold boot. Init in power saving mode"); + // Choose the minimum wakeup time (config.power.min_wake_sec) depending to your needs. + // The least time should be 5s to send just the detection message. + // Choose 45s+ for comfortable connectivity via BLE or USB. + // Device Telemetry is sent after ~62s. + return Default::getConfiguredOrDefaultMs(config.power.min_wake_secs, 90); + } else +#elif defined(ESP32_WITH_EXT0) + if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) { + + esp_sleep_wakeup_cause_t wakeCause = esp_sleep_get_wakeup_cause(); + uint64_t timeNow_us; + uint32_t timeDiff_s; + + switch (wakeCause) { + case ESP_SLEEP_WAKEUP_EXT0: + case ESP_SLEEP_WAKEUP_EXT1: + LOG_INFO("Woke up from interval sleep by GPIO"); + timeNow_us = rtc_time_slowclk_to_us(rtc_time_get(), esp_clk_slowclk_cal_get()); + timeDiff_s = (timeNow_us / 1000000 - sleepTime_s) + config.power.min_wake_secs; + // LOG_INFO("sleeptime (effectivly lastSentToMesh): %u", timeDiff_s - config.power.min_wake_secs); + // LOG_INFO("time since last run (effectivly lastSentToMesh): %us", timeDiff_s); + + if (timeDiff_s + time_acc_s > moduleConfig.detection_sensor.message_rate_limit) { + // LOG_INFO("sending as %u > %u", timeDiff_s + time_acc_s, + // moduleConfig.detection_sensor.message_rate_limit); + time_acc_s = 0; + sendDetectionMessage(); + } else { + // LOG_INFO("not sending as did not reach the rate limit. %us < %us", timeDiff_s + time_acc_s, + // moduleConfig.detection_sensor.message_rate_limit); + time_acc_s += timeDiff_s; + } + break; + case ESP_SLEEP_WAKEUP_TIMER: + // When encountering a timeout without setting the timer ourself the source must be another module running + // concurrently + LOG_INFO("Woke up by timeout."); + if (moduleConfig.detection_sensor.state_broadcast_interval) + sendCurrentStateMessage(getState()); + break; + case ESP_SLEEP_WAKEUP_TOUCHPAD: + case ESP_SLEEP_WAKEUP_ULP: + default: + // We booted fresh. Enforce sending on first detection event. + LOG_INFO("Cold boot. Init in power saving mode"); + break; + } + + if (rtc_gpio_is_valid_gpio((gpio_num_t)moduleConfig.detection_sensor.monitor_pin)) { + if (moduleConfig.detection_sensor.use_pullup) { + rtc_gpio_pulldown_dis((gpio_num_t)moduleConfig.detection_sensor.monitor_pin); + rtc_gpio_pullup_en((gpio_num_t)moduleConfig.detection_sensor.monitor_pin); + } else { + rtc_gpio_pulldown_dis((gpio_num_t)moduleConfig.detection_sensor.monitor_pin); + rtc_gpio_pullup_dis((gpio_num_t)moduleConfig.detection_sensor.monitor_pin); + } + if (esp_sleep_enable_ext0_wakeup((gpio_num_t)moduleConfig.detection_sensor.monitor_pin, + (moduleConfig.detection_sensor.detection_trigger_type & 1)) != ESP_OK) + LOG_ERROR("error enabling ext0 on gpio %d", moduleConfig.detection_sensor.monitor_pin); + return Default::getConfiguredOrDefaultMs(config.power.min_wake_secs, 90); + } else { + LOG_WARN("Enabled, but specified pin is not an RTC pin. Disabling module"); + printRtcPins(); + return disable(); + } + } else +#elif defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2) + LOG_WARN("Enabled, but used ESP32 variant lacks necessary EXT0. Disabling module"); + return disable(); +#endif + pinMode(moduleConfig.detection_sensor.monitor_pin, + moduleConfig.detection_sensor.use_pullup ? INPUT_PULLUP : INPUT); } else { - LOG_WARN("Detection Sensor Module: Set to enabled but no monitor pin is set. Disable module"); + LOG_WARN("Enabled, but no monitor pin is set. Disabling module"); return disable(); } - LOG_INFO("Detection Sensor Module: init"); + LOG_INFO("Init in default mode"); return setStartDelay(); } - // LOG_DEBUG("Detection Sensor Module: Current pin state: %i", digitalRead(moduleConfig.detection_sensor.monitor_pin)); +#if defined(ARCH_NRF52) || defined(ESP32_WITH_EXT0) + if ((config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving)) { + // If 'State Broadcast Interval' (moduleConfig.detection_sensor.state_broadcast_interval) is specified it will be used, if + // unset, the sleep will last 'forever', interrupted by specified GPIO event + // nRF52: Using a timeout the module enters a low power loop. Without, it will enter a low power delay to comply with the + // message_rate_limit and finally 'shutdown' while sensing the GPIO. + // ESP32: Always uses deep sleep with RTC + + uint32_t nightyNightMs = + Default::getConfiguredOrDefault(moduleConfig.detection_sensor.state_broadcast_interval * 1000, portMAX_DELAY); + +#ifdef ESP32_WITH_EXT0 + sleepTime_s = rtc_time_slowclk_to_us(rtc_time_get(), esp_clk_slowclk_cal_get()) / 1000000; +#endif + doDeepSleep(nightyNightMs, false, true); + } +#endif if (!Throttle::isWithinTimespanMs(lastSentToMesh, - Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs))) { + Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.message_rate_limit))) { bool isDetected = hasDetectionEvent(); DetectionSensorTriggerVerdict verdict = handlers[moduleConfig.detection_sensor.detection_trigger_type](wasDetected, isDetected); @@ -97,20 +240,20 @@ int32_t DetectionSensorModule::runOnce() sendDetectionMessage(); return DELAYED_INTERVAL; case DetectionSensorVerdictSendState: - sendCurrentStateMessage(isDetected); + sendCurrentStateMessage(getState()); return DELAYED_INTERVAL; case DetectionSensorVerdictNoop: break; } } // Even if we haven't detected an event, broadcast our current state to the mesh on the scheduled interval as a sort - // of heartbeat. We only do this if the minimum broadcast interval is greater than zero, otherwise we'll only broadcast state + // of heartbeat. We only do this if the broadcast interval is greater than zero, otherwise we'll only broadcast state // change detections. - if (moduleConfig.detection_sensor.state_broadcast_secs > 0 && + if (moduleConfig.detection_sensor.state_broadcast_interval > 0 && !Throttle::isWithinTimespanMs(lastSentToMesh, - Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_secs, + Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_interval, default_telemetry_broadcast_interval_secs))) { - sendCurrentStateMessage(hasDetectionEvent()); + sendCurrentStateMessage(getState()); return DELAYED_INTERVAL; } return GPIO_POLLING_INTERVAL; @@ -124,6 +267,9 @@ void DetectionSensorModule::sendDetectionMessage() meshtastic_MeshPacket *p = allocDataPacket(); p->want_ack = false; p->decoded.payload.size = strlen(message); + if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) + p->priority = meshtastic_MeshPacket_Priority_RELIABLE; + memcpy(p->decoded.payload.bytes, message, p->decoded.payload.size); if (moduleConfig.detection_sensor.send_bell && p->decoded.payload.size < meshtastic_Constants_DATA_PAYLOAD_LEN) { p->decoded.payload.bytes[p->decoded.payload.size] = 7; // Bell character @@ -142,10 +288,14 @@ void DetectionSensorModule::sendDetectionMessage() void DetectionSensorModule::sendCurrentStateMessage(bool state) { char *message = new char[40]; - sprintf(message, "%s state: %i", moduleConfig.detection_sensor.name, state); + sprintf(message, "%s state %s, %d%%", moduleConfig.detection_sensor.name, state ? "high" : "low", + powerStatus->getBatteryChargePercent()); meshtastic_MeshPacket *p = allocDataPacket(); p->want_ack = false; p->decoded.payload.size = strlen(message); + if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) + p->priority = meshtastic_MeshPacket_Priority_RELIABLE; + memcpy(p->decoded.payload.bytes, message, p->decoded.payload.size); lastSentToMesh = millis(); if (!channels.isDefaultChannel(0)) { @@ -156,9 +306,72 @@ void DetectionSensorModule::sendCurrentStateMessage(bool state) delete[] message; } +boolean DetectionSensorModule::getState() +{ + return digitalRead(moduleConfig.detection_sensor.monitor_pin); +} + bool DetectionSensorModule::hasDetectionEvent() { - bool currentState = digitalRead(moduleConfig.detection_sensor.monitor_pin); - // LOG_DEBUG("Detection Sensor Module: Current state: %i", currentState); + bool currentState = getState(); return (moduleConfig.detection_sensor.detection_trigger_type & 1) ? currentState : !currentState; -} \ No newline at end of file +} + +#ifdef ARCH_NRF52 +boolean DetectionSensorModule::shouldLoop() +{ + return moduleConfig.detection_sensor.enabled && config.power.is_power_saving && moduleConfig.detection_sensor.monitor_pin > 0; +} + +void DetectionSensorModule::lpDelay() +{ + sd_power_mode_set(NRF_POWER_MODE_LOWPWR); + delay(moduleConfig.detection_sensor.message_rate_limit - config.power.min_wake_secs); +} + +void DetectionSensorModule::lpLoop(uint32_t msecToWake) +{ + for (uint32_t i = msecToWake / 100;; i--) { + delay(100); + if (hasDetectionEvent() && + !Throttle::isWithinTimespanMs(lastSentToMesh, + Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.message_rate_limit))) { + if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS && + sd_power_gpregret_set(0, BOOT_FROM_GPIOEVENT) == NRF_SUCCESS)) { + // necessary if softdevice is not enabled yet or never was + NRF_POWER->GPREGRET = BOOT_FROM_GPIOEVENT; + } + break; + } + if (i == 0) { + if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS && sd_power_gpregret_set(0, BOOT_FROM_TIMEOUT) == NRF_SUCCESS)) { + NRF_POWER->GPREGRET = BOOT_FROM_TIMEOUT; + } + break; + } + } +} +#elif defined(ESP32_WITH_EXT0) +void DetectionSensorModule::printRtcPins() +{ +#if defined(CONFIG_IDF_TARGET_ESP32) + LOG_WARN("Available GPIOs for wakeup: 0,2,4,12-15,25-27,32-39"); +#elif defined(CONFIG_IDF_TARGET_ESP32S2) + LOG_WARN("Available GPIOs for wakeup: 0-21"); +#elif defined(CONFIG_IDF_TARGET_ESP32S3) + LOG_WARN("Available GPIOs for wakeup: 0-21"); +#endif +} + +bool DetectionSensorModule::skipGPIO(int gpio) +{ + return moduleConfig.detection_sensor.enabled && config.power.is_power_saving && + moduleConfig.detection_sensor.monitor_pin == gpio; +} +#else +bool DetectionSensorModule::skipGPIO(int gpio) +{ + // always return false for ESP32s without EXT0 (e.g ESP32c6) + return false; +} +#endif diff --git a/src/modules/DetectionSensorModule.h b/src/modules/DetectionSensorModule.h index 3ba10d32929..e6597325d8f 100644 --- a/src/modules/DetectionSensorModule.h +++ b/src/modules/DetectionSensorModule.h @@ -1,6 +1,11 @@ #pragma once #include "SinglePortModule.h" +#if (defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)) && \ + !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2)) +#define ESP32_WITH_EXT0 +#endif + class DetectionSensorModule : public SinglePortModule, private concurrency::OSThread { public: @@ -8,6 +13,14 @@ class DetectionSensorModule : public SinglePortModule, private concurrency::OSTh { } +#ifdef ARCH_NRF52 + boolean shouldLoop(); + void lpDelay(); + void lpLoop(uint32_t msecToWake); +#else + bool skipGPIO(int gpio); +#endif + protected: virtual int32_t runOnce() override; @@ -18,6 +31,8 @@ class DetectionSensorModule : public SinglePortModule, private concurrency::OSTh void sendDetectionMessage(); void sendCurrentStateMessage(bool state); bool hasDetectionEvent(); + boolean getState(); + void printRtcPins(); }; extern DetectionSensorModule *detectionSensorModule; \ No newline at end of file diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 760964119c2..80f5b8ac41a 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -25,6 +25,10 @@ #include #include +#ifndef DMESHTASTIC_EXCLUDE_DETECTIONSENSOR +#include "modules/DetectionSensorModule.h" +#endif + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH void setBluetoothEnable(bool enable) { @@ -225,13 +229,16 @@ void cpuDeepSleep(uint32_t msecToWake) 34, 35, 37}; for (int i = 0; i < sizeof(rtcGpios); i++) - rtc_gpio_isolate((gpio_num_t)rtcGpios[i]); +#ifndef DMESHTASTIC_EXCLUDE_DETECTIONSENSOR + if (detectionSensorModule == nullptr || !detectionSensorModule->skipGPIO(rtcGpios[i])) +#endif + rtc_gpio_isolate((gpio_num_t)rtcGpios[i]); #endif - // FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using - // to detect wake and in normal operation the external part drives them hard. + // FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using + // to detect wake and in normal operation the external part drives them hard. #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. + // 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 && SOC_PM_SUPPORT_EXT_WAKEUP uint64_t gpioMask = (1ULL << (config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); #endif diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index c03cc4454a6..bcea52a0486 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -30,6 +30,10 @@ #include "BQ25713.h" #endif +#ifndef DMESHTASTIC_EXCLUDE_DETECTIONSENSOR +#include "modules/DetectionSensorModule.h" +#endif + // Weak empty variant initialization function. // May be redefined by variant files. void variant_shutdown() __attribute__((weak)); @@ -398,21 +402,32 @@ void cpuDeepSleep(uint32_t msecToWake) #endif variant_shutdown(); - // Sleepy trackers or sensors can low power "sleep" - // Don't enter this if we're sleeping portMAX_DELAY, since that's a shutdown event + // Sleepy trackers or sensors can low power "sleep". + // When they wake up in regular intervals enter the following delay loop, which monitors user inputs and potential GPIO events + // of the detection sensor if active. Detection Sensors may also shutdown and still monitor their assigned GPIO if (msecToWake != portMAX_DELAY && (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TRACKER, meshtastic_Config_DeviceConfig_Role_TAK_TRACKER, meshtastic_Config_DeviceConfig_Role_SENSOR) && config.power.is_power_saving == true)) { sd_power_mode_set(NRF_POWER_MODE_LOWPWR); - delay(msecToWake); +#ifndef DMESHTASTIC_EXCLUDE_DETECTIONSENSOR + if (detectionSensorModule != nullptr && detectionSensorModule->shouldLoop()) { + detectionSensorModule->lpLoop(msecToWake); + } else +#endif + delay(msecToWake); NVIC_SystemReset(); + } else { // Resume on user button press // https://github.com/lyusupov/SoftRF/blob/81c519ca75693b696752235d559e881f2e0511ee/software/firmware/source/SoftRF/src/platform/nRF52.cpp#L1738 constexpr uint32_t DFU_MAGIC_SKIP = 0x6d; - sd_power_gpregret_clr(0, 0xFF); // Clear the register before setting a new values in it for stability reasons - sd_power_gpregret_set(0, DFU_MAGIC_SKIP); // Equivalent NRF_POWER->GPREGRET = DFU_MAGIC_SKIP + + // Clear the register before setting a new values in it for stability reasons + // if sd_functions fail the softdevice is not initialized. Fallback to setting GPREGRET directly. + if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS && sd_power_gpregret_set(0, DFU_MAGIC_SKIP) == NRF_SUCCESS)) { + NRF_POWER->GPREGRET = DFU_MAGIC_SKIP; + } // FIXME, use system off mode with ram retention for key state? // FIXME, use non-init RAM per @@ -451,6 +466,13 @@ void cpuDeepSleep(uint32_t msecToWake) ; #endif +#ifndef DMESHTASTIC_EXCLUDE_DETECTIONSENSOR + // enforce the rules of message_rate_limit of the detectionSensorModule. simply by delaying deep sleep + // in a low power mode delay + if (detectionSensorModule != nullptr && detectionSensorModule->shouldLoop()) + detectionSensorModule->lpDelay(); +#endif + auto ok = sd_power_system_off(); if (ok != NRF_SUCCESS) { LOG_ERROR("FIXME: Ignoring soft device (EasyDMA pending?) and forcing system-off!");