diff --git a/.vscode/settings.json b/.vscode/settings.json index cd2a5ca5c2..45052fa77d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -57,6 +57,7 @@ "HFSR", "Meshtastic", "NEMAGPS", + "NMEAGPS", "RDEF", "Ublox", "bkpt", diff --git a/bin/version.sh b/bin/version.sh index a2c90861a9..fb34ea22bb 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=1.1.0 \ No newline at end of file +export VERSION=1.1.1 \ No newline at end of file diff --git a/docs/hardware/WIFI_LoRa_32_V2(868-915).PDF b/docs/hardware/WIFI_LoRa_32_V2(868-915).PDF new file mode 100644 index 0000000000..11d499e7d8 Binary files /dev/null and b/docs/hardware/WIFI_LoRa_32_V2(868-915).PDF differ diff --git a/docs/hardware/u-blox6_ReceiverDescrProtSpec_(GPS.G6-SW-10018)_Public.pdf b/docs/hardware/u-blox6_ReceiverDescrProtSpec_(GPS.G6-SW-10018)_Public.pdf new file mode 100644 index 0000000000..f7231158cc Binary files /dev/null and b/docs/hardware/u-blox6_ReceiverDescrProtSpec_(GPS.G6-SW-10018)_Public.pdf differ diff --git a/docs/hardware/u-blox8-M8_ReceiverDescrProtSpec_(UBX-13003221).pdf b/docs/hardware/u-blox8-M8_ReceiverDescrProtSpec_(UBX-13003221).pdf new file mode 100644 index 0000000000..032455f2c7 Binary files /dev/null and b/docs/hardware/u-blox8-M8_ReceiverDescrProtSpec_(UBX-13003221).pdf differ diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 4a63cf0407..514fda9cf8 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -2,6 +2,16 @@ You probably don't care about this section - skip to the next one. +Threading tasks: + +- Use https://github.com/ivanseidel/ArduinoThread? rather than full coroutines +- clean up main loop() +- check that we are mostly asleep, show which thread is causing us to wake +- +- use tickless idle on nrf52, and sleep X msec or until an interrupt occurs or the cooperative scheduling changes. https://devzone.nordicsemi.com/f/nordic-q-a/12363/nrf52-freertos-power-consumption-tickless-idle +- BAD IDEA: use vTaskDelay and https://www.freertos.org/xTaskAbortDelay.html if scheduling changes. (define INCLUDE_xTaskAbortDelay on ESP32 and NRF52 - seems impossible to find?) +- GOOD IDEA: use xSemaphoreTake to take a semaphore using a timeout. Expect semaphore to not be set, but set it to indicate scheduling has changed. + Nimble tasks: - readerror.txt stress test bug diff --git a/docs/software/gps-todo.txt b/docs/software/gps-todo.txt new file mode 100644 index 0000000000..79503ebcda --- /dev/null +++ b/docs/software/gps-todo.txt @@ -0,0 +1,45 @@ +gps todo - bug 376 + +for taiwan region: +bin/run.sh --set region 8 + +time only mode +./bin/run.sh --set gps_operation 3 + +increase acquisition time until ublox power management can be improved see 9.3.1 + +ublox parsing failure + +record power measurements and update spreadsheet + +fix has_gps based on new logic + +make sure we are turning off lora radio in deep sleep + +don't send locations if the user has forbidden that (lie to phone so phone won't either) + +have loop methods return allowable sleep time (from their perspective) +increase main cpu sleep time + +add set router mode in python tool - it will also set GPS to stationary +make sure location still gets set once per boot and stays marked as valid on the gui +send position updates super rarely +turn off checking for usb power and forcing always on +(which will shrink DARK and NB period to zero and + make light_sleep very long) + +warn people about crummy gps antennas - add to faq + + +gps states + +Active - for gps_attempt_time seconds +Sleeping - for (gps_update_rate or sleep forever) seconds +ForcedSleep - PowerFSM says we don't want to use GPS right now +(no need for sleep forever state) + +gps triggers +GPS_TRIG_FORCE_SLEEP - from powerfsm +GPS_TRIG_FORCE_WAKE - from powerfsm +GPS_SETTINGS - if GPS settings changed, reset params and possibly become active + diff --git a/platformio.ini b/platformio.ini index 8dfc9dc5f7..b48dc39381 100644 --- a/platformio.ini +++ b/platformio.ini @@ -34,8 +34,10 @@ build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -Ilib/n ; leave this commented out to avoid breaking Windows ;upload_port = /dev/ttyUSB0 ;monitor_port = /dev/ttyUSB0 -upload_port = /dev/cu.SLAB_USBtoUART -monitor_port = /dev/cu.SLAB_USBtoUART + +; geeksville: I think setting this should not be required - it breaks linux +;upload_port = /dev/cu.SLAB_USBtoUART +;monitor_port = /dev/cu.SLAB_USBtoUART ; the default is esptool ; upload_protocol = esp-prog @@ -60,8 +62,8 @@ lib_deps = 1260 ; OneButton library for non-blocking button debounce 1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib https://github.com/meshtastic/arduino-fsm.git - https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git - https://github.com/meshtastic/RadioLib.git#ac7feac00f5e0bd95a3ac5d5852b4cc7344cf95c + https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git#cb8353dfddd1b0e205098f5e70d5f2a5f74b4838 + https://github.com/meshtastic/RadioLib.git#1083c2e76f9906c5f80dfec726facebf8413eef0 https://github.com/meshtastic/TinyGPSPlus.git https://github.com/meshtastic/AXP202X_Library.git#8404abb6d4b486748636bc6ad72d2a47baaf5460 Wire ; explicitly needed here because the AXP202 library forgets to add it diff --git a/proto b/proto index 5cdd7bff56..a0b8d88896 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 5cdd7bff56b0ea54351e5ea0e358e864b061078f +Subproject commit a0b8d888961720d70ab7467c94d8fa0687e58020 diff --git a/src/Power.cpp b/src/Power.cpp index f6ae1c691b..90e9881670 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -172,7 +172,7 @@ bool Power::axp192Init() DEBUG_MSG("----------------------------------------\n"); axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio - axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power + // axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power - now turned on in setGpsPower axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON); axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON); axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON); @@ -204,8 +204,11 @@ bool Power::axp192Init() PMU_IRQ, [] { pmu_irq = true; }, FALLING); axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1); - axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ | - AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ, + // we do not look for AXP202_CHARGING_FINISHED_IRQ & AXP202_CHARGING_IRQ because it occurs repeatedly while there is + // no battery also it could cause inadvertent waking from light sleep just because the battery filled + // we don't look for AXP202_BATT_REMOVED_IRQ because it occurs repeatedly while no battery installed + // we don't look at AXP202_VBUS_REMOVED_IRQ because we don't have anything hooked to vbus + axp.enableIRQ(AXP202_BATT_CONNECT_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ, 1); axp.clearIRQ(); diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 14f7b996b0..9a949638a7 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -12,7 +12,7 @@ static void sdsEnter() { // FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw - doDeepSleep(radioConfig.preferences.sds_secs * 1000LL); + doDeepSleep(getPref_sds_secs() * 1000LL); } #include "error.h" @@ -21,7 +21,7 @@ static uint32_t secsSlept; static void lsEnter() { - DEBUG_MSG("lsEnter begin, ls_secs=%u\n", radioConfig.preferences.ls_secs); + DEBUG_MSG("lsEnter begin, ls_secs=%u\n", getPref_ls_secs()); screen.setOn(false); secsSlept = 0; // How long have we been sleeping this time @@ -30,13 +30,13 @@ static void lsEnter() static void lsIdle() { - // DEBUG_MSG("lsIdle begin ls_secs=%u\n", radioConfig.preferences.ls_secs); + // DEBUG_MSG("lsIdle begin ls_secs=%u\n", getPref_ls_secs()); #ifndef NO_ESP32 esp_sleep_source_t wakeCause = ESP_SLEEP_WAKEUP_UNDEFINED; // Do we have more sleeping to do? - if (secsSlept < radioConfig.preferences.ls_secs) { + if (secsSlept < getPref_ls_secs()) { // Briefly come out of sleep long enough to blink the led once every few seconds uint32_t sleepTime = 30; @@ -45,7 +45,8 @@ static void lsIdle() setLed(false); // Never leave led on while in light sleep wakeCause = doLightSleep(sleepTime * 1000LL); - if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) { + switch (wakeCause) { + case ESP_SLEEP_WAKEUP_TIMER: // Normal case: timer expired, we should just go back to sleep ASAP setLed(true); // briefly turn on led @@ -53,12 +54,15 @@ static void lsIdle() secsSlept += sleepTime; // DEBUG_MSG("sleeping, flash led!\n"); - } - if (wakeCause == ESP_SLEEP_WAKEUP_UART) { + break; + + case ESP_SLEEP_WAKEUP_UART: // Not currently used (because uart triggers in hw have problems) powerFSM.trigger(EVENT_SERIAL_CONNECTED); - } else { - // We woke for some other reason (button press, uart, device interrupt) + break; + + default: + // We woke for some other reason (button press, device interrupt) // uint64_t status = esp_sleep_get_ext1_wakeup_status(); DEBUG_MSG("wakeCause %d\n", wakeCause); @@ -72,8 +76,10 @@ static void lsIdle() powerFSM.trigger(EVENT_PRESS); } else { // Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc) + // we lie and say "wake timer" because the interrupt will be handled by the regular IRQ code powerFSM.trigger(EVENT_WAKE_TIMER); } + break; } } else { // Someone says we can't sleep now, so just save some power by sleeping the CPU for 100ms or so @@ -91,7 +97,7 @@ static void lsIdle() static void lsExit() { // setGPSPower(true); // restore GPS power - gps->startLock(); + gps->forceWake(true); } static void nbEnter() @@ -136,8 +142,6 @@ static void onEnter() } } -static void wakeForPing() {} - static void screenPress() { screen.onPress(); @@ -157,15 +161,20 @@ Fsm powerFSM(&stateBOOT); void PowerFSM_setup() { - // If we already have AC power go to POWER state after init, otherwise go to ON - bool hasPower = powerStatus && powerStatus->getHasUSB(); + // If we are not a router and we already have AC power go to POWER state after init, otherwise go to ON + // We assume routers might be powered all the time, but from a low current (solar) source + bool isLowPower = radioConfig.preferences.is_low_power; + bool hasPower = !isLowPower && powerStatus && powerStatus->getHasUSB(); + bool isRouter = radioConfig.preferences.is_router; DEBUG_MSG("PowerFSM init, USB power=%d\n", hasPower); powerFSM.add_timed_transition(&stateBOOT, hasPower ? &statePOWER : &stateON, 3 * 1000, NULL, "boot timeout"); - powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WAKE_TIMER, wakeForPing, "Wake timer"); + // wake timer expired or a packet arrived + // if we are a router node, we go to NB (no need for bluetooth) otherwise we go to DARK (so we can send message to phone) + powerFSM.add_transition(&stateLS, isRouter ? &stateNB : &stateDARK, EVENT_WAKE_TIMER, NULL, "Wake timer"); - // Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB and then - // it handles things powerFSM.add_transition(&stateLS, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet"); + // Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB or dark and + // then it handles things powerFSM.add_transition(&stateLS, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet"); powerFSM.add_transition(&stateNB, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet, resetting win wake"); @@ -189,24 +198,31 @@ void PowerFSM_setup() powerFSM.add_transition(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing"); powerFSM.add_transition(&stateON, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing"); - powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); - powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); - powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); - - powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); - powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); - powerFSM.add_transition(&stateDARK, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); - powerFSM.add_transition(&stateON, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); // restarts the sleep timer + // if we are a router we don't turn the screen on for these things + if (!isRouter) { + // show the latest node when we get a new node db update + powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); + powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); + powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); + + // Show the received text message + powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); + powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); + powerFSM.add_transition(&stateDARK, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); + powerFSM.add_transition(&stateON, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); // restarts the sleep timer + } powerFSM.add_transition(&stateLS, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API"); powerFSM.add_transition(&stateNB, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API"); powerFSM.add_transition(&stateDARK, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API"); powerFSM.add_transition(&stateON, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API"); - powerFSM.add_transition(&stateLS, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect"); - powerFSM.add_transition(&stateNB, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect"); - powerFSM.add_transition(&stateDARK, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect"); - powerFSM.add_transition(&stateON, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect"); + if (!isLowPower) { + powerFSM.add_transition(&stateLS, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect"); + powerFSM.add_transition(&stateNB, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect"); + powerFSM.add_transition(&stateDARK, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect"); + powerFSM.add_transition(&stateON, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect"); + } powerFSM.add_transition(&statePOWER, &stateON, EVENT_POWER_DISCONNECTED, NULL, "power disconnected"); powerFSM.add_transition(&stateSERIAL, &stateON, EVENT_POWER_DISCONNECTED, NULL, "power disconnected"); @@ -217,22 +233,22 @@ void PowerFSM_setup() powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone"); - powerFSM.add_timed_transition(&stateON, &stateDARK, radioConfig.preferences.screen_on_secs * 1000, NULL, "Screen-on timeout"); + powerFSM.add_timed_transition(&stateON, &stateDARK, getPref_screen_on_secs() * 1000, NULL, "Screen-on timeout"); - powerFSM.add_timed_transition(&stateDARK, &stateNB, radioConfig.preferences.phone_timeout_secs * 1000, NULL, "Phone timeout"); + powerFSM.add_timed_transition(&stateDARK, &stateNB, getPref_phone_timeout_secs() * 1000, NULL, "Phone timeout"); #ifndef NRF52_SERIES // We never enter light-sleep state on NRF52 (because the CPU uses so little power normally) - powerFSM.add_timed_transition(&stateNB, &stateLS, radioConfig.preferences.min_wake_secs * 1000, NULL, "Min wake timeout"); + powerFSM.add_timed_transition(&stateNB, &stateLS, getPref_min_wake_secs() * 1000, NULL, "Min wake timeout"); - powerFSM.add_timed_transition(&stateDARK, &stateLS, radioConfig.preferences.wait_bluetooth_secs * 1000, NULL, - "Bluetooth timeout"); + powerFSM.add_timed_transition(&stateDARK, &stateLS, getPref_wait_bluetooth_secs() * 1000, NULL, "Bluetooth timeout"); #endif - powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.mesh_sds_timeout_secs * 1000, NULL, - "mesh timeout"); + auto meshSds = getPref_mesh_sds_timeout_secs(); + if (meshSds != UINT32_MAX) + powerFSM.add_timed_transition(&stateLS, &stateSDS, meshSds * 1000, NULL, "mesh timeout"); // removing for now, because some users don't even have phones - // powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.phone_sds_timeout_sec * 1000, NULL, "phone + // powerFSM.add_timed_transition(&stateLS, &stateSDS, getPref_phone_sds_timeout_sec() * 1000, NULL, "phone // timeout"); powerFSM.run_machine(); // run one interation of the state machine, so we run our on enter tasks for the initial DARK state diff --git a/src/configuration.h b/src/configuration.h index ad783d0d5c..504a7aad8a 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -148,11 +148,6 @@ along with this program. If not, see . // devices. Comment this out to not rotate screen 180 degrees. #define FLIP_SCREEN_VERTICALLY -// DEBUG LED -#ifndef LED_INVERTED -#define LED_INVERTED 0 // define as 1 if LED is active low (on) -#endif - // ----------------------------------------------------------------------------- // GPS // ----------------------------------------------------------------------------- @@ -171,6 +166,9 @@ along with this program. If not, see . #define BUTTON_PIN 38 // The middle button GPIO on the T-Beam #define BUTTON_PIN_ALT 13 // Alternate GPIO for an external button if needed +#define LED_INVERTED 1 +#define LED_PIN 4 // Newer tbeams (1.1) have an extra led on GPIO4 + // TTGO uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if // not found then probe for SX1262 #define USE_RF95 @@ -192,8 +190,9 @@ along with this program. If not, see . // code) #endif -// Leave undefined to disable our PMU IRQ handler -#define PMU_IRQ 35 +// Leave undefined to disable our PMU IRQ handler. DO NOT ENABLE THIS because the pmuirq can cause sperious interrupts +// and waking from light sleep +// #define PMU_IRQ 35 #define AXP192_SLAVE_ADDRESS 0x34 #elif defined(TBEAM_V07) @@ -369,6 +368,11 @@ along with this program. If not, see . #endif +// DEBUG LED +#ifndef LED_INVERTED +#define LED_INVERTED 0 // define as 1 if LED is active low (on) +#endif + #ifdef USE_RF95 #define RF95_RESET LORA_RESET #define RF95_IRQ LORA_DIO0 // on SX1262 version this is a no connect DIO0 diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index f8b29de3dc..ba43041d77 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1,6 +1,8 @@ #include "GPS.h" +#include "NodeDB.h" #include "configuration.h" +#include "sleep.h" #include #include @@ -44,7 +46,7 @@ void readFromRTC() } /// If we haven't yet set our RTC this boot, set it from a GPS derived time -void perhapsSetRTC(const struct timeval *tv) +bool perhapsSetRTC(const struct timeval *tv) { if (!timeSetFromGPS) { timeSetFromGPS = true; @@ -55,10 +57,13 @@ void perhapsSetRTC(const struct timeval *tv) DEBUG_MSG("ERROR TIME SETTING NOT IMPLEMENTED!\n"); #endif readFromRTC(); + return true; + } else { + return false; } } -void perhapsSetRTC(struct tm &t) +bool perhapsSetRTC(struct tm &t) { /* Convert to unix time The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 @@ -70,10 +75,12 @@ void perhapsSetRTC(struct tm &t) tv.tv_usec = 0; // time.centisecond() * (10 / 1000); // DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec); - if (t.tm_year < 0 || t.tm_year >= 300) - DEBUG_MSG("Ignoring invalid GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec); - else - perhapsSetRTC(&tv); + if (t.tm_year < 0 || t.tm_year >= 300) { + // DEBUG_MSG("Ignoring invalid GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec); + return false; + } else { + return perhapsSetRTC(&tv); + } } uint32_t getTime() @@ -86,19 +93,168 @@ uint32_t getValidTime() return timeSetFromGPS ? getTime() : 0; } +bool GPS::setup() +{ + setAwake(true); // Wake GPS power before doing any init + bool ok = setupGPS(); + + if (ok) + notifySleepObserver.observe(¬ifySleep); + + return ok; +} + /** * Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode * * calls sleep/wake */ -void GPS::setWantLocation(bool on) +void GPS::setAwake(bool on) { - if (wantNewLocation != on) { - wantNewLocation = on; + if (!wakeAllowed && on) { + DEBUG_MSG("Inhibiting because !wakeAllowed\n"); + on = false; + } + + if (isAwake != on) { DEBUG_MSG("WANT GPS=%d\n", on); - if (on) + if (on) { + lastWakeStartMsec = millis(); wake(); - else + } else { + lastSleepStartMsec = millis(); sleep(); } -} \ No newline at end of file + + isAwake = on; + } +} + +GpsOperation GPS::getGpsOp() const +{ + auto op = radioConfig.preferences.gps_operation; + + if (op == GpsOperation_GpsOpUnset) + op = (radioConfig.preferences.location_share == LocationSharing_LocDisabled) ? GpsOperation_GpsOpTimeOnly + : GpsOperation_GpsOpMobile; + + return op; +} + +/** Get how long we should stay looking for each aquisition in msecs + */ +uint32_t GPS::getWakeTime() const +{ + uint32_t t = radioConfig.preferences.gps_attempt_time; + + if (t == UINT32_MAX) + return t; // already maxint + + if (t == 0) + t = 5 * 60; // Allow up to 5 mins for each attempt (probably will be much less if we can find sats) + + t *= 1000; // msecs + + return t; +} + +/** Get how long we should sleep between aqusition attempts in msecs + */ +uint32_t GPS::getSleepTime() const +{ + uint32_t t = radioConfig.preferences.gps_update_interval; + + auto op = getGpsOp(); + if ((timeSetFromGPS && op == GpsOperation_GpsOpTimeOnly) || (op == GpsOperation_GpsOpDisabled)) + t = UINT32_MAX; // Sleep forever now + + if (t == UINT32_MAX) + return t; // already maxint + + if (t == 0) + t = 2 * 60; // 2 mins + + t *= 1000; + + return t; +} + +void GPS::publishUpdate() +{ + DEBUG_MSG("publishing GPS lock=%d\n", hasLock()); + + // Notify any status instances that are observing us + const meshtastic::GPSStatus status = + meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop, heading, numSatellites); + newStatus.notifyObservers(&status); +} + +void GPS::loop() +{ + if (whileIdle()) { + // if we have received valid NMEA claim we are connected + isConnected = true; + } + + // If we are overdue for an update, turn on the GPS and at least publish the current status + uint32_t now = millis(); + + auto sleepTime = getSleepTime(); + if (!isAwake && sleepTime != UINT32_MAX && (now - lastSleepStartMsec) > sleepTime) { + // We now want to be awake - so wake up the GPS + setAwake(true); + } + + // While we are awake + if (isAwake) { + // DEBUG_MSG("looking for location\n"); + if ((now - lastWhileActiveMsec) > 1000) { + lastWhileActiveMsec = now; + whileActive(); + } + + // If we've already set time from the GPS, no need to ask the GPS + bool gotTime = timeSetFromGPS || lookForTime(); + bool gotLoc = lookForLocation(); + + // We've been awake too long - force sleep + auto wakeTime = getWakeTime(); + bool tooLong = wakeTime != UINT32_MAX && (now - lastWakeStartMsec) > wakeTime; + + // Once we get a location we no longer desperately want an update + // or if we got a time and we are in GpsOpTimeOnly mode + // DEBUG_MSG("gotLoc %d, tooLong %d, gotTime %d\n", gotLoc, tooLong, gotTime); + if (gotLoc || tooLong || (gotTime && getGpsOp() == GpsOperation_GpsOpTimeOnly)) { + if (gotLoc) + hasValidLocation = true; + + if (tooLong) { + // we didn't get a location during this ack window, therefore declare loss of lock + hasValidLocation = false; + } + + setAwake(false); + publishUpdate(); // publish our update for this just finished acquisition window + } + } +} + +void GPS::forceWake(bool on) +{ + if (on) { + DEBUG_MSG("Allowing GPS lock\n"); + // lastSleepStartMsec = 0; // Force an update ASAP + wakeAllowed = true; + } else { + wakeAllowed = false; + setAwake(false); + } +} + +/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs +int GPS::prepareSleep(void *unused) +{ + forceWake(false); + + return 0; +} diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 3b81b54f7e..1f8429a44e 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -6,8 +6,8 @@ #include "sys/time.h" /// If we haven't yet set our RTC this boot, set it from a GPS derived time -void perhapsSetRTC(const struct timeval *tv); -void perhapsSetRTC(struct tm &t); +bool perhapsSetRTC(const struct timeval *tv); +bool perhapsSetRTC(struct tm &t); // Generate a string representation of DOP const char *getDOPString(uint32_t dop); @@ -27,11 +27,18 @@ void readFromRTC(); */ class GPS { - protected: + private: + uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastWhileActiveMsec = 0; + bool hasValidLocation = false; // default to false, until we complete our first read - bool wantNewLocation = false; // true if we want a location right now + bool isAwake = false; // true if we want a location right now + + bool wakeAllowed = true; // false if gps must be forced to sleep regardless of what time it is + + CallbackObserver notifySleepObserver = CallbackObserver(this, &GPS::prepareSleep); + protected: public: /** If !NULL we will use this serial port to construct our GPS */ static HardwareSerial *_serial_gps; @@ -48,7 +55,7 @@ class GPS bool isConnected = false; // Do we have a GPS we are talking to - virtual ~GPS() {} + virtual ~GPS() {} // FIXME, we really should unregister our sleep observer /** We will notify this observable anytime GPS state has changed meaningfully */ Observable newStatus; @@ -56,32 +63,82 @@ class GPS /** * Returns true if we succeeded */ - virtual bool setup() { return true; } + virtual bool setup(); - /// A loop callback for subclasses that need it. FIXME, instead just block on serial reads - virtual void loop() {} + virtual void loop(); /// Returns ture if we have acquired GPS lock. bool hasLock() const { return hasValidLocation; } + /** + * Restart our lock attempt - try to get and broadcast a GPS reading ASAP + * called after the CPU wakes from light-sleep state + * + * Or set to false, to disallow any sort of waking + * */ + void forceWake(bool on); + + protected: + /// Do gps chipset specific init, return true for success + virtual bool setupGPS() = 0; + + /// If possible force the GPS into sleep/low power mode + virtual void sleep() {} + + /// wake the GPS into normal operation mode + virtual void wake() {} + + /** Subclasses should look for serial rx characters here and feed it to their GPS parser + * + * Return true if we received a valid message from the GPS + */ + virtual bool whileIdle() = 0; + + /** Idle processing while GPS is looking for lock, called once per secondish */ + virtual void whileActive() {} + + /** + * Perform any processing that should be done only while the GPS is awake and looking for a fix. + * Override this method to check for new locations + * + * @return true if we've acquired a time + */ + virtual bool lookForTime() = 0; + + /** + * Perform any processing that should be done only while the GPS is awake and looking for a fix. + * Override this method to check for new locations + * + * @return true if we've acquired a new location + */ + virtual bool lookForLocation() = 0; + + private: + /// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs + /// always returns 0 to indicate okay to sleep + int prepareSleep(void *unused); + /** * Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode - * + * * calls sleep/wake */ - void setWantLocation(bool on); + void setAwake(bool on); - /** - * Restart our lock attempt - try to get and broadcast a GPS reading ASAP - * called after the CPU wakes from light-sleep state */ - virtual void startLock() {} + /** Get how long we should stay looking for each aquisition + */ + uint32_t getWakeTime() const; -protected: - /// If possible force the GPS into sleep/low power mode - virtual void sleep() {} + /** Get how long we should sleep between aqusition attempts + */ + uint32_t getSleepTime() const; - /// wake the GPS into normal operation mode - virtual void wake() {} + GpsOperation getGpsOp() const; + + /** + * Tell users we have new GPS readings + */ + void publishUpdate(); }; extern GPS *gps; diff --git a/src/gps/NMEAGPS.cpp b/src/gps/NMEAGPS.cpp index 2d8af6d72a..1b6bb1b353 100644 --- a/src/gps/NMEAGPS.cpp +++ b/src/gps/NMEAGPS.cpp @@ -1,7 +1,6 @@ #include "NMEAGPS.h" #include "configuration.h" - static int32_t toDegInt(RawDegrees d) { int32_t degMult = 10000000; // 1e7 @@ -11,7 +10,7 @@ static int32_t toDegInt(RawDegrees d) return r; } -bool NMEAGPS::setup() +bool NMEAGPS::setupGPS() { #ifdef PIN_GPS_PPS // pulse per second @@ -22,89 +21,89 @@ bool NMEAGPS::setup() return true; } -void NMEAGPS::loop() +/** + * Perform any processing that should be done only while the GPS is awake and looking for a fix. + * Override this method to check for new locations + * + * @return true if we've acquired a new location + */ +bool NMEAGPS::lookForTime() { - // First consume any chars that have piled up at the receiver - while (_serial_gps->available() > 0) { - int c = _serial_gps->read(); - // DEBUG_MSG("%c", c); - bool isValid = reader.encode(c); + auto ti = reader.time; + auto d = reader.date; + if (ti.isUpdated() && ti.isValid() && d.isValid()) { + /* Convert to unix time +The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 +(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). +*/ + struct tm t; + t.tm_sec = ti.second(); + t.tm_min = ti.minute(); + t.tm_hour = ti.hour(); + t.tm_mday = d.day(); + t.tm_mon = d.month() - 1; + t.tm_year = d.year() - 1900; + t.tm_isdst = false; + perhapsSetRTC(t); - // if we have received valid NMEA claim we are connected - if (isValid) - isConnected = true; - } + return true; + } else + return false; +} - // If we are overdue for an update, turn on the GPS and at least publish the current status - uint32_t now = millis(); - bool mustPublishUpdate = false; - if ((now - lastUpdateMsec) > 30 * 1000 && !wantNewLocation) { - // Ugly hack for now - limit update checks to once every 30 secs - setWantLocation(true); - mustPublishUpdate = - true; // Even if we don't have an update this time, we at least want to occasionally publish the current state - } +/** + * Perform any processing that should be done only while the GPS is awake and looking for a fix. + * Override this method to check for new locations + * + * @return true if we've acquired a new location + */ +bool NMEAGPS::lookForLocation() +{ + bool foundLocation = false; - // Only bother looking at GPS state if we are interested in what it has to say - if (wantNewLocation) { - auto ti = reader.time; - auto d = reader.date; - if (ti.isUpdated() && ti.isValid() && d.isValid()) { - /* Convert to unix time - The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 - (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). - */ - struct tm t; - t.tm_sec = ti.second(); - t.tm_min = ti.minute(); - t.tm_hour = ti.hour(); - t.tm_mday = d.day(); - t.tm_mon = d.month() - 1; - t.tm_year = d.year() - 1900; - t.tm_isdst = false; - perhapsSetRTC(t); - } + // uint8_t fixtype = reader.fixQuality(); + // hasValidLocation = ((fixtype >= 1) && (fixtype <= 5)); - uint8_t fixtype = reader.fixQuality(); - hasValidLocation = ((fixtype >= 1) && (fixtype <= 5)); + if (reader.location.isUpdated()) { + if (reader.altitude.isValid()) + altitude = reader.altitude.meters(); - if (reader.location.isUpdated()) { - lastUpdateMsec = now; + if (reader.location.isValid()) { + auto loc = reader.location.value(); + latitude = toDegInt(loc.lat); + longitude = toDegInt(loc.lng); + foundLocation = true; + } - if (reader.altitude.isValid()) - altitude = reader.altitude.meters(); + // Diminution of precision (an accuracy metric) is reported in 10^2 units, so we need to scale down when we use it + if (reader.hdop.isValid()) { + dop = reader.hdop.value(); + } + if (reader.course.isValid()) { + heading = reader.course.value() * 1e3; // Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5 + } + if (reader.satellites.isValid()) { + numSatellites = reader.satellites.value(); + } - if (reader.location.isValid()) { - auto loc = reader.location.value(); - latitude = toDegInt(loc.lat); - longitude = toDegInt(loc.lng); + // expect gps pos lat=37.520825, lon=-122.309162, alt=158 + DEBUG_MSG("new NMEA GPS pos lat=%f, lon=%f, alt=%d, hdop=%g, heading=%f\n", latitude * 1e-7, longitude * 1e-7, altitude, + dop * 1e-2, heading * 1e-5); + } - // Once we get a location we no longer desperately want an update - setWantLocation(false); - } - // Diminution of precision (an accuracy metric) is reported in 10^2 units, so we need to scale down when we use it - if (reader.hdop.isValid()) { - dop = reader.hdop.value(); - } - if (reader.course.isValid()) { - heading = - reader.course.value() * 1e3; // Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5 - } - if (reader.satellites.isValid()) { - numSatellites = reader.satellites.value(); - } + return foundLocation; +} - // expect gps pos lat=37.520825, lon=-122.309162, alt=158 - DEBUG_MSG("new NMEA GPS pos lat=%f, lon=%f, alt=%d, hdop=%g, heading=%f\n", latitude * 1e-7, longitude * 1e-7, - altitude, dop * 1e-2, heading * 1e-5); - mustPublishUpdate = true; - } +bool NMEAGPS::whileIdle() +{ + bool isValid = false; - if (mustPublishUpdate) { - // Notify any status instances that are observing us - const meshtastic::GPSStatus status = - meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop, heading, numSatellites); - newStatus.notifyObservers(&status); - } + // First consume any chars that have piled up at the receiver + while (_serial_gps->available() > 0) { + int c = _serial_gps->read(); + // DEBUG_MSG("%c", c); + isValid |= reader.encode(c); } -} \ No newline at end of file + + return isValid; +} diff --git a/src/gps/NMEAGPS.h b/src/gps/NMEAGPS.h index b46aa0c65e..f411e4d166 100644 --- a/src/gps/NMEAGPS.h +++ b/src/gps/NMEAGPS.h @@ -14,10 +14,29 @@ class NMEAGPS : public GPS { TinyGPSPlus reader; - uint32_t lastUpdateMsec = 0; - public: - virtual bool setup(); + virtual bool setupGPS(); + + protected: + /** Subclasses should look for serial rx characters here and feed it to their GPS parser + * + * Return true if we received a valid message from the GPS + */ + virtual bool whileIdle(); + + /** + * Perform any processing that should be done only while the GPS is awake and looking for a fix. + * Override this method to check for new locations + * + * @return true if we've acquired a time + */ + virtual bool lookForTime(); - virtual void loop(); + /** + * Perform any processing that should be done only while the GPS is awake and looking for a fix. + * Override this method to check for new locations + * + * @return true if we've acquired a new location + */ + virtual bool lookForLocation(); }; diff --git a/src/gps/UBloxGPS.cpp b/src/gps/UBloxGPS.cpp index 0d133891e2..1007162b64 100644 --- a/src/gps/UBloxGPS.cpp +++ b/src/gps/UBloxGPS.cpp @@ -3,10 +3,7 @@ #include "sleep.h" #include -UBloxGPS::UBloxGPS() : concurrency::PeriodicTask() -{ - notifySleepObserver.observe(¬ifySleep); -} +UBloxGPS::UBloxGPS() {} bool UBloxGPS::tryConnect() { @@ -26,7 +23,7 @@ bool UBloxGPS::tryConnect() return isConnected; } -bool UBloxGPS::setup() +bool UBloxGPS::setupGPS() { if (_serial_gps) { #ifdef GPS_RX_PIN @@ -34,15 +31,12 @@ bool UBloxGPS::setup() #else _serial_gps->begin(GPS_BAUDRATE); #endif - // _serial_gps.setRxBufferSize(1024); // the default is 256 - } - -#ifdef GPS_POWER_EN - pinMode(GPS_POWER_EN, OUTPUT); - digitalWrite(GPS_POWER_EN, 1); - delay(200); // Give time for the GPS to startup after we gave power +#ifndef NO_ESP32 + _serial_gps->setRxBufferSize(1024); // the default is 256 #endif + } + // uncomment to see debug info // ublox.enableDebugging(Serial); // try a second time, the ublox lib serial parsing is buggy? @@ -56,8 +50,6 @@ bool UBloxGPS::setup() if (!setUBXMode()) recordCriticalError(UBloxInitFailed); // Don't halt the boot if saving the config fails, but do report the bug - concurrency::PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device - return true; } else { return false; @@ -120,38 +112,34 @@ bool UBloxGPS::factoryReset() return ok; } -/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs -int UBloxGPS::prepareSleep(void *unused) +/** Idle processing while GPS is looking for lock */ +void UBloxGPS::whileActive() { - if (isConnected) - ublox.powerOff(); - - return 0; + ublox.getT(maxWait()); // ask for new time data - hopefully ready when we come back + + // Ask for a new position fix - hopefully it will have results ready by next time + // the order here is important, because we only check for has latitude when reading + ublox.getSIV(maxWait()); + ublox.getPDOP(maxWait()); + ublox.getP(maxWait()); + + // Update fixtype + if (ublox.moduleQueried.fixType) { + fixType = ublox.getFixType(0); + DEBUG_MSG("GPS fix type %d\n", fixType); + } } -void UBloxGPS::doTask() +/** + * Perform any processing that should be done only while the GPS is awake and looking for a fix. + * Override this method to check for new locations + * + * @return true if we've acquired a new location + */ +bool UBloxGPS::lookForTime() { - if (isConnected) { - // Consume all characters that have arrived - - uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix - - // if using i2c or serial look too see if any chars are ready - ublox.checkUblox(); // See if new data is available. Process bytes as they come in. - - // If we don't have a fix (a quick check), don't try waiting for a solution) - // Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions - // turn off for now - uint16_t maxWait = i2cAddress ? 300 : 0; // If using i2c we must poll with wait - fixtype = ublox.getFixType(maxWait); - DEBUG_MSG("GPS fix type %d\n", fixtype); - - // DEBUG_MSG("sec %d\n", ublox.getSecond()); - // DEBUG_MSG("lat %d\n", ublox.getLatitude()); - - // any fix that has time - - if (ublox.getT(maxWait)) { + if (fixType >= 2) { + if (ublox.moduleQueried.gpsSecond) { /* Convert to unix time The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). @@ -165,44 +153,69 @@ void UBloxGPS::doTask() t.tm_year = ublox.getYear(0) - 1900; t.tm_isdst = false; perhapsSetRTC(t); + return true; } + } - latitude = ublox.getLatitude(0); - longitude = ublox.getLongitude(0); - altitude = ublox.getAltitudeMSL(0) / 1000; // in mm convert to meters - dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it - heading = ublox.getHeading(0); - numSatellites = ublox.getSIV(0); + return false; +} - // bogus lat lon is reported as 0 or 0 (can be bogus just for one) - // Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg! - hasValidLocation = - (latitude != 0) && (longitude != 0) && (latitude <= 900000000 && latitude >= -900000000) && (numSatellites > 0); +/** + * Perform any processing that should be done only while the GPS is awake and looking for a fix. + * Override this method to check for new locations + * + * @return true if we've acquired a new location + */ +bool UBloxGPS::lookForLocation() +{ + bool foundLocation = false; - // we only notify if position has changed due to a new fix - if ((fixtype >= 3 && fixtype <= 4) && ublox.getP(maxWait)) // rd fixes only + // we only notify if position has changed due to a new fix + if ((fixType >= 3 && fixType <= 4)) { + if (ublox.moduleQueried.latitude) // rd fixes only { - if (hasValidLocation) { - setWantLocation(false); - // ublox.powerOff(); - } - } else // we didn't get a location update, go back to sleep and hope the characters show up - setWantLocation(true); - - // Notify any status instances that are observing us - const meshtastic::GPSStatus status = - meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop, heading, numSatellites); - newStatus.notifyObservers(&status); + latitude = ublox.getLatitude(0); + longitude = ublox.getLongitude(0); + altitude = ublox.getAltitudeMSL(0) / 1000; // in mm convert to meters + dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it + + // Note: heading is only currently implmented in the ublox for the 8m chipset - therefore + // don't read it here - it will generate an ignored getPVT command on the 6ms + // heading = ublox.getHeading(0); + numSatellites = ublox.getSIV(0); + + // bogus lat lon is reported as 0 or 0 (can be bogus just for one) + // Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg! + foundLocation = + (latitude != 0) && (longitude != 0) && (latitude <= 900000000 && latitude >= -900000000) && (numSatellites > 0); + } } - // Once we have sent a location once we only poll the GPS rarely, otherwise check back every 10s until we have something - // over the serial - setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000); + return foundLocation; +} + +bool UBloxGPS::whileIdle() +{ + // if using i2c or serial look too see if any chars are ready + return ublox.checkUblox(); // See if new data is available. Process bytes as they come in. } -void UBloxGPS::startLock() +/// If possible force the GPS into sleep/low power mode +/// Note: ublox doesn't need a wake method, because as soon as we send chars to the GPS it will wake up +void UBloxGPS::sleep() { - DEBUG_MSG("Looking for GPS lock\n"); - wantNewLocation = true; - setPeriod(1); + // Tell GPS to power down until we send it characters on serial port (we leave vcc connected) + ublox.powerOff(); + // setGPSPower(false); } + +void UBloxGPS::wake() +{ + fixType = 0; // assume we hace no fix yet + + setGPSPower(true); + + // Note: no delay needed because now we leave gps power on always and instead use ublox.powerOff() + // Give time for the GPS to boot + // delay(200); +} \ No newline at end of file diff --git a/src/gps/UBloxGPS.h b/src/gps/UBloxGPS.h index 03f2d2a1c0..9fda4bd0e3 100644 --- a/src/gps/UBloxGPS.h +++ b/src/gps/UBloxGPS.h @@ -1,6 +1,5 @@ #pragma once -#include "../concurrency/PeriodicTask.h" #include "GPS.h" #include "Observer.h" #include "SparkFun_Ublox_Arduino_Library.h" @@ -10,43 +9,63 @@ * * When new data is available it will notify observers. */ -class UBloxGPS : public GPS, public concurrency::PeriodicTask +class UBloxGPS : public GPS { SFE_UBLOX_GPS ublox; - - CallbackObserver notifySleepObserver = CallbackObserver(this, &UBloxGPS::prepareSleep); + uint8_t fixType = 0; public: UBloxGPS(); + /** + * Reset our GPS back to factory settings + * + * @return true for success + */ + bool factoryReset(); + + protected: /** * Returns true if we succeeded */ - virtual bool setup(); + virtual bool setupGPS(); - virtual void doTask(); + /** Subclasses should look for serial rx characters here and feed it to their GPS parser + * + * Return true if we received a valid message from the GPS + */ + virtual bool whileIdle(); + + /** Idle processing while GPS is looking for lock */ + virtual void whileActive(); /** - * Restart our lock attempt - try to get and broadcast a GPS reading ASAP - * called after the CPU wakes from light-sleep state */ - virtual void startLock(); + * Perform any processing that should be done only while the GPS is awake and looking for a fix. + * Override this method to check for new locations + * + * @return true if we've acquired a time + */ + virtual bool lookForTime(); /** - * Reset our GPS back to factory settings + * Perform any processing that should be done only while the GPS is awake and looking for a fix. + * Override this method to check for new locations * - * @return true for success + * @return true if we've acquired a new location */ - bool factoryReset(); + virtual bool lookForLocation(); - private: - /// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs - /// always returns 0 to indicate okay to sleep - int prepareSleep(void *unused); + /// If possible force the GPS into sleep/low power mode + virtual void sleep(); + virtual void wake(); + private: /// Attempt to connect to our GPS, returns false if no gps is present bool tryConnect(); /// Switch to our desired operating mode and save the settings to flash /// returns true for success bool setUBXMode(); + + uint16_t maxWait() const { return i2cAddress ? 300 : 0; /*If using i2c we must poll with wait */ } }; diff --git a/src/main.cpp b/src/main.cpp index 37e9077df5..f4e811f545 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,9 +21,9 @@ */ +#include "Air530GPS.h" #include "MeshRadio.h" #include "MeshService.h" -#include "Air530GPS.h" #include "NodeDB.h" #include "PowerFSM.h" #include "UBloxGPS.h" @@ -429,7 +429,7 @@ 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; + msecstosleep = 10; // FIXME, stop early if something happens and sleep much longer // TODO: This should go into a thread handled by FreeRTOS. handleWebResponse(); diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 8baed24c18..b514b0c5be 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -52,7 +52,7 @@ static uint32_t sendOwnerCb() { service.sendOurOwner(); - return radioConfig.preferences.send_owner_interval * radioConfig.preferences.position_broadcast_secs * 1000; + return getPref_send_owner_interval() * getPref_position_broadcast_secs() * 1000; } static concurrency::Periodic sendOwnerPeriod(sendOwnerCb); @@ -318,7 +318,7 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused) // We limit our GPS broadcasts to a max rate static uint32_t lastGpsSend; uint32_t now = millis(); - if (lastGpsSend == 0 || now - lastGpsSend > radioConfig.preferences.position_broadcast_secs * 1000) { + if (lastGpsSend == 0 || now - lastGpsSend > getPref_position_broadcast_secs() * 1000) { lastGpsSend = now; DEBUG_MSG("Sending position to mesh\n"); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 1f34eb5df5..349e7e67de 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -116,18 +116,9 @@ bool NodeDB::resetRadioConfig() DEBUG_MSG("Performing factory reset!\n"); installDefaultDeviceState(); didFactoryReset = true; - } else if (radioConfig.preferences.sds_secs == 0) { - DEBUG_MSG("Fixing bogus RadioConfig!\n"); - - radioConfig.preferences.send_owner_interval = 4; // per sw-design.md - radioConfig.preferences.position_broadcast_secs = 15 * 60; - radioConfig.preferences.wait_bluetooth_secs = 120; - radioConfig.preferences.screen_on_secs = 5 * 60; - radioConfig.preferences.mesh_sds_timeout_secs = 2 * 60 * 60; - radioConfig.preferences.phone_sds_timeout_sec = 2 * 60 * 60; - radioConfig.preferences.sds_secs = 365 * 24 * 60 * 60; // one year - radioConfig.preferences.ls_secs = 60 * 60; - radioConfig.preferences.phone_timeout_secs = 15 * 60; + } else if (!channelSettings.psk.size) { + DEBUG_MSG("Setting default preferences!\n"); + radioConfig.has_channel_settings = true; radioConfig.has_preferences = true; diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index e1a1b77d61..20dc47cf94 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -47,9 +47,9 @@ class NodeDB void saveToDisk(); /** Reinit radio config if needed, because either: - * a) sometimes a buggy android app might send us bogus settings or + * a) sometimes a buggy android app might send us bogus settings or * b) the client set factory_reset - * + * * @return true if the config was completely reset, in that case, we should send it back to the client */ bool resetRadioConfig(); @@ -137,4 +137,18 @@ their nodes * * https://github.com/meshtastic/Meshtastic-device/issues/269 */ -const char *getChannelName(); \ No newline at end of file +const char *getChannelName(); + +#define PREF_GET(name, defaultVal) \ + inline uint32_t getPref_##name() { return radioConfig.preferences.name ? radioConfig.preferences.name : (defaultVal); } + +PREF_GET(send_owner_interval, 4) +PREF_GET(position_broadcast_secs, 15 * 60) +PREF_GET(wait_bluetooth_secs, 120) +PREF_GET(screen_on_secs, 60) +PREF_GET(mesh_sds_timeout_secs, 2 * 60 * 60) +PREF_GET(phone_sds_timeout_sec, 2 * 60 * 60) +PREF_GET(sds_secs, 365 * 24 * 60 * 60) +PREF_GET(ls_secs, 60 * 60) +PREF_GET(phone_timeout_secs, 15 * 60) +PREF_GET(min_wake_secs, 10) diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 9ae5955d59..3cbcef1705 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -20,7 +20,7 @@ void PhoneAPI::init() void PhoneAPI::checkConnectionTimeout() { if (isConnected) { - bool newConnected = (millis() - lastContactMsec < radioConfig.preferences.phone_timeout_secs * 1000L); + bool newConnected = (millis() - lastContactMsec < getPref_phone_timeout_secs() * 1000L); if (!newConnected) { isConnected = false; onConnectionChanged(isConnected); diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index 594cec91f3..babd9475f1 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -42,7 +42,7 @@ bool RF95Interface::init() power = MAX_POWER; limitPower(); - + iface = lora = new RadioLibRF95(&module); #ifdef RF95_TCXO @@ -158,7 +158,7 @@ void RF95Interface::startReceive() isReceiving = true; - // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits + // Must be done AFTER, starting receive, because startReceive clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); } @@ -171,7 +171,7 @@ bool RF95Interface::isActivelyReceiving() bool RF95Interface::sleep() { // put chipset into sleep mode - disableInterrupt(); + setStandby(); // First cancel any active receving/sending lora->sleep(); return true; diff --git a/src/mesh/SX1262Interface.cpp b/src/mesh/SX1262Interface.cpp index 130f340c3b..b17acd9fe9 100644 --- a/src/mesh/SX1262Interface.cpp +++ b/src/mesh/SX1262Interface.cpp @@ -179,9 +179,17 @@ void SX1262Interface::startReceive() /** Could we send right now (i.e. either not actively receving or transmitting)? */ bool SX1262Interface::isActivelyReceiving() { - // return false; // FIXME - // FIXME this is not correct? - often always true - need to add an extra conditional - return lora.getPacketLength() > 0; + // The IRQ status will be cleared when we start our read operation. Check if we've started a preamble, but haven't yet + // received and handled the interrupt for reading the packet/handling errors. + + uint16_t irq = lora.getIrqStatus(); + bool hasPreamble = (irq & SX126X_IRQ_PREAMBLE_DETECTED); + + // this is not correct - often always true - need to add an extra conditional + // size_t bytesPending = lora.getPacketLength(); + + // if (hasPreamble || bytesPending) DEBUG_MSG("rx hasPre %d, bytes %d\n", hasPreamble, bytesPending); + return hasPreamble; } bool SX1262Interface::sleep() diff --git a/src/mesh/mesh.pb.c b/src/mesh/mesh.pb.c index 11ec3f49f5..d799c33d2c 100644 --- a/src/mesh/mesh.pb.c +++ b/src/mesh/mesh.pb.c @@ -27,7 +27,7 @@ PB_BIND(MeshPacket, MeshPacket, 2) PB_BIND(ChannelSettings, ChannelSettings, AUTO) -PB_BIND(RadioConfig, RadioConfig, AUTO) +PB_BIND(RadioConfig, RadioConfig, 2) PB_BIND(RadioConfig_UserPreferences, RadioConfig_UserPreferences, 2) @@ -60,3 +60,5 @@ PB_BIND(ManufacturingData, ManufacturingData, AUTO) + + diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index b726ab6e1c..01ba5b5dd4 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -37,6 +37,19 @@ typedef enum _RegionCode { RegionCode_TW = 8 } RegionCode; +typedef enum _GpsOperation { + GpsOperation_GpsOpUnset = 0, + GpsOperation_GpsOpMobile = 2, + GpsOperation_GpsOpTimeOnly = 3, + GpsOperation_GpsOpDisabled = 4 +} GpsOperation; + +typedef enum _LocationSharing { + LocationSharing_LocUnset = 0, + LocationSharing_LocEnabled = 1, + LocationSharing_LocDisabled = 2 +} LocationSharing; + typedef enum _Data_Type { Data_Type_OPAQUE = 0, Data_Type_CLEAR_TEXT = 1, @@ -121,6 +134,12 @@ typedef struct _RadioConfig_UserPreferences { char wifi_password[64]; bool wifi_ap_mode; RegionCode region; + LocationSharing location_share; + GpsOperation gps_operation; + uint32_t gps_update_interval; + uint32_t gps_attempt_time; + bool is_router; + bool is_low_power; bool factory_reset; pb_size_t ignore_incoming_count; uint32_t ignore_incoming[3]; @@ -248,6 +267,14 @@ typedef struct _ToRadio { #define _RegionCode_MAX RegionCode_TW #define _RegionCode_ARRAYSIZE ((RegionCode)(RegionCode_TW+1)) +#define _GpsOperation_MIN GpsOperation_GpsOpUnset +#define _GpsOperation_MAX GpsOperation_GpsOpDisabled +#define _GpsOperation_ARRAYSIZE ((GpsOperation)(GpsOperation_GpsOpDisabled+1)) + +#define _LocationSharing_MIN LocationSharing_LocUnset +#define _LocationSharing_MAX LocationSharing_LocDisabled +#define _LocationSharing_ARRAYSIZE ((LocationSharing)(LocationSharing_LocDisabled+1)) + #define _Data_Type_MIN Data_Type_OPAQUE #define _Data_Type_MAX Data_Type_CLEAR_READACK #define _Data_Type_ARRAYSIZE ((Data_Type)(Data_Type_CLEAR_READACK+1)) @@ -266,7 +293,7 @@ typedef struct _ToRadio { #define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0, 0} #define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, "", 0, 0, 0, 0} #define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default} -#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, 0, 0, {0, 0, 0}} +#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 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, "", "", "", 0, 0, 0, 0, 0, 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}, false, MeshPacket_init_default, 0, 0, 0} @@ -282,7 +309,7 @@ typedef struct _ToRadio { #define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0, 0} #define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, "", 0, 0, 0, 0} #define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero} -#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, 0, 0, {0, 0, 0}} +#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 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, "", "", "", 0, 0, 0, 0, 0, 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}, false, MeshPacket_init_zero, 0, 0, 0} @@ -341,7 +368,13 @@ typedef struct _ToRadio { #define RadioConfig_UserPreferences_wifi_password_tag 13 #define RadioConfig_UserPreferences_wifi_ap_mode_tag 14 #define RadioConfig_UserPreferences_region_tag 15 +#define RadioConfig_UserPreferences_is_router_tag 37 +#define RadioConfig_UserPreferences_is_low_power_tag 38 #define RadioConfig_UserPreferences_factory_reset_tag 100 +#define RadioConfig_UserPreferences_location_share_tag 32 +#define RadioConfig_UserPreferences_gps_operation_tag 33 +#define RadioConfig_UserPreferences_gps_update_interval_tag 34 +#define RadioConfig_UserPreferences_gps_attempt_time_tag 36 #define RadioConfig_UserPreferences_ignore_incoming_tag 103 #define RouteDiscovery_route_tag 2 #define User_id_tag 1 @@ -498,6 +531,12 @@ X(a, STATIC, SINGULAR, STRING, wifi_ssid, 12) \ X(a, STATIC, SINGULAR, STRING, wifi_password, 13) \ X(a, STATIC, SINGULAR, BOOL, wifi_ap_mode, 14) \ X(a, STATIC, SINGULAR, UENUM, region, 15) \ +X(a, STATIC, SINGULAR, UENUM, location_share, 32) \ +X(a, STATIC, SINGULAR, UENUM, gps_operation, 33) \ +X(a, STATIC, SINGULAR, UINT32, gps_update_interval, 34) \ +X(a, STATIC, SINGULAR, UINT32, gps_attempt_time, 36) \ +X(a, STATIC, SINGULAR, BOOL, is_router, 37) \ +X(a, STATIC, SINGULAR, BOOL, is_low_power, 38) \ X(a, STATIC, SINGULAR, BOOL, factory_reset, 100) \ X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103) #define RadioConfig_UserPreferences_CALLBACK NULL @@ -635,11 +674,11 @@ extern const pb_msgdesc_t ManufacturingData_msg; #define SubPacket_size 274 #define MeshPacket_size 313 #define ChannelSettings_size 84 -#define RadioConfig_size 282 -#define RadioConfig_UserPreferences_size 193 +#define RadioConfig_size 308 +#define RadioConfig_UserPreferences_size 219 #define NodeInfo_size 132 #define MyNodeInfo_size 110 -#define DeviceState_size 5434 +#define DeviceState_size 5460 #define DebugString_size 258 #define FromRadio_size 322 #define ToRadio_size 316 diff --git a/src/sleep.cpp b/src/sleep.cpp index 731273c9be..a6753d8ac8 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -163,6 +163,9 @@ void doDeepSleep(uint64_t msecToWake) digitalWrite(VEXT_ENABLE, 1); // turn off the display power #endif + // Kill GPS power completely (even if previously we just had it in sleep mode) + setGPSPower(false); + setLed(false); #ifdef TBEAM_V10 @@ -176,7 +179,8 @@ void doDeepSleep(uint64_t msecToWake) // axp.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // LORA radio - setGPSPower(false); + // now done by UBloxGPS.cpp + // setGPSPower(false); } #endif @@ -260,12 +264,20 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r // We treat the serial port as a GPIO for a fast/low power way of waking, if we see a rising edge that means // someone started to send something - // Alas - doesn't work reliably, instead need to use the uart specific version (which burns a little power) - // FIXME: gpio 3 is RXD for serialport 0 on ESP32 + // gpio 3 is RXD for serialport 0 on ESP32 // Send a few Z characters to wake the port - gpio_wakeup_enable((gpio_num_t)SERIAL0_RX_GPIO, GPIO_INTR_LOW_LEVEL); - // uart_set_wakeup_threshold(UART_NUM_0, 3); - // esp_sleep_enable_uart_wakeup(0); + + // this doesn't work on TBEAMs when the USB is depowered (causes bogus interrupts) + // So we disable this "wake on serial" feature - because now when a TBEAM (only) has power connected it + // never tries to go to sleep if the user is using the API + // gpio_wakeup_enable((gpio_num_t)SERIAL0_RX_GPIO, GPIO_INTR_LOW_LEVEL); + + // doesn't help - I think the USB-UART chip losing power is pulling the signal llow + // gpio_pullup_en((gpio_num_t)SERIAL0_RX_GPIO); + + // alas - can only work if using the refclock, which is limited to about 9600 bps + // assert(uart_set_wakeup_threshold(UART_NUM_0, 3) == ESP_OK); + // assert(esp_sleep_enable_uart_wakeup(0) == ESP_OK); #endif #ifdef BUTTON_PIN gpio_wakeup_enable((gpio_num_t)BUTTON_PIN, GPIO_INTR_LOW_LEVEL); // when user presses, this button goes low @@ -274,7 +286,7 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r gpio_wakeup_enable((gpio_num_t)RF95_IRQ_GPIO, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high #endif #ifdef PMU_IRQ - // FIXME, disable wake due to PMU because it seems to fire all the time? + // wake due to PMU can happen repeatedly if there is no battery installed or the battery fills if (axp192_found) gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_LOW_LEVEL); // pmu irq #endif