diff --git a/TODO.md b/TODO.md index b63acadde..9f000ce10 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,8 @@ - # High priority Items to complete before the first alpha release. +* implement sleep state machine * have gps implement canSleep(), print nmea for debugging and discard buffers on the way into sleep * implement CustomRF95::canSleep * make gps prevent light sleep if we are waiting for data @@ -13,10 +13,9 @@ for it (because it will redownload the nodedb when it comes back) * don't enter light sleep while the screen is on * any time we wake from light sleep, briefly blink the led -* turn light sleep on agressively (while lora is on but BLE off) +* turn light sleep on aggressively (while lora is on but BLE off) * retest BLE software update for both board types * default to enter deep sleep if no LORA received for two hours (indicates user has probably left the meshS) -* article writeup for hackaday? * send note about Adafruit Clue * send note to the guy who designed the cases * update the prebuilt bins for different regulatory regions @@ -47,7 +46,8 @@ Items to complete before the first beta release. * How do avalanche beacons work? Could this do that as well? possibly by using beacon mode feature of the RF95? * use std::map in node db * make a HAM build: yep - that's a great idea. I'll add it to the TODO. should be pretty painless - just a new frequency list, a bool to say 'never do encryption' and use hte callsign as that node's unique id. -from Girts -* add frequency hopping +* add frequency hopping +* publish update articles on the web # Pre-beta priority diff --git a/docs/sw-design.md b/docs/sw-design.md index 5de6280ce..5c200fba1 100644 --- a/docs/sw-design.md +++ b/docs/sw-design.md @@ -2,21 +2,36 @@ This is a mini design doc for various core behaviors... # Rules for sleep -## Terms +## States From lower to higher power consumption. * Super-deep-sleep (SDS) - everything is off, CPU, radio, bluetooth, GPS. Only wakes due to timer or button press + * deep-sleep (DS) - CPU is off, radio is on, bluetooth and GPS is off. Note: This mode is never used currently, because it only saves 1.5mA vs light-sleep + onEntry: setBluetoothOn(false), call doDeepSleep + onExit: (standard bootup code, starts in DARK) + * light-sleep (LS) - CPU is suspended (RAM stays alive), radio is on, bluetooth is off, GPS is off. Note: currently GPS is not turned off during light sleep, but there is a TODO item to fix this. -* No bluetooth (NB) - CPU is running, radio is on, GPS is on but bluetooth is off, screen is off. Note: We might not need this mode -* running dark (DARK) - Everything is on except screen -* full on (ON) - Everything is on + onEntry: setBluetoothOn(false), setGPSPower(false), call doLightSleep + onExit: start trying to get gps lock: gps.startLock(), once lock arrives service.sendPosition(BROADCAST) +* No bluetooth (NB) - CPU is running, radio is on, GPS is on but bluetooth is off, screen is off. + onEntry: setGPSPower(true), setBluetoothOn(false) + onExit: + +* running dark (DARK) - Everything is on except screen + onEntry: setBluetoothOn(true) + onExit: + +* full on (ON) - Everything is on + onEntry: setBluetoothOn(true), screen.setOn(true) + onExit: screen.setOn(false) + ## Behavior -### things that increase CPU activity +### events that increase CPU activity * While in LS: Once every position_broadcast_secs (default 15 mins) - the unit will wake into DARK mode and broadcast a "networkPing" (our position) and stay alive for wait_bluetooth_secs (default 30 seconds). This allows other nodes to have a record of our last known position if we go away and allows a paired phone to hear from us and download messages. * While in LS: Every send_owner_interval (defaults to 4, i.e. one hour), when we wake to send our position we _also_ broadcast our owner. This lets new nodes on the network find out about us or correct duplicate node number assignments. @@ -24,10 +39,11 @@ off during light sleep, but there is a TODO item to fix this. * While in LS/NB/DARK: If we receive text messages, we go to full ON mode for screen_on_secs (same as if user pressed a button) * While in LS: if we receive packets on the radio we will wake and handle them and stay awake in NB mode for min_wake_secs (default 10 seconds) - if we don't have packets we need to deliver to our phone. If we do have packets the phone would want we instead stay in DARK mode for wait_bluetooth secs. -### Things that decrease cpu activity +### events that decrease cpu activity -* If time since last contact by our phone exceeds phone_timeout_secs (15 minutes) and we are in DARK mode, we transition down into NB mode -* If nothing above is forcing us to stay in a higher mode (wait_bluetooth_secs, screen_on_secs, or min_wake_secs) we will lower down +* While in ON: If it has been more than screen_on_secs since a press, lower to DARK +* While in DARK: If time since last contact by our phone exceeds phone_timeout_secs (15 minutes), we transition down into NB mode +* While in DARK or NB: If nothing above is forcing us to stay in a higher mode (wait_bluetooth_secs, min_wake_secs) we will lower down into either LS or SDS levels. If either phone_sds_timeout_secs (default 1 hr) or mesh_sds_timeout_secs (default 1 hr) are exceeded we will lower into SDS mode for sds_secs (or a button press). Otherwise we will lower into LS mode for ls_secs (default 1 hr) (or until an interrupt, button press) TODO: Eventually these scheduled intervals should be synchronized to the GPS clock, so that we can consider leaving the lora receiver off to save even more power. diff --git a/platformio.ini b/platformio.ini index 3a652f73a..245d71956 100644 --- a/platformio.ini +++ b/platformio.ini @@ -64,6 +64,7 @@ lib_deps = OneButton CRC32 ; explicitly needed because dependency is missing in the ble ota update lib Wire ; explicitly needed here because the AXP202 library forgets to add it + https://github.com/geeksville/arduino-fsm.git ;[env:tbeam] ;board = ttgo-t-beam diff --git a/src/MeshService.cpp b/src/MeshService.cpp index 7b321d7a5..63fcbd482 100644 --- a/src/MeshService.cpp +++ b/src/MeshService.cpp @@ -173,7 +173,7 @@ uint32_t sendOwnerCb() { service.sendOurOwner(); - return radioConfig.preferences.send_owner_secs * 1000; + return radioConfig.preferences.send_owner_interval * radioConfig.preferences.position_broadcast_secs * 1000; } Periodic sendOwnerPeriod(sendOwnerCb); diff --git a/src/NodeDB.cpp b/src/NodeDB.cpp index c040c0e06..84b3566cc 100644 --- a/src/NodeDB.cpp +++ b/src/NodeDB.cpp @@ -49,8 +49,13 @@ void NodeDB::init() devicestate.node_db_count = 0; devicestate.receive_queue_count = 0; - radioConfig.preferences.send_owner_secs = 60 * 60; // default to once an hour - radioConfig.preferences.position_broadcast_secs = 15 * 60; // default to once every 15 mins + radioConfig.preferences.send_owner_interval = 4; // per sw-design.md + radioConfig.preferences.position_broadcast_secs = 20; // 15 * 60; + radioConfig.preferences.wait_bluetooth_secs = 10; // 30; + radioConfig.preferences.screen_on_secs = 30; + radioConfig.preferences.mesh_sds_timeout_secs = 60 * 60; + radioConfig.preferences.phone_sds_timeout_sec = 60 * 60; + radioConfig.preferences.sds_secs = 60 * 60; #ifdef GPS_RX_PIN // some hardware defaults to have a built in GPS @@ -244,7 +249,8 @@ void NodeDB::updateFrom(const MeshPacket &mp) switch (p.which_variant) { - case SubPacket_position_tag: { + case SubPacket_position_tag: + { // we carefully preserve the old time, because we always trust our local timestamps more uint32_t oldtime = info->position.time; info->position = p.variant.position; diff --git a/src/configuration.h b/src/configuration.h index 98e7731a1..7bf206db8 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -57,11 +57,7 @@ along with this program. If not, see . #define DEBUG_PORT Serial // Serial debug port #define SERIAL_BAUD 115200 // Serial debug baud rate -#define SLEEP_MSECS (30 * 24 * 60 * 60 * 1000LL) // Sleep for these many millis (or a button press or a lora msg?) -#define MESSAGE_TO_SLEEP_DELAY 5000 // Time after message before going to sleep - -#define LOGO_DELAY 2000 // Time to show logo on first boot #define REQUIRE_RADIO true // If true, we will fail to start if the radio is not found // If not defined, we will wait for lock forever diff --git a/src/main.h b/src/main.h new file mode 100644 index 000000000..00e286557 --- /dev/null +++ b/src/main.h @@ -0,0 +1,4 @@ +#pragma once + +extern bool axp192_found; +extern bool ssd1306_found; \ No newline at end of file diff --git a/src/main.ino b/src/main.ino index 8cb2953da..ad7790799 100644 --- a/src/main.ino +++ b/src/main.ino @@ -36,6 +36,7 @@ #include "esp32/pm.h" #include "esp_pm.h" #include "MeshRadio.h" +#include "sleep.h" #ifdef T_BEAM_V10 #include "axp20x.h" @@ -48,11 +49,6 @@ bool isUSBPowered = false; bool ssd1306_found = false; bool axp192_found = false; -bool packetSent, packetQueued; - -// deep sleep support -RTC_DATA_ATTR int bootCount = 0; -esp_sleep_source_t wakeCause; // the reason we booted this time #define xstr(s) str(s) #define str(s) #s @@ -61,224 +57,6 @@ esp_sleep_source_t wakeCause; // the reason we booted this time // Application // ----------------------------------------------------------------------------- -/** - * Control CPU core speed (80MHz vs 240MHz) - * - * We leave CPU at full speed during init, but once loop is called switch to low speed (for a 50% power savings) - * - */ -void setCPUFast(bool on) -{ - setCpuFrequencyMhz(on ? 240 : 80); -} - -static void setLed(bool ledOn) -{ -#ifdef LED_PIN - // toggle the led so we can get some rough sense of how often loop is pausing - digitalWrite(LED_PIN, ledOn); -#endif - -#ifdef T_BEAM_V10 - if (axp192_found) - { - // blink the axp led - axp.setChgLEDMode(ledOn ? AXP20X_LED_LOW_LEVEL : AXP20X_LED_OFF); - } -#endif -} - -void setGPSPower(bool on) -{ -#ifdef T_BEAM_V10 - if (axp192_found) - axp.setPowerOutPut(AXP192_LDO3, on ? AXP202_ON : AXP202_OFF); // GPS main power -#endif -} - -void doDeepSleep(uint64_t msecToWake) -{ - DEBUG_MSG("Entering deep sleep for %llu seconds\n", msecToWake / 1000); - - // not using wifi yet, but once we are this is needed to shutoff the radio hw - // esp_wifi_stop(); - - BLEDevice::deinit(false); // We are required to shutdown bluetooth before deep or light sleep - - screen_off(); // datasheet says this will draw only 10ua - - // Put radio in sleep mode (will still draw power but only 0.2uA) - service.radio.rf95.sleep(); - - nodeDB.saveToDisk(); - -#ifdef RESET_OLED - digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power -#endif - -#ifdef VEXT_ENABLE - digitalWrite(VEXT_ENABLE, 1); // turn off the display power -#endif - - setLed(false); - -#ifdef T_BEAM_V10 - if (axp192_found) - { - // No need to turn this off if the power draw in sleep mode really is just 0.2uA and turning it off would - // leave floating input for the IRQ line - - // If we want to leave the radio receving in would be 11.5mA current draw, but most of the time it is just waiting - // in its sequencer (true?) so the average power draw should be much lower even if we were listinging for packets - // all the time. - - // axp.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // LORA radio - - setGPSPower(false); - } -#endif - - /* - Some ESP32 IOs have internal pullups or pulldowns, which are enabled by default. - If an external circuit drives this pin in deep sleep mode, current consumption may - increase due to current flowing through these pullups and pulldowns. - - To isolate a pin, preventing extra current draw, call rtc_gpio_isolate() function. - For example, on ESP32-WROVER module, GPIO12 is pulled up externally. - GPIO12 also has an internal pulldown in the ESP32 chip. This means that in deep sleep, - some current will flow through these external and internal resistors, increasing deep - sleep current above the minimal possible value. - - Note: we don't isolate pins that are used for the LORA, LED, i2c, spi or the wake button - */ - static const uint8_t rtcGpios[] = {/* 0, */ 2, - /* 4, */ -#ifndef USE_JTAG - 12, 13, /* 14, */ /* 15, */ -#endif - /* 25, */ 26, /* 27, */ - 32, 33, 34, 35, 36, 37, /* 38, */ 39}; - - for (int i = 0; i < sizeof(rtcGpios); i++) - rtc_gpio_isolate((gpio_num_t)rtcGpios[i]); - - // 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. - - // We want RTC peripherals to stay on - 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. - uint64_t gpioMask = (1ULL << BUTTON_PIN); - - // 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); - - esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW); -#endif - - esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs - esp_deep_sleep_start(); // TBD mA sleep current (battery) -} - -#include "esp_bt_main.h" - -bool bluetoothOn = true; // we turn it on during setup() so default on - -void setBluetoothEnable(bool on) -{ - if (on != bluetoothOn) - { - DEBUG_MSG("Setting bluetooth enable=%d\n", on); - - bluetoothOn = on; - if (on) - { - if (esp_bt_controller_enable(ESP_BT_MODE_BTDM) != ESP_OK) - DEBUG_MSG("error reenabling bt controller\n"); - if (esp_bluedroid_enable() != ESP_OK) - DEBUG_MSG("error reenabling bluedroid\n"); - } - else - { - if (esp_bluedroid_disable() != ESP_OK) - DEBUG_MSG("error disabling bluedroid\n"); - if (esp_bt_controller_disable() != ESP_OK) - DEBUG_MSG("error disabling bt controller\n"); - } - } -} - -/** - * enter light sleep (preserves ram but stops everything about CPU). - * - * Returns (after restoring hw state) when the user presses a button or we get a LoRa interrupt - */ -void doLightSleep(uint32_t sleepMsec = 20 * 1000) // FIXME, use a more reasonable default -{ - DEBUG_MSG("Enter light sleep\n"); - uint64_t sleepUsec = sleepMsec * 1000LL; - - gps.prepareSleep(); // abandon in-process parsing - setLed(false); // Never leave led on while in light sleep - - // NOTE! ESP docs say we must disable bluetooth and wifi before light sleep - - // We want RTC peripherals to stay on - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - - gpio_wakeup_enable((gpio_num_t)BUTTON_PIN, GPIO_INTR_LOW_LEVEL); // when user presses, this button goes low - gpio_wakeup_enable((gpio_num_t)DIO0_GPIO, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high -#ifdef PMU_IRQ - gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_HIGH_LEVEL); // pmu irq -#endif - esp_sleep_enable_gpio_wakeup(); - esp_sleep_enable_timer_wakeup(sleepUsec); - esp_light_sleep_start(); - DEBUG_MSG("Exit light sleep\n"); -} - -/** - * enable modem sleep mode as needed and available. Should lower our CPU current draw to an average of about 20mA. - * - * per https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/power_management.html - * - * supposedly according to https://github.com/espressif/arduino-esp32/issues/475 this is already done in arduino - */ -void enableModemSleep() -{ - static esp_pm_config_esp32_t config; // filled with zeros because bss - - config.max_freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ; - config.min_freq_mhz = 10; // 10Mhz is minimum recommended - config.light_sleep_enable = false; - DEBUG_MSG("Sleep request result %x\n", esp_pm_configure(&config)); -} - -void sleep() -{ -#ifdef SLEEP_MSECS - - // If the user has a screen, tell them we are about to sleep - if (ssd1306_found) - { - // Show the going to sleep message on the screen - char buffer[20]; - snprintf(buffer, sizeof(buffer), "Sleeping in %3.1fs\n", (MESSAGE_TO_SLEEP_DELAY / 1000.0)); - screen_print(buffer); - - // Wait for MESSAGE_TO_SLEEP_DELAY millis to sleep - delay(MESSAGE_TO_SLEEP_DELAY); - } - - // We sleep for the interval between messages minus the current millis - // this way we distribute the messages evenly every SEND_INTERVAL millis - doDeepSleep(SLEEP_MSECS); - -#endif -} void scanI2Cdevice(void) { @@ -409,21 +187,6 @@ void axp192Init() #endif } -// Perform power on init that we do on each wake from deep sleep -void initDeepSleep() -{ - bootCount++; - wakeCause = esp_sleep_get_wakeup_cause(); - /* - Not using yet because we are using wake on all buttons being low - - wakeButtons = esp_sleep_get_ext1_wakeup_status(); // If one of these buttons is set it was the reason we woke - if (wakeCause == ESP_SLEEP_WAKEUP_EXT1 && !wakeButtons) // we must have been using the 'all buttons rule for waking' to support busted boards, assume button one was pressed - wakeButtons = ((uint64_t)1) << buttons.gpios[0]; - */ - - DEBUG_MSG("booted, wake cause %d (boot count %d)\n", wakeCause, bootCount); -} const char *getDeviceName() { @@ -617,7 +380,7 @@ void loop() // !isUSBPowered <- doesn't work yet because the axp192 isn't letting the battery fully charge when we are awake - FIXME if (millis() - lastPressMs > MINWAKE_MSECS) { - sleep(); + doDeepSleep(radioConfig.preferences.sds_secs); } #endif diff --git a/src/mesh.pb.h b/src/mesh.pb.h index 58c583269..df1f0f266 100644 --- a/src/mesh.pb.h +++ b/src/mesh.pb.h @@ -33,8 +33,8 @@ typedef enum _ChannelSettings_ModemConfig { typedef enum _DeviceState_Version { DeviceState_Version_Unset = 0, - DeviceState_Version_Minimum = 15, - DeviceState_Version_Current = 15 + DeviceState_Version_Minimum = 16, + DeviceState_Version_Current = 16 } DeviceState_Version; /* Struct definitions */ @@ -69,8 +69,15 @@ typedef struct _Position { typedef struct _RadioConfig_UserPreferences { uint32_t position_broadcast_secs; - uint32_t send_owner_secs; + uint32_t send_owner_interval; uint32_t num_missed_to_fail; + uint32_t wait_bluetooth_secs; + uint32_t screen_on_secs; + uint32_t phone_timeout_secs; + uint32_t phone_sds_timeout_sec; + uint32_t mesh_sds_timeout_secs; + uint32_t sds_secs; + uint32_t ls_secs; bool keep_all_packets; bool promiscuous_mode; } RadioConfig_UserPreferences; @@ -175,7 +182,7 @@ typedef struct _ToRadio { #define MeshPacket_init_default {0, 0, false, SubPacket_init_default, 0} #define ChannelSettings_init_default {0, 0, _ChannelSettings_ModemConfig_MIN, {0}, ""} #define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default} -#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0} +#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0} #define MyNodeInfo_init_default {0, 0, 0} #define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default}, _DeviceState_Version_MIN, false, MeshPacket_init_default} @@ -188,7 +195,7 @@ typedef struct _ToRadio { #define MeshPacket_init_zero {0, 0, false, SubPacket_init_zero, 0} #define ChannelSettings_init_zero {0, 0, _ChannelSettings_ModemConfig_MIN, {0}, ""} #define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero} -#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0} +#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0} #define MyNodeInfo_init_zero {0, 0, 0} #define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero}, _DeviceState_Version_MIN, false, MeshPacket_init_zero} @@ -213,8 +220,15 @@ typedef struct _ToRadio { #define Position_from_hardware_tag 5 #define Position_time_tag 6 #define RadioConfig_UserPreferences_position_broadcast_secs_tag 1 -#define RadioConfig_UserPreferences_send_owner_secs_tag 2 +#define RadioConfig_UserPreferences_send_owner_interval_tag 2 #define RadioConfig_UserPreferences_num_missed_to_fail_tag 3 +#define RadioConfig_UserPreferences_wait_bluetooth_secs_tag 4 +#define RadioConfig_UserPreferences_screen_on_secs_tag 5 +#define RadioConfig_UserPreferences_phone_timeout_secs_tag 6 +#define RadioConfig_UserPreferences_phone_sds_timeout_sec_tag 7 +#define RadioConfig_UserPreferences_mesh_sds_timeout_secs_tag 8 +#define RadioConfig_UserPreferences_sds_secs_tag 9 +#define RadioConfig_UserPreferences_ls_secs_tag 10 #define RadioConfig_UserPreferences_keep_all_packets_tag 100 #define RadioConfig_UserPreferences_promiscuous_mode_tag 101 #define User_id_tag 1 @@ -311,8 +325,15 @@ X(a, STATIC, OPTIONAL, MESSAGE, channel_settings, 2) #define RadioConfig_UserPreferences_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, position_broadcast_secs, 1) \ -X(a, STATIC, SINGULAR, UINT32, send_owner_secs, 2) \ +X(a, STATIC, SINGULAR, UINT32, send_owner_interval, 2) \ X(a, STATIC, SINGULAR, UINT32, num_missed_to_fail, 3) \ +X(a, STATIC, SINGULAR, UINT32, wait_bluetooth_secs, 4) \ +X(a, STATIC, SINGULAR, UINT32, screen_on_secs, 5) \ +X(a, STATIC, SINGULAR, UINT32, phone_timeout_secs, 6) \ +X(a, STATIC, SINGULAR, UINT32, phone_sds_timeout_sec, 7) \ +X(a, STATIC, SINGULAR, UINT32, mesh_sds_timeout_secs, 8) \ +X(a, STATIC, SINGULAR, UINT32, sds_secs, 9) \ +X(a, STATIC, SINGULAR, UINT32, ls_secs, 10) \ X(a, STATIC, SINGULAR, BOOL, keep_all_packets, 100) \ X(a, STATIC, SINGULAR, BOOL, promiscuous_mode, 101) #define RadioConfig_UserPreferences_CALLBACK NULL @@ -402,11 +423,11 @@ extern const pb_msgdesc_t ToRadio_msg; #define SubPacket_size 261 #define MeshPacket_size 292 #define ChannelSettings_size 50 -#define RadioConfig_size 78 -#define RadioConfig_UserPreferences_size 24 +#define RadioConfig_size 120 +#define RadioConfig_UserPreferences_size 66 #define NodeInfo_size 157 #define MyNodeInfo_size 24 -#define DeviceState_size 15037 +#define DeviceState_size 15079 #define FromRadio_size 301 #define ToRadio_size 295 diff --git a/src/sleep.cpp b/src/sleep.cpp new file mode 100644 index 000000000..f07a986bd --- /dev/null +++ b/src/sleep.cpp @@ -0,0 +1,252 @@ +#include "configuration.h" +#include "rom/rtc.h" +#include +#include +#include +#include "BluetoothUtil.h" +#include "MeshBluetoothService.h" +#include "MeshService.h" +#include "GPS.h" +#include "screen.h" +#include "NodeDB.h" +#include "Periodic.h" +#include "esp32/pm.h" +#include "esp_pm.h" +#include "MeshRadio.h" +#include "main.h" + +#ifdef T_BEAM_V10 +#include "axp20x.h" +extern AXP20X_Class axp; +#endif + +// deep sleep support +RTC_DATA_ATTR int bootCount = 0; +esp_sleep_source_t wakeCause; // the reason we booted this time + +#define xstr(s) str(s) +#define str(s) #s + +#include "esp_bt_main.h" + +bool bluetoothOn = true; // we turn it on during setup() so default on + + +// ----------------------------------------------------------------------------- +// Application +// ----------------------------------------------------------------------------- + +/** + * Control CPU core speed (80MHz vs 240MHz) + * + * We leave CPU at full speed during init, but once loop is called switch to low speed (for a 50% power savings) + * + */ +void setCPUFast(bool on) +{ + setCpuFrequencyMhz(on ? 240 : 80); +} + +void setLed(bool ledOn) +{ +#ifdef LED_PIN + // toggle the led so we can get some rough sense of how often loop is pausing + digitalWrite(LED_PIN, ledOn); +#endif + +#ifdef T_BEAM_V10 + if (axp192_found) + { + // blink the axp led + axp.setChgLEDMode(ledOn ? AXP20X_LED_LOW_LEVEL : AXP20X_LED_OFF); + } +#endif +} + +void setGPSPower(bool on) +{ +#ifdef T_BEAM_V10 + if (axp192_found) + axp.setPowerOutPut(AXP192_LDO3, on ? AXP202_ON : AXP202_OFF); // GPS main power +#endif +} + +// Perform power on init that we do on each wake from deep sleep +void initDeepSleep() +{ + bootCount++; + wakeCause = esp_sleep_get_wakeup_cause(); + /* + Not using yet because we are using wake on all buttons being low + + wakeButtons = esp_sleep_get_ext1_wakeup_status(); // If one of these buttons is set it was the reason we woke + if (wakeCause == ESP_SLEEP_WAKEUP_EXT1 && !wakeButtons) // we must have been using the 'all buttons rule for waking' to support busted boards, assume button one was pressed + wakeButtons = ((uint64_t)1) << buttons.gpios[0]; + */ + + DEBUG_MSG("booted, wake cause %d (boot count %d)\n", wakeCause, bootCount); +} + + +void doDeepSleep(uint64_t msecToWake) +{ + DEBUG_MSG("Entering deep sleep for %llu seconds\n", msecToWake / 1000); + + // not using wifi yet, but once we are this is needed to shutoff the radio hw + // esp_wifi_stop(); + + BLEDevice::deinit(false); // We are required to shutdown bluetooth before deep or light sleep + + screen_off(); // datasheet says this will draw only 10ua + + // Put radio in sleep mode (will still draw power but only 0.2uA) + service.radio.rf95.sleep(); + + nodeDB.saveToDisk(); + +#ifdef RESET_OLED + digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power +#endif + +#ifdef VEXT_ENABLE + digitalWrite(VEXT_ENABLE, 1); // turn off the display power +#endif + + setLed(false); + +#ifdef T_BEAM_V10 + if (axp192_found) + { + // No need to turn this off if the power draw in sleep mode really is just 0.2uA and turning it off would + // leave floating input for the IRQ line + + // If we want to leave the radio receving in would be 11.5mA current draw, but most of the time it is just waiting + // in its sequencer (true?) so the average power draw should be much lower even if we were listinging for packets + // all the time. + + // axp.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // LORA radio + + setGPSPower(false); + } +#endif + + /* + Some ESP32 IOs have internal pullups or pulldowns, which are enabled by default. + If an external circuit drives this pin in deep sleep mode, current consumption may + increase due to current flowing through these pullups and pulldowns. + + To isolate a pin, preventing extra current draw, call rtc_gpio_isolate() function. + For example, on ESP32-WROVER module, GPIO12 is pulled up externally. + GPIO12 also has an internal pulldown in the ESP32 chip. This means that in deep sleep, + some current will flow through these external and internal resistors, increasing deep + sleep current above the minimal possible value. + + Note: we don't isolate pins that are used for the LORA, LED, i2c, spi or the wake button + */ + static const uint8_t rtcGpios[] = {/* 0, */ 2, + /* 4, */ +#ifndef USE_JTAG + 12, 13, /* 14, */ /* 15, */ +#endif + /* 25, */ 26, /* 27, */ + 32, 33, 34, 35, 36, 37, /* 38, */ 39}; + + for (int i = 0; i < sizeof(rtcGpios); i++) + rtc_gpio_isolate((gpio_num_t)rtcGpios[i]); + + // 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. + + // We want RTC peripherals to stay on + 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. + uint64_t gpioMask = (1ULL << BUTTON_PIN); + + // 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); + + esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW); +#endif + + esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs + esp_deep_sleep_start(); // TBD mA sleep current (battery) +} + + +void setBluetoothEnable(bool on) +{ + if (on != bluetoothOn) + { + DEBUG_MSG("Setting bluetooth enable=%d\n", on); + + bluetoothOn = on; + if (on) + { + if (esp_bt_controller_enable(ESP_BT_MODE_BTDM) != ESP_OK) + DEBUG_MSG("error reenabling bt controller\n"); + if (esp_bluedroid_enable() != ESP_OK) + DEBUG_MSG("error reenabling bluedroid\n"); + } + else + { + if (esp_bluedroid_disable() != ESP_OK) + DEBUG_MSG("error disabling bluedroid\n"); + if (esp_bt_controller_disable() != ESP_OK) + DEBUG_MSG("error disabling bt controller\n"); + } + } +} + +/** + * enter light sleep (preserves ram but stops everything about CPU). + * + * Returns (after restoring hw state) when the user presses a button or we get a LoRa interrupt + */ +void doLightSleep(uint64_t sleepMsec) // FIXME, use a more reasonable default +{ + DEBUG_MSG("Enter light sleep\n"); + uint64_t sleepUsec = sleepMsec * 1000LL; + + setBluetoothEnable(false); // has to be off before calling light sleep + gps.prepareSleep(); // abandon in-process parsing + setLed(false); // Never leave led on while in light sleep + + // NOTE! ESP docs say we must disable bluetooth and wifi before light sleep + + // We want RTC peripherals to stay on + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + + gpio_wakeup_enable((gpio_num_t)BUTTON_PIN, GPIO_INTR_LOW_LEVEL); // when user presses, this button goes low + gpio_wakeup_enable((gpio_num_t)DIO0_GPIO, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high +#ifdef PMU_IRQ + gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_HIGH_LEVEL); // pmu irq +#endif + esp_sleep_enable_gpio_wakeup(); + esp_sleep_enable_timer_wakeup(sleepUsec); + esp_light_sleep_start(); + DEBUG_MSG("Exit light sleep\n"); +} + +#if 0 +// not legal on the stock android ESP build + +/** + * enable modem sleep mode as needed and available. Should lower our CPU current draw to an average of about 20mA. + * + * per https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/power_management.html + * + * supposedly according to https://github.com/espressif/arduino-esp32/issues/475 this is already done in arduino + */ +void enableModemSleep() +{ + static esp_pm_config_esp32_t config; // filled with zeros because bss + + config.max_freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ; + config.min_freq_mhz = 10; // 10Mhz is minimum recommended + config.light_sleep_enable = false; + DEBUG_MSG("Sleep request result %x\n", esp_pm_configure(&config)); +} +#endif \ No newline at end of file diff --git a/src/sleep.h b/src/sleep.h new file mode 100644 index 000000000..ae3d09f42 --- /dev/null +++ b/src/sleep.h @@ -0,0 +1,17 @@ +#pragma once + +void doDeepSleep(uint64_t msecToWake); +void doLightSleep(uint64_t msecToWake); +void setBluetoothEnable(bool on); + +// Perform power on init that we do on each wake from deep sleep +void initDeepSleep(); + +void setCPUFast(bool on); +void setLed(bool ledOn); + +extern int bootCount; +extern esp_sleep_source_t wakeCause; + +// is bluetooth sw currently running? +extern bool bluetoothOn; \ No newline at end of file