WIP state machine builds

This commit is contained in:
geeksville 2020-02-22 12:01:59 -08:00
parent 045529d91f
commit 509f9b6e2b
10 changed files with 174 additions and 55 deletions

View File

@ -72,6 +72,7 @@ until the phone pulls those packets. Ever so often power on bluetooth just so w
Items after the first final candidate release.
* read the PMU battery fault indicators and blink/led/warn user on screen
* make a no bluetooth configured yet screen - include this screen in the loop if the user hasn't yet paired
* the AXP debug output says it is trying to charge at 700mA, but the max I've seen is 180mA, so AXP registers probably need to be set to tell them the circuit can only provide 300mAish max. So that the low charge rate kicks in faster and we don't wear out batteries.
* increase the max charging rate a bit for 18650s, currently it limits to 180mA (at 4V). Work backwards from the 500mA USB limit (at 5V) and let the AXP charge at that rate.

View File

@ -7,44 +7,50 @@ This is a mini design doc for various core behaviors...
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)
* 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
(Not currently used)
* 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.
onEntry: setBluetoothOn(false), setGPSPower(false), call doLightSleep
onExit: start trying to get gps lock: gps.startLock(), once lock arrives service.sendPosition(BROADCAST)
onEntry: setBluetoothOn(false), setGPSPower(false) - happens inside doLightSleep()
onIdle: (if we wake because our led blink timer has expired) blink the led then go back to sleep until we sleep for ls_secs
onExit: setGPSPower(true), 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)
onEntry: setBluetoothOn(false)
onExit:
* running dark (DARK) - Everything is on except screen
onEntry: setBluetoothOn(true)
onExit:
onExit:
* full on (ON) - Everything is on
onEntry: setBluetoothOn(true), screen.setOn(true)
onExit: screen.setOn(false)
## Behavior
### events that increase CPU activity
* At cold boot: The initial state (after setup() has run) is DARK
* While in DARK: if we receive EVENT_BOOT, transition to ON (and show the bootscreen). This event will be sent if we detect we woke due to reset (as opposed to deep sleep)
* 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.
* While in LS/NB/DARK: If the user presses a button we go to full ON mode for screen_on_secs (default 30 seconds). Multiple presses keeps resetting this timeout
* 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.
* While in LS/NB/DARK: If the user presses a button (EVENT_PRESS) we go to full ON mode for screen_on_secs (default 30 seconds). Multiple presses keeps resetting this timeout
* While in LS/NB/DARK: If we receive new text messages (EVENT_RECEIVED_TEXT_MSG), 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 (EVENT_RECEIVED_PACKET) we will wake and handle them and stay awake in NB mode for min_wake_secs (default 10 seconds)
* While in NB: If we do have packets the phone (EVENT_PACKETS_FOR_PHONE) would want we transition to DARK mode for wait_bluetooth secs.
### events that decrease cpu activity
* While in ON: If PRESS event occurs, reset screen_on_secs timer and tell the screen to handle the pess
* 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)
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 (default 1 hr) (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.

106
src/PowerFSM.cpp Normal file
View File

@ -0,0 +1,106 @@
#include "sleep.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "configuration.h"
#include "screen.h"
#include "PowerFSM.h"
static void sdsEnter()
{
/*
// Don't deepsleep if we have USB power or if the user as pressed a button recently
// !isUSBPowered <- doesn't work yet because the axp192 isn't letting the battery fully charge when we are awake - FIXME
if (millis() - lastPressMs > radioConfig.preferences.mesh_sds_timeout_secs)
{
doDeepSleep(radioConfig.preferences.sds_secs);
}
*/
doDeepSleep(radioConfig.preferences.sds_secs * 1000LL);
}
static void lsEnter()
{
/*
// while we have bluetooth on, we can't do light sleep, but once off stay in light_sleep all the time
// we will wake from light sleep on button press or interrupt from the RF95 radio
if (!bluetoothOn && !is_screen_on() && service.radio.rf95.canSleep() && gps.canSleep())
doLightSleep(radioConfig.preferences.ls_secs);
else
{
delay(msecstosleep);
} */
doLightSleep(radioConfig.preferences.ls_secs * 1000LL);
}
static void lsIdle()
{
// FIXME - blink led when we occasionally wake from timer, then go back to light sleep
}
static void lsExit()
{
// nothing
}
static void nbEnter()
{
setBluetoothEnable(false);
}
static void darkEnter()
{
DEBUG_MSG("screen timeout, turn it off for now...\n");
screen.setOn(true);
}
static void onEnter()
{
screen.setOn(true);
setBluetoothEnable(true);
}
static void onExit()
{
screen.setOn(false);
}
static void wakeForPing()
{
}
static void screenPress()
{
screen.onPress();
}
State stateSDS(sdsEnter, NULL, NULL);
State stateLS(lsEnter, lsIdle, lsExit);
State stateNB(nbEnter, NULL, NULL);
State stateDARK(darkEnter, NULL, NULL);
State stateON(onEnter, NULL, onExit);
Fsm powerFSM(&stateDARK);
void PowerFSM_setup()
{
powerFSM.add_transition(&stateDARK, &stateON, EVENT_BOOT, NULL);
powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WAKE_TIMER, wakeForPing);
powerFSM.add_transition(&stateLS, &stateNB, EVENT_RECEIVED_PACKET, NULL);
powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL);
powerFSM.add_transition(&stateNB, &stateON, EVENT_PRESS, NULL);
powerFSM.add_transition(&stateDARK, &stateON, EVENT_PRESS, NULL);
powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, screenPress); // reenter On to restart our timers
powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL);
powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL);
powerFSM.add_transition(&stateDARK, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL);
powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL);
powerFSM.add_timed_transition(&stateON, &stateDARK, radioConfig.preferences.screen_on_secs, NULL);
powerFSM.add_timed_transition(&stateDARK, &stateNB, radioConfig.preferences.phone_timeout_secs, NULL);
}

14
src/PowerFSM.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include "Fsm.h"
// See sw-design.md for documentation
#define EVENT_PRESS 1
#define EVENT_WAKE_TIMER 2
#define EVENT_RECEIVED_PACKET 3
#define EVENT_PACKET_FOR_PHONE 4
#define EVENT_RECEIVED_TEXT_MSG 5
#define EVENT_BOOT 6
extern Fsm powerFSM;

View File

@ -60,10 +60,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#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
#define MINWAKE_MSECS (60 * 60 * 1000) // stay awake a long time (30 mins) for debugging - FIXME, change per the TBD sleep rules doc
// #define MINWAKE_MSECS (30 * 1000) // Wait after every boot for GPS lock (may need longer than 5s because we turned the gps off during deep sleep)
// -----------------------------------------------------------------------------
// DEBUG

View File

@ -37,6 +37,7 @@
#include "esp_pm.h"
#include "MeshRadio.h"
#include "sleep.h"
#include "PowerFSM.h"
#ifdef T_BEAM_V10
#include "axp20x.h"
@ -337,7 +338,7 @@ void loop()
// if user presses button for more than 3 secs, discard our network prefs and reboot (FIXME, use a debounce lib instead of this boilerplate)
static bool wasPressed = false;
static uint32_t minPressMs; // what tick should we call this press long enough
static uint32_t lastPingMs, lastPressMs;
static uint32_t lastPingMs;
if (!digitalRead(BUTTON_PIN))
{
if (!wasPressed)
@ -349,7 +350,6 @@ void loop()
wasPressed = true;
uint32_t now = millis();
lastPressMs = now;
minPressMs = now + 3000;
if (now - lastPingMs > 60 * 1000)
@ -358,7 +358,7 @@ void loop()
lastPingMs = now;
}
screen_press();
powerFSM.trigger(EVENT_PRESS);
}
}
else if (wasPressed)
@ -375,15 +375,6 @@ void loop()
}
#endif
#ifdef MINWAKE_MSECS
// Don't deepsleep if we have USB power or if the user as pressed a button recently
// !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)
{
doDeepSleep(radioConfig.preferences.sds_secs);
}
#endif
// No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in)
// i.e. don't just keep spinning in loop as fast as we can.
//DEBUG_MSG("msecs %d\n", msecstosleep);
@ -391,12 +382,5 @@ void loop()
// FIXME - until button press handling is done by interrupt (see polling above) we can't sleep very long at all or buttons feel slow
msecstosleep = 10;
// while we have bluetooth on, we can't do light sleep, but once off stay in light_sleep all the time
// we will wake from light sleep on button press or interrupt from the RF95 radio
if (!bluetoothOn && !is_screen_on() && service.radio.rf95.canSleep() && gps.canSleep())
doLightSleep(60 * 1000); // FIXME, wake up to briefly flash led, then go back to sleep (without repowering bluetooth)
else
{
delay(msecstosleep);
}
delay(msecstosleep);
}

View File

@ -60,7 +60,7 @@ static bool showingBootScreen = true; // start by showing the bootscreen
uint32_t lastPressMs;
bool is_screen_on() { return screenOn; }
bool Screen::isOn() { return screenOn; }
void msOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
{
@ -491,22 +491,19 @@ void _screen_header()
}
#endif
void screen_off()
{
if (!disp)
return;
dispdev.displayOff();
screenOn = false;
}
void screen_on()
void Screen::setOn(bool on)
{
if (!disp)
return;
if(on != screenOn) {
if(on)
dispdev.displayOn();
screenOn = true;
else
dispdev.displayOff();
screenOn = on;
}
}
static void screen_print(const char *text, uint8_t x, uint8_t y, uint8_t alignment)
@ -579,7 +576,7 @@ void Screen::setup()
// Scroll buffer
dispdev.setLogBuffer(3, 32);
screen_on(); // update our screenOn bool
setOn(true); // update our screenOn bool
#ifdef BICOLOR_DISPLAY
dispdev.flipScreenVertically(); // looks better without this on lora32
@ -612,7 +609,7 @@ void Screen::doTask()
if (wakeScreen) // If a new text message arrived, turn the screen on immedately
{
lastPressMs = millis(); // if we were told to wake the screen, reset the press timeout
screen_on(); // make sure the screen is not asleep
screen.setOn(true); // make sure the screen is not asleep
wakeScreen = false;
}
@ -655,11 +652,12 @@ void Screen::doTask()
nodeDB.updateTextMessage = false;
}
/*
if (millis() - lastPressMs > SCREEN_SLEEP_MS)
{
DEBUG_MSG("screen timeout, turn it off for now...\n");
screen_off();
}
screen.setOn(false);
} */
}
}
@ -716,7 +714,7 @@ void screen_set_frames()
}
/// handle press of the button
void screen_press()
void Screen::onPress()
{
// screen_start_bluetooth(123456);

View File

@ -4,7 +4,6 @@
void screen_print(const char * text);
void screen_on(), screen_off(), screen_press();
// Show the bluetooth PIN screen
void screen_start_bluetooth(uint32_t pin);
@ -12,7 +11,6 @@ void screen_start_bluetooth(uint32_t pin);
// restore our regular frame list
void screen_set_frames();
bool is_screen_on();
/**
* Slowly I'm moving screen crap into this class
@ -27,6 +25,15 @@ public:
/// Turn on the screen asap
void doWakeScreen();
/// Is the screen currently on
bool isOn();
/// Turn the screen on/off
void setOn(bool on);
/// Handle a button press
void onPress();
};
extern Screen screen;

View File

@ -65,6 +65,8 @@ void setLed(bool ledOn)
void setGPSPower(bool on)
{
DEBUG_MSG("Setting GPS power=%d\n", on);
#ifdef T_BEAM_V10
if (axp192_found)
axp.setPowerOutPut(AXP192_LDO3, on ? AXP202_ON : AXP202_OFF); // GPS main power
@ -97,7 +99,7 @@ void doDeepSleep(uint64_t msecToWake)
BLEDevice::deinit(false); // We are required to shutdown bluetooth before deep or light sleep
screen_off(); // datasheet says this will draw only 10ua
screen.setOn(false); // datasheet says this will draw only 10ua
// Put radio in sleep mode (will still draw power but only 0.2uA)
service.radio.rf95.sleep();
@ -212,6 +214,7 @@ void doLightSleep(uint64_t sleepMsec) // FIXME, use a more reasonable default
setBluetoothEnable(false); // has to be off before calling light sleep
gps.prepareSleep(); // abandon in-process parsing
setGPSPower(false); // kill GPS power
setLed(false); // Never leave led on while in light sleep
// NOTE! ESP docs say we must disable bluetooth and wifi before light sleep
@ -228,6 +231,8 @@ void doLightSleep(uint64_t sleepMsec) // FIXME, use a more reasonable default
esp_sleep_enable_timer_wakeup(sleepUsec);
esp_light_sleep_start();
DEBUG_MSG("Exit light sleep\n");
setGPSPower(true); // restore GPS power
}
#if 0

View File

@ -1,5 +1,7 @@
#pragma once
#include "Arduino.h"
void doDeepSleep(uint64_t msecToWake);
void doLightSleep(uint64_t msecToWake);
void setBluetoothEnable(bool on);