lots of changes:

* preflightSleep, notifySleep, notifyDeepSleep now allow arbitrary
drivers/devices/software to register for sleep notification.
* Use the proceeding to clean up MeshRadio - now the mesh radio is more
like an independent driver that doesn't care so much about other systems
* clean up MeshService so that it can work with zero MeshRadios added.
This is a prelude to supporting boards with multiple interfaces (wifi,
extra LORA radios etc) and allows development/testing in sim with a bare
ESP32 board
* Remove remaining ESP32 dependencies from the bare simulation target
this allows running on anything that implements the arduino API
This commit is contained in:
geeksville 2020-04-14 11:40:49 -07:00
parent ac7f3cd603
commit 4757b6807e
9 changed files with 147 additions and 65 deletions

View File

@ -5,8 +5,10 @@
#include <assert.h>
#include "MeshRadio.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "configuration.h"
#include "sleep.h"
#include <pb_decode.h>
#include <pb_encode.h>
@ -25,7 +27,7 @@ separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts
bool useHardware = true;
MeshRadio::MeshRadio(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &_rxDest)
: radioIf(_pool, _rxDest) // , manager(radioIf)
: radioIf(_pool, _rxDest), sendPacketObserver(this, &MeshRadio::send) // , manager(radioIf)
{
myNodeInfo.num_channels = NUM_CHANNELS;
@ -40,6 +42,11 @@ bool MeshRadio::init()
DEBUG_MSG("Starting meshradio init...\n");
configChangedObserver.observe(&service.configChanged);
sendPacketObserver.observe(&service.sendViaRadio);
preflightSleepObserver.observe(&preflightSleep);
notifyDeepSleepObserver.observe(&notifyDeepSleep);
#ifdef RESET_GPIO
pinMode(RESET_GPIO, OUTPUT); // Deassert reset
digitalWrite(RESET_GPIO, HIGH);
@ -84,7 +91,7 @@ unsigned long hash(char *str)
return hash;
}
void MeshRadio::reloadConfig()
int MeshRadio::reloadConfig(void *unused)
{
radioIf.setModeIdle(); // Need to be idle before doing init
@ -116,17 +123,21 @@ void MeshRadio::reloadConfig()
// Done with init tell radio to start receiving
radioIf.setModeRx();
return 0;
}
ErrorCode MeshRadio::send(MeshPacket *p)
int MeshRadio::send(MeshPacket *p)
{
lastTxStart = millis();
if (useHardware)
return radioIf.send(p);
else {
radioIf.pool.release(p);
return ERRNO_OK;
if (useHardware) {
radioIf.send(p);
// Note: we ignore the error code, because no matter what the interface has already freed the packet.
return 1; // Indicate success - stop offering this packet to radios
} else {
// fail
return 0;
}
}

View File

@ -3,6 +3,7 @@
#include "CustomRF95.h"
#include "MemoryPool.h"
#include "MeshTypes.h"
#include "Observer.h"
#include "PointerQueue.h"
#include "configuration.h"
#include "mesh.pb.h"
@ -80,22 +81,42 @@ class MeshRadio
bool init();
/// Send a packet (possibly by enquing in a private fifo). This routine will
/// later free() the packet to pool. This routine is not allowed to stall because it is called from
/// bluetooth comms code. If the txmit queue is empty it might return an error
ErrorCode send(MeshPacket *p);
/// Do loop callback operations (we currently FIXME poll the receive mailbox here)
/// for received packets it will call the rx handler
void loop();
/// The radioConfig object just changed, call this to force the hw to change to the new settings
void reloadConfig();
private:
// RHReliableDatagram manager; // don't use mesh yet
// RHMesh manager;
/// Used for the tx timer watchdog, to check for bugs in our transmit code, msec of last time we did a send
uint32_t lastTxStart = 0;
CallbackObserver<MeshRadio, void *> configChangedObserver =
CallbackObserver<MeshRadio, void *>(this, &MeshRadio::reloadConfig);
CallbackObserver<MeshRadio, void *> preflightSleepObserver =
CallbackObserver<MeshRadio, void *>(this, &MeshRadio::preflightSleepCb);
CallbackObserver<MeshRadio, void *> notifyDeepSleepObserver =
CallbackObserver<MeshRadio, void *>(this, &MeshRadio::notifyDeepSleepDb);
CallbackObserver<MeshRadio, MeshPacket *> sendPacketObserver; /* =
CallbackObserver<MeshRadio, MeshPacket *>(this, &MeshRadio::send); */
/// Send a packet (possibly by enquing in a private fifo). This routine will
/// later free() the packet to pool. This routine is not allowed to stall because it is called from
/// bluetooth comms code. If the txmit queue is empty it might return an error.
///
/// Returns 1 for success or 0 for failure (and if we fail it is the _callers_ responsibility to free the packet)
int send(MeshPacket *p);
/// The radioConfig object just changed, call this to force the hw to change to the new settings
int reloadConfig(void *unused = NULL);
/// Return 0 if sleep is okay
int preflightSleepCb(void *unused = NULL) { return radioIf.canSleep() ? 0 : 1; }
int notifyDeepSleepDb(void *unused = NULL)
{
radioIf.sleep();
return 0;
}
};

View File

@ -51,8 +51,7 @@ MeshService service;
#define MAX_RX_FROMRADIO \
4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big
MeshService::MeshService()
: packetPool(MAX_PACKETS), toPhoneQueue(MAX_RX_TOPHONE), fromRadioQueue(MAX_RX_FROMRADIO), radio(packetPool, fromRadioQueue)
MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE), packetPool(MAX_PACKETS), fromRadioQueue(MAX_RX_FROMRADIO)
{
// assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro
}
@ -61,9 +60,6 @@ void MeshService::init()
{
nodeDB.init();
if (!radio.init())
DEBUG_MSG("radio init failed\n");
gpsObserver.observe(&gps);
// No need to call this here, our periodic task will fire quite soon
@ -205,8 +201,6 @@ Periodic sendOwnerPeriod(sendOwnerCb);
/// Do idle processing (mostly processing messages which have been queued from the radio)
void MeshService::loop()
{
radio.loop(); // FIXME, possibly move radio interaction to own thread
handleFromRadio();
// occasionally send our owner info
@ -218,7 +212,7 @@ void MeshService::reloadConfig()
{
// If we can successfully set this radio to these settings, save them to disk
nodeDB.resetRadioConfig(); // Don't let the phone send us fatally bad settings
radio.reloadConfig();
configChanged.notifyObservers(NULL);
nodeDB.saveToDisk();
}
@ -276,8 +270,12 @@ void MeshService::sendToMesh(MeshPacket *p)
DEBUG_MSG("Dropping locally processed message\n");
else {
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
if (radio.send(p) != ERRNO_OK)
DEBUG_MSG("Dropped packet because send queue was full!\n");
int didSend = sendViaRadio.notifyObservers(p);
if (!didSend) {
DEBUG_MSG("No radio was able to send packet, discarding...");
releaseToPool(p);
}
}
}

View File

@ -17,26 +17,32 @@ class MeshService
{
CallbackObserver<MeshService, void *> gpsObserver = CallbackObserver<MeshService, void *>(this, &MeshService::onGPSChanged);
MemoryPool<MeshPacket> packetPool;
/// received packets waiting for the phone to process them
/// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure
/// we never hang because android hasn't been there in a while
/// FIXME - save this to flash on deep sleep
PointerQueue<MeshPacket> toPhoneQueue;
/// Packets which have just arrived from the radio, ready to be processed by this service and possibly
/// forwarded to the phone.
PointerQueue<MeshPacket> fromRadioQueue;
/// The current nonce for the newest packet which has been queued for the phone
uint32_t fromNum = 0;
public:
MeshRadio radio;
MemoryPool<MeshPacket> packetPool;
/// Packets which have just arrived from the radio, ready to be processed by this service and possibly
/// forwarded to the phone.
PointerQueue<MeshPacket> fromRadioQueue;
/// Called when some new packets have arrived from one of the radios
Observable<uint32_t> fromNumChanged;
/// Called when radio config has changed (radios should observe this and set their hardware as required)
Observable<void *> configChanged;
/// Radios should observe this and return 0 if they were unable to process the packet or 1 if they were (and therefore it
/// should not be offered to other radios)
Observable<MeshPacket *> sendViaRadio;
MeshService();
void init();

View File

@ -31,22 +31,6 @@ static void lsEnter()
DEBUG_MSG("lsEnter begin, ls_secs=%u\n", radioConfig.preferences.ls_secs);
screen.setOn(false);
uint32_t now = millis();
while (!service.radio.radioIf.canSleep()) {
delay(10); // 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
recordCriticalError(ErrSleepEnterWait);
break;
}
}
gps.prepareSleep(); // abandon in-process parsing
// if (!isUSBPowered) // FIXME - temp hack until we can put gps in sleep mode, if we have AC when we go to sleep then
// leave GPS on
// setGPSPower(false); // kill GPS power
DEBUG_MSG("lsEnter end\n");
}

View File

@ -1,7 +1,7 @@
#pragma once
/// Error codes for critical error
enum CriticalErrorCode { NoError, ErrTxWatchdog, ErrSleepEnterWait };
enum CriticalErrorCode { NoError, ErrTxWatchdog, ErrSleepEnterWait, ErrNoRadio };
/// Record an error that should be reported via analytics
void recordCriticalError(CriticalErrorCode code, uint32_t address = 0);

View File

@ -28,6 +28,7 @@
#include "Periodic.h"
#include "PowerFSM.h"
#include "configuration.h"
#include "error.h"
#include "esp32/pm.h"
#include "esp_pm.h"
#include "power.h"
@ -205,6 +206,8 @@ const char *getDeviceName()
return name;
}
static MeshRadio *radio = NULL;
void setup()
{
// Debug
@ -259,6 +262,14 @@ void setup()
service.init();
#ifndef NO_ESP32
// MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
radio = new MeshRadio(service.packetPool, service.fromRadioQueue);
#endif
if (radio && !radio->init())
recordCriticalError(ErrNoRadio);
// This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values
PowerFSM_setup(); // we will transition to ON in a couple of seconds, FIXME, only do this for cold boots, not waking from SDS
@ -307,6 +318,9 @@ void loop()
gps.loop();
service.loop();
if (radio)
radio->loop();
ledPeriodic.loop();
// axpDebugOutput.loop();
@ -315,7 +329,7 @@ void loop()
#endif
// for debug printing
// service.radio.radioIf.canSleep();
// radio.radioIf.canSleep();
#ifdef PMU_IRQ
if (pmu_irq) {

View File

@ -5,6 +5,7 @@
#include "NodeDB.h"
#include "Periodic.h"
#include "configuration.h"
#include "error.h"
#include "esp32/pm.h"
#include "esp_pm.h"
#include "main.h"
@ -22,6 +23,12 @@
extern AXP20X_Class axp;
#endif
/// Called to ask any observers if they want to veto sleep. Return 1 to veto or 0 to allow sleep to happen
Observable<void *> preflightSleep;
/// Called to tell observers we are now entering sleep and you should prepare. Must return 0
Observable<void *> notifySleep, notifyDeepSleep;
// deep sleep support
RTC_DATA_ATTR int bootCount = 0;
esp_sleep_source_t wakeCause; // the reason we booted this time
@ -101,22 +108,53 @@ void initDeepSleep()
DEBUG_MSG("booted, wake cause %d (boot count %d), reset_reason=%s\n", wakeCause, bootCount, reason);
}
/// return true if sleep is allowed
static bool doPreflightSleep()
{
if (preflightSleep.notifyObservers(NULL) != 0)
return false; // vetoed
else
return true;
}
/// Tell devices we are going to sleep and wait for them to handle things
static void waitEnterSleep()
{
/*
former hardwired code - now moved into notifySleep callbacks:
// Put radio in sleep mode (will still draw power but only 0.2uA)
service.radio.radioIf.sleep();
*/
uint32_t now = millis();
while (!doPreflightSleep()) {
delay(10); // 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
recordCriticalError(ErrSleepEnterWait);
break;
}
}
// Code that still needs to be moved into notifyObservers
Serial.flush(); // send all our characters before we stop cpu clock
setBluetoothEnable(false); // has to be off before calling light sleep
gps.prepareSleep(); // abandon in-process parsing
notifySleep.notifyObservers(NULL);
}
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();
#ifndef NO_ESP32
BLEDevice::deinit(false); // We are required to shutdown bluetooth before deep or light sleep
#endif
waitEnterSleep();
notifyDeepSleep.notifyObservers(NULL);
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.radioIf.sleep();
nodeDB.saveToDisk();
#ifdef RESET_OLED
@ -204,10 +242,10 @@ void doDeepSleep(uint64_t msecToWake)
esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more reasonable default
{
// DEBUG_MSG("Enter light sleep\n");
uint64_t sleepUsec = sleepMsec * 1000LL;
Serial.flush(); // send all our characters before we stop cpu clock
setBluetoothEnable(false); // has to be off before calling light sleep
waitEnterSleep();
uint64_t sleepUsec = sleepMsec * 1000LL;
// NOTE! ESP docs say we must disable bluetooth and wifi before light sleep

View File

@ -1,6 +1,7 @@
#pragma once
#include "Arduino.h"
#include "Observer.h"
#include "esp_sleep.h"
void doDeepSleep(uint64_t msecToWake);
@ -17,4 +18,13 @@ extern int bootCount;
extern esp_sleep_source_t wakeCause;
// is bluetooth sw currently running?
extern bool bluetoothOn;
extern bool bluetoothOn;
/// Called to ask any observers if they want to veto sleep. Return 1 to veto or 0 to allow sleep to happen
extern Observable<void *> preflightSleep;
/// Called to tell observers we are now entering (light or deep) sleep and you should prepare. Must return 0
extern Observable<void *> notifySleep;
/// Called to tell observers we are now entering (deep) sleep and you should prepare. Must return 0
extern Observable<void *> notifyDeepSleep;