From 5569a4b91173da917578a77b2b5d4ac482d36495 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 21 Feb 2020 08:09:07 -0800 Subject: [PATCH] don't turn bluetooth back on every time we exit light sleep --- TODO.md | 2 + src/CustomRF95.cpp | 14 +++++ src/CustomRF95.h | 10 ++++ src/MeshRadio.cpp | 125 +-------------------------------------------- src/MeshRadio.h | 6 +-- src/TypedQueue.h | 5 ++ src/main.ino | 62 ++++++++++++++-------- 7 files changed, 74 insertions(+), 150 deletions(-) diff --git a/TODO.md b/TODO.md index 8ca7111bb..0e214e891 100644 --- a/TODO.md +++ b/TODO.md @@ -2,7 +2,9 @@ Items to complete before the first alpha release. +* implement CustomRF95::canSleep * document rules for sleep wrt lora/bluetooth/screen/gps. also: if I have text messages (only) for the phone, then give a few seconds in the hopes BLE can get it across before we have to go back to sleep. +* wake from light sleep as needed for our next scheduled periodic task (needed for gps position broadcasts etc) * if the phone doesn't read fromradio mailbox within X seconds, assume the phone is gone and we can stop queing location msgs for it (because it will redownload the nodedb when it comes back) * don't enter light sleep while the screen is on diff --git a/src/CustomRF95.cpp b/src/CustomRF95.cpp index e369e5c00..e684a1f67 100644 --- a/src/CustomRF95.cpp +++ b/src/CustomRF95.cpp @@ -18,6 +18,20 @@ CustomRF95::CustomRF95(MemoryPool &_pool, PointerQueue & { } +bool CustomRF95::canSleep() +{ + return (_mode == RHModeIdle || _mode == RHModeRx) && txQueue.isEmpty(); // FIXME - also check if we have started receiving +} + +bool CustomRF95::sleep() +{ + // we no longer care about interrupts from this device + prepareDeepSleep(); + + // FIXME - leave the device state in rx mode instead + return RH_RF95::sleep(); +} + bool CustomRF95::init() { bool ok = RH_RF95::init(); diff --git a/src/CustomRF95.h b/src/CustomRF95.h index 7fa4d8e4c..8261d1c6a 100644 --- a/src/CustomRF95.h +++ b/src/CustomRF95.h @@ -26,6 +26,16 @@ public: */ CustomRF95(MemoryPool &pool, PointerQueue &rxDest); + /** + * Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving) + * + * This method must be used before putting the CPU into deep or light sleep. + */ + bool canSleep(); + + /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep. + virtual bool sleep(); + /// 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 diff --git a/src/MeshRadio.cpp b/src/MeshRadio.cpp index dc6f66390..459ba323c 100644 --- a/src/MeshRadio.cpp +++ b/src/MeshRadio.cpp @@ -109,138 +109,15 @@ void MeshRadio::reloadConfig() rf95.setModeRx(); } -void MeshRadio::sleep() -{ - // we no longer care about interrupts from this device - rf95.prepareDeepSleep(); - - // FIXME - leave the device state in rx mode instead - rf95.sleep(); -} ErrorCode MeshRadio::send(MeshPacket *p) { -#if 1 return rf95.send(p); -#else - DEBUG_MSG("enquing packet for send from=0x%x, to=0x%x\n", p->from, p->to); - return txQueue.enqueue(p, 0); // nowait -#endif } -#if 0 -ErrorCode MeshRadio::sendTo(NodeNum dest, const uint8_t *buf, size_t len) -{ - // We must do this before each send, because we might have just changed our nodenum - manager.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor time. - assert(len <= 251); // Make sure we don't overflow the tiny max packet size - - uint32_t start = millis(); - // Note: we don't use sendToWait here because we don't want to wait and for the time being don't require - // reliable delivery - // return manager.sendtoWait((uint8_t *) buf, len, dest); - ErrorCode res = manager.sendto((uint8_t *)buf, len, dest) ? ERRNO_OK : ERRNO_UNKNOWN; - - // FIXME, we have to wait for sending to complete before freeing the buffer, otherwise it might get wiped - // instead just have the radiohead layer understand queues. - if (res == ERRNO_OK) - manager.waitPacketSent(); - - DEBUG_MSG("mesh sendTo %d bytes to 0x%x (%lu msecs)\n", len, dest, millis() - start); - - return res; -} - -/// enqueue a received packet in rxDest -void MeshRadio::handleReceive(MeshPacket *mp) -{ - int res = rxDest.enqueue(mp, 0); // NOWAIT - fixme, if queue is full, delete older messages - assert(res == pdTRUE); -} -#endif void MeshRadio::loop() { - // FIXME read from radio with recvfromAckTimeout - -#if 0 -static int16_t packetnum = 0; // packet counter, we increment per xmission - - char radiopacket[20] = "Hello World # "; - sprintf(radiopacket, "hello %d", packetnum++); - - assert(sendTo(NODENUM_BROADCAST, (uint8_t *)radiopacket, sizeof(radiopacket)) == ERRNO_OK); -#endif - -#if 0 - /// A temporary buffer used for sending/receving packets, sized to hold the biggest buffer we might need -#define MAX_RHPACKETLEN 251 - static uint8_t radiobuf[MAX_RHPACKETLEN]; - uint8_t rxlen; - uint8_t srcaddr, destaddr, id, flags; - - // Poll to see if we've received a packet - // if (manager.recvfromAckTimeout(radiobuf, &rxlen, 0, &srcaddr, &destaddr, &id, &flags)) - // prefill rxlen with the max length we can accept - very important - rxlen = (uint8_t) MAX_RHPACKETLEN; - if (manager.recvfrom(radiobuf, &rxlen, &srcaddr, &destaddr, &id, &flags)) - { - // We received a packet - int32_t freqerr = rf95.frequencyError(), snr = rf95.lastSNR(); - DEBUG_MSG("Received packet from mesh src=0x%x,dest=0x%x,id=%d,len=%d rxGood=%d,rxBad=%d,freqErr=%d,snr=%d\n", - srcaddr, destaddr, id, rxlen, rf95.rxGood(), rf95.rxBad(), freqerr, snr); - - MeshPacket *mp = pool.allocZeroed(); - - SubPacket *p = &mp->payload; - - mp->from = srcaddr; - mp->to = destaddr; - - // If we already have an entry in the DB for this nodenum, goahead and hide the snr/freqerr info there. - // Note: we can't create it at this point, because it might be a bogus User node allocation. But odds are we will - // already have a record we can hide this debugging info in. - NodeInfo *info = nodeDB.getNode(mp->from); - if (info) - { - info->snr = snr; - info->frequency_error = freqerr; - } - - if (!pb_decode_from_bytes(radiobuf, rxlen, SubPacket_fields, p)) - { - pool.release(mp); - } - else - { - // parsing was successful, queue for our recipient - mp->has_payload = true; - handleReceive(mp); - } - } -#endif - -#if 0 - // Poll to see if we need to send any packets - MeshPacket *txp = txQueue.dequeuePtr(0); // nowait - if (txp) - { - DEBUG_MSG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", rf95.txGood(), rf95.rxGood(), rf95.rxBad()); - assert(txp->has_payload); - - size_t numbytes = pb_encode_to_bytes(radiobuf, sizeof(radiobuf), SubPacket_fields, &txp->payload); - - int res = sendTo(txp->to, radiobuf, numbytes); - assert(res == ERRNO_OK); - - bool loopbackTest = false; // if true we will pretend to receive any packets we just sent - if (loopbackTest) - handleReceive(txp); - else - pool.release(txp); - - DEBUG_MSG("Done with send\n"); - } -#endif + // Currently does nothing, since we do it all in ISRs now } diff --git a/src/MeshRadio.h b/src/MeshRadio.h index 4221d23f7..2b72c67b3 100644 --- a/src/MeshRadio.h +++ b/src/MeshRadio.h @@ -55,6 +55,8 @@ */ class MeshRadio { public: + CustomRF95 rf95; // the raw radio interface - for now I'm leaving public - because this class is shrinking to be almost nothing + /** pool is the pool we will alloc our rx packets from * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool */ @@ -62,9 +64,6 @@ public: bool init(); - /// Prepare the radio to enter sleep mode, where it should draw only 0.2 uA - void sleep(); - /// 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 @@ -78,7 +77,6 @@ public: void reloadConfig(); private: - CustomRF95 rf95; // the raw radio interface // RHDatagram manager; // RHReliableDatagram manager; // don't use mesh yet diff --git a/src/TypedQueue.h b/src/TypedQueue.h index 61e9c309c..36f07fab5 100644 --- a/src/TypedQueue.h +++ b/src/TypedQueue.h @@ -29,6 +29,11 @@ public: return uxQueueSpacesAvailable(h); } + bool isEmpty() + { + return uxQueueMessagesWaiting(h) == 0; + } + // pdTRUE for success else failure BaseType_t enqueue(T x, TickType_t maxWait = portMAX_DELAY) { diff --git a/src/main.ino b/src/main.ino index 26a82082b..733e76912 100644 --- a/src/main.ino +++ b/src/main.ino @@ -35,6 +35,7 @@ #include "Periodic.h" #include "esp32/pm.h" #include "esp_pm.h" +#include "MeshRadio.h" #ifdef T_BEAM_V10 #include "axp20x.h" @@ -71,7 +72,8 @@ void setCPUFast(bool on) setCpuFrequencyMhz(on ? 240 : 80); } -static void setLed(bool ledOn) { +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); @@ -98,7 +100,7 @@ void doDeepSleep(uint64_t msecToWake) screen_off(); // datasheet says this will draw only 10ua // Put radio in sleep mode (will still draw power but only 0.2uA) - service.radio.sleep(); + service.radio.rf95.sleep(); nodeDB.saveToDisk(); @@ -110,7 +112,7 @@ void doDeepSleep(uint64_t msecToWake) digitalWrite(VEXT_ENABLE, 1); // turn off the display power #endif -setLed(false); + setLed(false); #ifdef T_BEAM_V10 if (axp192_found) @@ -175,6 +177,32 @@ setLed(false); #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). * @@ -187,26 +215,20 @@ void doLightSleep(uint32_t sleepMsec = 20 * 1000) // FIXME, use a more reasonabl setLed(false); // Never leave led on while in light sleep - // ESP docs say we must disable bluetooth and wifi before light sleep - 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"); + // 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_LOW_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 (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"); } /** @@ -470,10 +492,8 @@ void setup() serve->getAdvertising()->start(); } - // enableModemSleep(); - setCPUFast(false); - - // doLightSleep(); + setBluetoothEnable(false); + setCPUFast(true); // FIXME, switch to low speed now } uint32_t ledBlinker() @@ -483,7 +503,6 @@ uint32_t ledBlinker() setLed(ledOn); - // have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that return isCharging ? 1000 : (ledOn ? 2 : 1000); } @@ -594,13 +613,12 @@ 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; - bool bluetoothOn = false; // FIXME, leave bluetooth on per our power management policy (see doc) - // 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()) + if (!bluetoothOn && !is_screen_on() && service.radio.rf95.canSleep()) doLightSleep(60 * 1000); // FIXME, wake up to briefly flash led, then go back to sleep (without repowering bluetooth) - else { + else + { delay(msecstosleep); } } \ No newline at end of file