diff --git a/src/MeshRadio.cpp b/src/MeshRadio.cpp index 1f7d20d43..7950760bf 100644 --- a/src/MeshRadio.cpp +++ b/src/MeshRadio.cpp @@ -5,8 +5,10 @@ #include #include "MeshRadio.h" +#include "MeshService.h" #include "NodeDB.h" #include "configuration.h" +#include "sleep.h" #include #include @@ -25,7 +27,7 @@ separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts bool useHardware = true; MeshRadio::MeshRadio(MemoryPool &_pool, PointerQueue &_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(¬ifyDeepSleep); + #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; } } diff --git a/src/MeshRadio.h b/src/MeshRadio.h index efb9e4faa..4b4d3c7b6 100644 --- a/src/MeshRadio.h +++ b/src/MeshRadio.h @@ -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 configChangedObserver = + CallbackObserver(this, &MeshRadio::reloadConfig); + + CallbackObserver preflightSleepObserver = + CallbackObserver(this, &MeshRadio::preflightSleepCb); + + CallbackObserver notifyDeepSleepObserver = + CallbackObserver(this, &MeshRadio::notifyDeepSleepDb); + + CallbackObserver sendPacketObserver; /* = + CallbackObserver(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; + } }; diff --git a/src/MeshService.cpp b/src/MeshService.cpp index 38fdc3473..2af86b4c3 100644 --- a/src/MeshService.cpp +++ b/src/MeshService.cpp @@ -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); + } + } } diff --git a/src/MeshService.h b/src/MeshService.h index e8f8721eb..e9d4fe488 100644 --- a/src/MeshService.h +++ b/src/MeshService.h @@ -17,26 +17,32 @@ class MeshService { CallbackObserver gpsObserver = CallbackObserver(this, &MeshService::onGPSChanged); - MemoryPool 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 toPhoneQueue; - /// Packets which have just arrived from the radio, ready to be processed by this service and possibly - /// forwarded to the phone. - PointerQueue fromRadioQueue; - /// The current nonce for the newest packet which has been queued for the phone uint32_t fromNum = 0; public: - MeshRadio radio; + MemoryPool packetPool; + /// Packets which have just arrived from the radio, ready to be processed by this service and possibly + /// forwarded to the phone. + PointerQueue fromRadioQueue; + + /// Called when some new packets have arrived from one of the radios Observable fromNumChanged; + /// Called when radio config has changed (radios should observe this and set their hardware as required) + Observable 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 sendViaRadio; + MeshService(); void init(); diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index d484efe62..716e4e2c3 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -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"); } diff --git a/src/error.h b/src/error.h index cc3328c3b..3fa3f4709 100644 --- a/src/error.h +++ b/src/error.h @@ -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); diff --git a/src/main.cpp b/src/main.cpp index 0c0958a63..a10de160b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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) { diff --git a/src/sleep.cpp b/src/sleep.cpp index 2a3dcda24..0ae911b04 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -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 preflightSleep; + +/// Called to tell observers we are now entering sleep and you should prepare. Must return 0 +Observable 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 diff --git a/src/sleep.h b/src/sleep.h index 129728f50..e63fa70a6 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -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; \ No newline at end of file +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 preflightSleep; + +/// Called to tell observers we are now entering (light or deep) sleep and you should prepare. Must return 0 +extern Observable notifySleep; + +/// Called to tell observers we are now entering (deep) sleep and you should prepare. Must return 0 +extern Observable notifyDeepSleep; \ No newline at end of file