diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 2633d3bcf..5c09e9b54 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -4,11 +4,19 @@ You probably don't care about this section - skip to the next one. ## before next release +* DONE have android fill in if local GPS has poor signal +* fix heltec battery scaling +* add reference counting to mesh packets +* allow multiple simultanteous phoneapi connections +* DONE split position.time and last_heard +* DONE update android app to use last_heard +* DONE turn off bluetooth interface ENTIRELY while using serial API (was python client times out on connect sometimes) +* DONE gps assistance from phone not working? * DONE test latest firmware update with is_router * DONE firmware OTA updates of is_router true nodes fails? * DONE add UI in android app to reset to defaults https://github.com/meshtastic/Meshtastic-Android/issues/263 * DONE TEST THIS! changing channels requires a reboot to take effect https://github.com/meshtastic/Meshtastic-device/issues/752 -* DIBE bug report with remote info request timing out +* DONE bug report with remote info request timing out * DONE retest channel changing in android (using sim?) * DONE move remote admin doc from forum into git * DONE check crashlytics diff --git a/proto b/proto index 820fa497d..0ea232802 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 820fa497dfde07e129cad6955bf2f4b2b9cecebc +Subproject commit 0ea232802651fd6aaa53c93c09f4c2eb36470dd0 diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 86bc91639..338412c3d 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -9,6 +9,23 @@ #include "sleep.h" #include "target_specific.h" +/// Should we behave as if we have AC power now? +static bool isPowered() +{ + bool isRouter = radioConfig.preferences.is_router; + + // 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 || isRouter; + + /* To determine if we're externally powered, assumptions + 1) If we're powered up and there's no battery, we must be getting power externally. (because we'd be dead otherwise) + + 2) If we detect USB power from the power management chip, we must be getting power externally. + */ + return !isLowPower && powerStatus && (!powerStatus->getHasBattery() || powerStatus->getHasUSB()); +} + static void sdsEnter() { // FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw @@ -119,14 +136,34 @@ static void serialEnter() { setBluetoothEnable(false); screen->setOn(true); - screen->print("Using API...\n"); + screen->print("Serial connected\n"); +} + +static void serialExit() +{ + screen->print("Serial disconnected\n"); } static void powerEnter() { - screen->setOn(true); - setBluetoothEnable(true); - screen->print("Powered...\n"); + if (!isPowered()) { + // If we got here, we are in the wrong state - we should be in powered, let that state ahndle things + DEBUG_MSG("Loss of power in Powered\n"); + powerFSM.trigger(EVENT_POWER_DISCONNECTED); + } else { + screen->setOn(true); + setBluetoothEnable(true); + screen->print("Powered...\n"); + } +} + +static void powerIdle() +{ + if (!isPowered()) { + // If we got here, we are in the wrong state + DEBUG_MSG("Loss of power in Powered\n"); + powerFSM.trigger(EVENT_POWER_DISCONNECTED); + } } static void powerExit() @@ -153,6 +190,14 @@ static void onEnter() } } +static void onIdle() +{ + if (isPowered()) { + // If we got here, we are in the wrong state - we should be in powered, let that state ahndle things + powerFSM.trigger(EVENT_POWER_CONNECTED); + } +} + static void screenPress() { screen->onPress(); @@ -164,26 +209,16 @@ State stateSDS(sdsEnter, NULL, NULL, "SDS"); State stateLS(lsEnter, lsIdle, lsExit, "LS"); State stateNB(nbEnter, NULL, NULL, "NB"); State stateDARK(darkEnter, NULL, NULL, "DARK"); -State stateSERIAL(serialEnter, NULL, NULL, "SERIAL"); +State stateSERIAL(serialEnter, NULL, serialExit, "SERIAL"); State stateBOOT(bootEnter, NULL, NULL, "BOOT"); -State stateON(onEnter, NULL, NULL, "ON"); -State statePOWER(powerEnter, NULL, powerExit, "POWER"); +State stateON(onEnter, onIdle, NULL, "ON"); +State statePOWER(powerEnter, powerIdle, powerExit, "POWER"); Fsm powerFSM(&stateBOOT); void PowerFSM_setup() { bool isRouter = radioConfig.preferences.is_router; - - // 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 || isRouter; - - /* To determine if we're externally powered, assumptions - 1) If we're powered up and there's no battery, we must be getting power externally. (because we'd be dead otherwise) - - 2) If we detect USB power from the power management chip, we must be getting power externally. - */ - bool hasPower = !isLowPower && powerStatus && (!powerStatus->getHasBattery() || powerStatus->getHasUSB()); + bool hasPower = isPowered(); DEBUG_MSG("PowerFSM init, USB power=%d\n", hasPower); powerFSM.add_timed_transition(&stateBOOT, hasPower ? &statePOWER : &stateON, 3 * 1000, NULL, "boot timeout"); @@ -231,22 +266,25 @@ void PowerFSM_setup() powerFSM.add_transition(&stateON, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); // restarts the sleep timer } + // If we are not in statePOWER but get a serial connection, suppress sleep (and keep the screen on) while connected 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(&statePOWER, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API"); - 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"); - } + // If we get power connected, go to the power connect state + 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"); + // powerFSM.add_transition(&stateSERIAL, &stateON, EVENT_POWER_DISCONNECTED, NULL, "power disconnected"); - powerFSM.add_transition(&stateSERIAL, &stateNB, EVENT_SERIAL_DISCONNECTED, NULL, "serial disconnect"); + // the only way to leave state serial is for the client to disconnect (or we timeout and force disconnect them) + // when we leave, go to ON (which might not be the correct state if we have power connected, we will fix that in onEnter) + powerFSM.add_transition(&stateSERIAL, &stateON, EVENT_SERIAL_DISCONNECTED, NULL, "serial disconnect"); powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone"); diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index ad1cf642a..137fbcf0d 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -6,27 +6,29 @@ #define Port Serial -SerialConsole console; +SerialConsole *console; + +void consoleInit() +{ + new SerialConsole(); // Must be dynamically allocated because we are now inheriting from thread +} void consolePrintf(const char *format, ...) { va_list arg; va_start(arg, format); - console.vprintf(format, arg); + console->vprintf(format, arg); va_end(arg); } SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port) { + assert(!console); + console = this; canWrite = false; // We don't send packets to our port until it has talked to us first - // setDestination(&noopPrint); for testing, try turning off 'all' debug output and see what leaks -} + // setDestination(&noopPrint); for testing, try turning off 'all' debug output and see what leaks -/// Do late init that can't happen at constructor time -void SerialConsole::init() -{ Port.begin(SERIAL_BAUD); - StreamAPI::init(); emitRebooted(); } @@ -34,14 +36,14 @@ void SerialConsole::init() * we override this to notice when we've received a protobuf over the serial * stream. Then we shunt off debug serial output. */ -void SerialConsole::handleToRadio(const uint8_t *buf, size_t len) +bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len) { // Turn off debug serial printing once the API is activated, because other threads could print and corrupt packets if (!radioConfig.preferences.debug_log_enabled) setDestination(&noopPrint); canWrite = true; - StreamAPI::handleToRadio(buf, len); + return StreamAPI::handleToRadio(buf, len); } /// Hookable to find out when connection changes diff --git a/src/SerialConsole.h b/src/SerialConsole.h index b4f076286..b057c1931 100644 --- a/src/SerialConsole.h +++ b/src/SerialConsole.h @@ -11,14 +11,11 @@ class SerialConsole : public StreamAPI, public RedirectablePrint public: SerialConsole(); - /// Do late init that can't happen at constructor time - virtual void init(); - /** * we override this to notice when we've received a protobuf over the serial stream. Then we shunt off * debug serial output. */ - virtual void handleToRadio(const uint8_t *buf, size_t len); + virtual bool handleToRadio(const uint8_t *buf, size_t len); virtual size_t write(uint8_t c) { @@ -34,5 +31,6 @@ class SerialConsole : public StreamAPI, public RedirectablePrint // A simple wrapper to allow non class aware code write to the console void consolePrintf(const char *format, ...); +void consoleInit(); -extern SerialConsole console; +extern SerialConsole *console; diff --git a/src/concurrency/BinarySemaphoreFreeRTOS.h b/src/concurrency/BinarySemaphoreFreeRTOS.h index b5e488fd2..2883151d8 100644 --- a/src/concurrency/BinarySemaphoreFreeRTOS.h +++ b/src/concurrency/BinarySemaphoreFreeRTOS.h @@ -1,6 +1,5 @@ #pragma once -#include "configuration.h" #include "../freertosinc.h" namespace concurrency @@ -28,4 +27,4 @@ class BinarySemaphoreFreeRTOS #endif -} \ No newline at end of file +} // namespace concurrency \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index 1704ad14a..f0d3a0a53 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -457,7 +457,7 @@ along with this program. If not, see . #include "SerialConsole.h" -#define DEBUG_PORT console // Serial debug port +#define DEBUG_PORT (*console) // Serial debug port // What platforms should use SEGGER? #ifdef NRF52_SERIES diff --git a/src/esp32/BluetoothSoftwareUpdate.cpp b/src/esp32/BluetoothSoftwareUpdate.cpp index 843b14cf6..870d20ffd 100644 --- a/src/esp32/BluetoothSoftwareUpdate.cpp +++ b/src/esp32/BluetoothSoftwareUpdate.cpp @@ -16,7 +16,6 @@ int16_t updateResultHandle = -1; static CRC32 crc; -static uint32_t rebootAtMsec = 0; // If not zero we will reboot at this time (used to reboot shortly after the update completes) static uint32_t updateExpectedSize, updateActualSize; static uint8_t update_result; @@ -139,14 +138,6 @@ int update_region_callback(uint16_t conn_handle, uint16_t attr_handle, struct bl return chr_readwrite8(&update_region, sizeof(update_region), ctxt); } -void bluetoothRebootCheck() -{ - if (rebootAtMsec && millis() > rebootAtMsec) { - DEBUG_MSG("Rebooting for update\n"); - ESP.restart(); - } -} - /* See bluetooth-api.md diff --git a/src/esp32/BluetoothSoftwareUpdate.h b/src/esp32/BluetoothSoftwareUpdate.h index f5e1a1ded..478d478d5 100644 --- a/src/esp32/BluetoothSoftwareUpdate.h +++ b/src/esp32/BluetoothSoftwareUpdate.h @@ -4,8 +4,6 @@ void reinitUpdateService(); -void bluetoothRebootCheck(); - #ifdef __cplusplus extern "C" { #endif diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index ebe5477b5..e000ad5af 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -97,7 +97,6 @@ void esp32Loop() { esp_task_wdt_reset(); // service our app level watchdog loopBLE(); - bluetoothRebootCheck(); // for debug printing // radio.radioIf.canSleep(); diff --git a/src/main.cpp b/src/main.cpp index 6a4caf330..7ce86ffc0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -177,6 +177,7 @@ class ButtonThread : public OSThread OneButton userButtonAlt; #endif static bool shutdown_on_long_stop; + public: static uint32_t longPressTime; @@ -250,15 +251,15 @@ class ButtonThread : public OSThread power->shutdown(); } #elif NRF52_SERIES - // Do actual shutdown when button released, otherwise the button release - // may wake the board immediatedly. - if (!shutdown_on_long_stop) { - DEBUG_MSG("Shutdown from long press"); - playBeep(); - ledOff(PIN_LED1); - ledOff(PIN_LED2); - shutdown_on_long_stop = true; - } + // Do actual shutdown when button released, otherwise the button release + // may wake the board immediatedly. + if (!shutdown_on_long_stop) { + DEBUG_MSG("Shutdown from long press"); + playBeep(); + ledOff(PIN_LED1); + ledOff(PIN_LED2); + shutdown_on_long_stop = true; + } #endif } else { // DEBUG_MSG("Long press %u\n", (millis() - longPressTime)); @@ -315,9 +316,8 @@ void setup() SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_TRIM); #endif -// Debug #ifdef DEBUG_PORT - DEBUG_PORT.init(); // Set serial baud rate and init our mesh console + consoleInit(); // Set serial baud rate and init our mesh console #endif initDeepSleep(); @@ -576,14 +576,24 @@ Periodic axpDebugOutput(axpDebugRead); axpDebugOutput.setup(); #endif +uint32_t rebootAtMsec; // If not zero we will reboot at this time (used to reboot shortly after the update completes) + +void rebootCheck() +{ + if (rebootAtMsec && millis() > rebootAtMsec) { +#ifndef NO_ESP32 + DEBUG_MSG("Rebooting for update\n"); + ESP.restart(); +#else + DEBUG_MSG("FIXME implement reboot for this platform"); +#endif + } +} + void loop() { // axpDebugOutput.loop(); -#ifdef DEBUG_PORT - DEBUG_PORT.loop(); // Send/receive protobufs over the serial port -#endif - // heap_caps_check_integrity_all(true); // FIXME - disable this expensive check #ifndef NO_ESP32 @@ -592,6 +602,7 @@ void loop() #ifdef NRF52_SERIES nrf52Loop(); #endif + rebootCheck(); // For debugging // if (rIf) ((RadioLibInterface *)rIf)->isActivelyReceiving(); diff --git a/src/main.h b/src/main.h index edb50d0d7..1a6dfe439 100644 --- a/src/main.h +++ b/src/main.h @@ -20,4 +20,6 @@ extern graphics::Screen *screen; // Return a human readable string of the form "Meshtastic_ab13" const char *getDeviceName(); +extern uint32_t rebootAtMsec; + void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop(); diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index d542b21cf..4a42f7716 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -114,7 +114,7 @@ bool MeshService::reloadConfig() void MeshService::reloadOwner() { assert(nodeInfoPlugin); - if(nodeInfoPlugin) + if (nodeInfoPlugin) nodeInfoPlugin->sendOurNodeInfo(); nodeDB.saveToDisk(); } @@ -172,13 +172,12 @@ void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies) assert(node); if (node->has_position) { - if(positionPlugin) { + if (positionPlugin) { DEBUG_MSG("Sending position ping to 0x%x, wantReplies=%d\n", dest, wantReplies); positionPlugin->sendOurPosition(dest, wantReplies); } - } - else { - if(nodeInfoPlugin) { + } else { + if (nodeInfoPlugin) { DEBUG_MSG("Sending nodeinfo ping to 0x%x, wantReplies=%d\n", dest, wantReplies); nodeInfoPlugin->sendOurNodeInfo(dest, wantReplies); } @@ -198,10 +197,13 @@ NodeInfo *MeshService::refreshMyNodeInfo() Position &position = node->position; - // Update our local node info with our position (even if we don't decide to update anyone else) - position.time = + // Update our local node info with our time (even if we don't decide to update anyone else) + node->last_heard = getValidTime(RTCQualityFromNet); // This nodedb timestamp might be stale, so update it if our clock is kinda valid + // For the time in the position field, only set that if we have a real GPS clock + position.time = getValidTime(RTCQualityGPS); + position.battery_level = powerStatus->getBatteryChargePercent(); updateBatteryLevel(position.battery_level); diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index 0f2e772b3..7d22a3004 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -84,13 +84,12 @@ class MeshService NodeInfo *refreshMyNodeInfo(); private: - /// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh /// returns 0 to allow futher processing int onGPSChanged(const meshtastic::GPSStatus *arg); - /// Handle a packet that just arrived from the radio. This method does _ReliableRouternot_ free the provided packet. If it needs - /// to keep the packet around it makes a copy + /// Handle a packet that just arrived from the radio. This method does _ReliableRouternot_ free the provided packet. If it + /// needs to keep the packet around it makes a copy int handleFromRadio(const MeshPacket *p); friend class RoutingPlugin; }; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 1600cec65..a6ce8e4c8 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -419,8 +419,7 @@ uint32_t sinceLastSeen(const NodeInfo *n) { uint32_t now = getTime(); - uint32_t last_seen = n->position.time; - int delta = (int)(now - last_seen); + int delta = (int)(now - n->last_heard); if (delta < 0) // our clock must be slightly off still - not set from GPS yet delta = 0; @@ -454,7 +453,7 @@ void NodeDB::updatePosition(uint32_t nodeId, const Position &p) // Be careful to only update fields that have been set by the sender // A lot of position reports don't have time populated. In that case, be careful to not blow away the time we // recorded based on the packet rxTime - if (!info->position.time && p.time) + if (p.time) info->position.time = p.time; if (p.battery_level) info->position.battery_level = p.battery_level; @@ -504,11 +503,8 @@ void NodeDB::updateFrom(const MeshPacket &mp) NodeInfo *info = getOrCreateNode(getFrom(&mp)); - if (mp.rx_time) { // if the packet has a valid timestamp use it to update our last_seen - - info->has_position = true; // at least the time is valid - info->position.time = mp.rx_time; - } + if (mp.rx_time) // if the packet has a valid timestamp use it to update our last_heard + info->last_heard = mp.rx_time; if (mp.rx_snr) info->snr = mp.rx_snr; // keep the most recent SNR we received for this node. diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index b50381e70..db039afb2 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -1,10 +1,10 @@ #include "PhoneAPI.h" +#include "Channels.h" #include "GPS.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" #include "RadioInterface.h" -#include "Channels.h" #include #if FromRadio_size > MAX_TO_FROM_RADIO_SIZE @@ -17,66 +17,73 @@ PhoneAPI::PhoneAPI() {} -void PhoneAPI::init() +PhoneAPI::~PhoneAPI() { - observe(&service.fromNumChanged); -} - -PhoneAPI::~PhoneAPI() { close(); } -void PhoneAPI::close() { - unobserve(); - state = STATE_SEND_NOTHING; - bool oldConnected = isConnected; - isConnected = false; - if(oldConnected != isConnected) - onConnectionChanged(isConnected); +void PhoneAPI::handleStartConfig() +{ + if (!isConnected()) { + onConnectionChanged(true); + observe(&service.fromNumChanged); + } + + // even if we were already connected - restart our state machine + state = STATE_SEND_MY_INFO; + + DEBUG_MSG("Reset nodeinfo read pointer\n"); + nodeInfoForPhone = NULL; // Don't keep returning old nodeinfos + nodeDB.resetReadPointer(); // FIXME, this read pointer should be moved out of nodeDB and into this class - because + // this will break once we have multiple instances of PhoneAPI running independently +} + +void PhoneAPI::close() +{ + if (state != STATE_SEND_NOTHING) { + state = STATE_SEND_NOTHING; + + unobserve(); + releasePhonePacket(); // Don't leak phone packets on shutdown + + onConnectionChanged(false); + } } void PhoneAPI::checkConnectionTimeout() { - if (isConnected) { + if (isConnected()) { bool newConnected = (millis() - lastContactMsec < getPref_phone_timeout_secs() * 1000L); - if (!newConnected) { - isConnected = false; - onConnectionChanged(isConnected); - } + if (!newConnected) + close(); } } /** * Handle a ToRadio protobuf */ -void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) +bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) { powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // As long as the phone keeps talking to us, don't let the radio go to sleep lastContactMsec = millis(); - if (!isConnected) { - isConnected = true; - onConnectionChanged(isConnected); - } + // return (lastContactMsec != 0) && memset(&toRadioScratch, 0, sizeof(toRadioScratch)); if (pb_decode_from_bytes(buf, bufLength, ToRadio_fields, &toRadioScratch)) { switch (toRadioScratch.which_payloadVariant) { - case ToRadio_packet_tag: { - MeshPacket &p = toRadioScratch.packet; - printPacket("PACKET FROM PHONE", &p); - service.handleToRadio(p); - break; - } + case ToRadio_packet_tag: + return handleToRadioPacket(toRadioScratch.packet); + case ToRadio_want_config_id_tag: config_nonce = toRadioScratch.want_config_id; DEBUG_MSG("Client wants config, nonce=%u\n", config_nonce); - state = STATE_SEND_MY_INFO; - DEBUG_MSG("Reset nodeinfo read pointer\n"); - nodeInfoForPhone = NULL; // Don't keep returning old nodeinfos - nodeDB.resetReadPointer(); // FIXME, this read pointer should be moved out of nodeDB and into this class - because - // this will break once we have multiple instances of PhoneAPI running independently + handleStartConfig(); + break; + + case ToRadio_disconnect_tag: + close(); break; default: @@ -86,6 +93,8 @@ void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) } else { DEBUG_MSG("Error: ignoring malformed toradio\n"); } + + return false; } /** @@ -120,14 +129,12 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) case STATE_SEND_MY_INFO: // If the user has specified they don't want our node to share its location, make sure to tell the phone // app not to send locations on our behalf. - myNodeInfo.has_gps = (radioConfig.preferences.location_share == LocationSharing_LocDisabled) - ? true - : (gps && gps->isConnected()); // Update with latest GPS connect info + myNodeInfo.has_gps = gps && gps->isConnected(); // Update with latest GPS connect info fromRadioScratch.which_payloadVariant = FromRadio_my_info_tag; fromRadioScratch.my_info = myNodeInfo; state = STATE_SEND_NODEINFO; - service.refreshMyNodeInfo(); // Update my NodeInfo because the client will be asking for it soon. + service.refreshMyNodeInfo(); // Update my NodeInfo because the client will be asking for it soon. break; case STATE_SEND_NODEINFO: { @@ -135,7 +142,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) nodeInfoForPhone = NULL; // We just consumed a nodeinfo, will need a new one next time if (info) { - DEBUG_MSG("Sending nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", info->num, info->position.time, info->user.id, + DEBUG_MSG("Sending nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", info->num, info->last_heard, info->user.id, info->user.long_name); fromRadioScratch.which_payloadVariant = FromRadio_node_info_tag; fromRadioScratch.node_info = *info; @@ -159,16 +166,13 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) case STATE_SEND_PACKETS: // Do we have a message from the mesh? if (packetForPhone) { - printPacket("phone downloaded packet", packetForPhone); // Encapsulate as a FromRadio packet fromRadioScratch.which_payloadVariant = FromRadio_packet_tag; fromRadioScratch.packet = *packetForPhone; - - service.releaseToPool(packetForPhone); // we just copied the bytes, so don't need this buffer anymore - packetForPhone = NULL; } + releasePhonePacket(); break; default: @@ -187,6 +191,16 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) return 0; } +void PhoneAPI::handleDisconnect() {} + +void PhoneAPI::releasePhonePacket() +{ + if (packetForPhone) { + service.releaseToPool(packetForPhone); // we just copied the bytes, so don't need this buffer anymore + packetForPhone = NULL; + } +} + /** * Return true if we have data available to send to the phone */ @@ -226,7 +240,13 @@ bool PhoneAPI::available() /** * Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool */ -void PhoneAPI::handleToRadioPacket(MeshPacket *p) {} +bool PhoneAPI::handleToRadioPacket(MeshPacket &p) +{ + printPacket("PACKET FROM PHONE", &p); + service.handleToRadio(p); + + return true; +} /// If the mesh service tells us fromNum has changed, tell the phone int PhoneAPI::onNotify(uint32_t newValue) diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 6d03d2c80..02165aae3 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -22,6 +22,7 @@ class PhoneAPI enum State { STATE_UNUSED, // (no longer used) old default state - until Android apps are all updated, uses the old BLE API STATE_SEND_NOTHING, // (Eventual) Initial state, don't send anything until the client starts asking for config + // (disconnected) STATE_SEND_MY_INFO, // send our my info record // STATE_SEND_RADIO, // in 1.2 we now send this as a regular mesh packet // STATE_SEND_OWNER, no need to send Owner specially, it is just part of the nodedb @@ -58,17 +59,15 @@ class PhoneAPI /// Destructor - calls close() virtual ~PhoneAPI(); - /// Do late init that can't happen at constructor time - virtual void init(); - // Call this when the client drops the connection, resets the state to STATE_SEND_NOTHING // Unregisters our observer. A closed connection **can** be reopened by calling init again. virtual void close(); /** * Handle a ToRadio protobuf + * @return true true if a packet was queued for sending (so that caller can yield) */ - virtual void handleToRadio(const uint8_t *buf, size_t len); + virtual bool handleToRadio(const uint8_t *buf, size_t len); /** * Get the next packet we want to send to the phone @@ -83,17 +82,16 @@ class PhoneAPI */ bool available(); - protected: - /// Are we currently connected to a client? - bool isConnected = false; + bool isConnected() { return state != STATE_SEND_NOTHING; } + protected: /// Our fromradio packet while it is being assembled FromRadio fromRadioScratch; /// Hookable to find out when connection changes virtual void onConnectionChanged(bool connected) {} - /// If we haven't heard from the other side in a while then say not connected + /// If we haven't heard from the other side in a while then say not connected void checkConnectionTimeout(); /** @@ -101,11 +99,22 @@ class PhoneAPI */ virtual void onNowHasData(uint32_t fromRadioNum) {} - private: /** - * Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool + * Subclasses can use this to find out when a client drops the link */ - void handleToRadioPacket(MeshPacket *p); + virtual void handleDisconnect(); + + private: + void releasePhonePacket(); + + /// begin a new connection + void handleStartConfig(); + + /** + * Handle a packet that the phone wants us to send. We can write to it but can not keep a reference to it + * @return true true if a packet was queued for sending + */ + bool handleToRadioPacket(MeshPacket &p); /// If the mesh service tells us fromNum has changed, tell the phone virtual int onNotify(uint32_t newValue); diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index a5e36d463..2bc82b084 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -5,48 +5,62 @@ #define START2 0xc3 #define HEADER_LEN 4 -void StreamAPI::loop() +int32_t StreamAPI::runOnce() { + auto result = readStream(); writeStream(); - readStream(); checkConnectionTimeout(); + return result; } /** * Read any rx chars from the link and call handleToRadio */ -void StreamAPI::readStream() +int32_t StreamAPI::readStream() { - while (stream->available()) { // Currently we never want to block - uint8_t c = stream->read(); + uint32_t now = millis(); + if (!stream->available()) { + // Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time + bool recentRx = (now - lastRxMsec) < 2000; + return recentRx ? 5 : 250; + } else { + while (stream->available()) { // Currently we never want to block + uint8_t c = stream->read(); - // Use the read pointer for a little state machine, first look for framing, then length bytes, then payload - size_t ptr = rxPtr++; // assume we will probably advance the rxPtr + // Use the read pointer for a little state machine, first look for framing, then length bytes, then payload + size_t ptr = rxPtr++; // assume we will probably advance the rxPtr - rxBuf[ptr] = c; // store all bytes (including framing) + rxBuf[ptr] = c; // store all bytes (including framing) - if (ptr == 0) { // looking for START1 - if (c != START1) - rxPtr = 0; // failed to find framing - } else if (ptr == 1) { // looking for START2 - if (c != START2) - rxPtr = 0; // failed to find framing - } else if (ptr >= HEADER_LEN) { // we have at least read our 4 byte framing - uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing + if (ptr == 0) { // looking for START1 + if (c != START1) + rxPtr = 0; // failed to find framing + } else if (ptr == 1) { // looking for START2 + if (c != START2) + rxPtr = 0; // failed to find framing + } else if (ptr >= HEADER_LEN) { // we have at least read our 4 byte framing + uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing - if (ptr == HEADER_LEN) { - // we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid - // protobuf also) - if (len > MAX_TO_FROM_RADIO_SIZE) - rxPtr = 0; // length is bogus, restart search for framing - } + if (ptr == HEADER_LEN) { + // we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid + // protobuf also) + if (len > MAX_TO_FROM_RADIO_SIZE) + rxPtr = 0; // length is bogus, restart search for framing + } - if (rxPtr != 0 && ptr + 1 == len + HEADER_LEN) { - // If we didn't just fail the packet and we now have the right # of bytes, parse it - handleToRadio(rxBuf + HEADER_LEN, len); - rxPtr = 0; // start over again + if (rxPtr != 0 && ptr + 1 == len + HEADER_LEN) { + rxPtr = 0; // start over again on the next packet + + // If we didn't just fail the packet and we now have the right # of bytes, parse it + if (handleToRadio(rxBuf + HEADER_LEN, len)) + return 0; // we want to be called again ASAP because we still have more work to do + } } } + + // we had packets available this time, so assume we might have them next time also + lastRxMsec = now; + return 0; } } @@ -71,7 +85,7 @@ void StreamAPI::writeStream() void StreamAPI::emitTxBuffer(size_t len) { if (len != 0) { - DEBUG_MSG("emit tx %d\n", len); + // DEBUG_MSG("emit tx %d\n", len); txBuf[0] = START1; txBuf[1] = START2; txBuf[2] = (len >> 8) & 0xff; @@ -93,6 +107,6 @@ void StreamAPI::emitRebooted() fromRadioScratch.which_payloadVariant = FromRadio_rebooted_tag; fromRadioScratch.rebooted = true; - DEBUG_MSG("Emitting reboot packet for serial shell\n"); + // DEBUG_MSG("Emitting reboot packet for serial shell\n"); emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, FromRadio_size, FromRadio_fields, &fromRadioScratch)); } \ No newline at end of file diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h index ed0a5fbd4..6180a95d8 100644 --- a/src/mesh/StreamAPI.h +++ b/src/mesh/StreamAPI.h @@ -2,6 +2,7 @@ #include "PhoneAPI.h" #include "Stream.h" +#include "concurrency/OSThread.h" // A To/FromRadio packet + our 32 bit header #define MAX_STREAM_BUF_SIZE (MAX_TO_FROM_RADIO_SIZE + sizeof(uint32_t)) @@ -27,7 +28,7 @@ valid utf8 encoding. This makes it a bit easier to start a device outputting reg after it has received a valid packet from the PC, turn off unencoded debug printing and switch to this packet encoding. */ -class StreamAPI : public PhoneAPI +class StreamAPI : public PhoneAPI, protected concurrency::OSThread { /** * The stream we read/write from @@ -37,21 +38,23 @@ class StreamAPI : public PhoneAPI uint8_t rxBuf[MAX_STREAM_BUF_SIZE]; size_t rxPtr = 0; + /// time of last rx, used, to slow down our polling if we haven't heard from anyone + uint32_t lastRxMsec = 0; + public: - StreamAPI(Stream *_stream) : stream(_stream) {} + StreamAPI(Stream *_stream) : concurrency::OSThread("StreamAPI"), stream(_stream) {} /** * Currently we require frequent invocation from loop() to check for arrived serial packets and to send new packets to the * phone. - * FIXME, to support better power behavior instead move to a thread and block on serial reads. */ - void loop(); + virtual int32_t runOnce(); private: /** * Read any rx chars from the link and call handleToRadio */ - void readStream(); + int32_t readStream(); /** * call getFromRadio() and deliver encapsulated packets to the Stream @@ -63,7 +66,7 @@ class StreamAPI : public PhoneAPI * Send a FromRadio.rebooted = true packet to the phone */ void emitRebooted(); - + /** * Send the current txBuffer over our stream */ diff --git a/src/mesh/generated/admin.pb.h b/src/mesh/generated/admin.pb.h index cf71a9cb2..a2e753546 100644 --- a/src/mesh/generated/admin.pb.h +++ b/src/mesh/generated/admin.pb.h @@ -26,6 +26,7 @@ typedef struct _AdminMessage { bool confirm_set_channel; bool confirm_set_radio; bool exit_simulator; + int32_t reboot_seconds; }; } AdminMessage; @@ -49,6 +50,7 @@ extern "C" { #define AdminMessage_confirm_set_channel_tag 32 #define AdminMessage_confirm_set_radio_tag 33 #define AdminMessage_exit_simulator_tag 34 +#define AdminMessage_reboot_seconds_tag 35 /* Struct field encoding specification for nanopb */ #define AdminMessage_FIELDLIST(X, a) \ @@ -61,7 +63,8 @@ X(a, STATIC, ONEOF, UINT32, (variant,get_channel_request,get_channel_requ X(a, STATIC, ONEOF, MESSAGE, (variant,get_channel_response,get_channel_response), 7) \ X(a, STATIC, ONEOF, BOOL, (variant,confirm_set_channel,confirm_set_channel), 32) \ X(a, STATIC, ONEOF, BOOL, (variant,confirm_set_radio,confirm_set_radio), 33) \ -X(a, STATIC, ONEOF, BOOL, (variant,exit_simulator,exit_simulator), 34) +X(a, STATIC, ONEOF, BOOL, (variant,exit_simulator,exit_simulator), 34) \ +X(a, STATIC, ONEOF, INT32, (variant,reboot_seconds,reboot_seconds), 35) #define AdminMessage_CALLBACK NULL #define AdminMessage_DEFAULT NULL #define AdminMessage_variant_set_radio_MSGTYPE RadioConfig diff --git a/src/mesh/generated/deviceonly.pb.h b/src/mesh/generated/deviceonly.pb.h index 55bf6ce13..fa5de7b93 100644 --- a/src/mesh/generated/deviceonly.pb.h +++ b/src/mesh/generated/deviceonly.pb.h @@ -125,7 +125,7 @@ extern const pb_msgdesc_t ChannelFile_msg; /* Maximum encoded size of messages (where known) */ #define LegacyRadioConfig_size 4 #define LegacyRadioConfig_LegacyPreferences_size 2 -#define DeviceState_size 4926 +#define DeviceState_size 5118 #define ChannelFile_size 832 #ifdef __cplusplus diff --git a/src/mesh/generated/mesh.pb.h b/src/mesh/generated/mesh.pb.h index f9db4e6b9..4f30ade8b 100644 --- a/src/mesh/generated/mesh.pb.h +++ b/src/mesh/generated/mesh.pb.h @@ -162,6 +162,7 @@ typedef struct _NodeInfo { User user; bool has_position; Position position; + uint32_t last_heard; float snr; } NodeInfo; @@ -192,6 +193,7 @@ typedef struct _ToRadio { union { MeshPacket packet; uint32_t want_config_id; + bool disconnect; }; } ToRadio; @@ -233,7 +235,7 @@ extern "C" { #define Routing_init_default {0, {RouteDiscovery_init_default}} #define Data_init_default {_PortNum_MIN, {0, {0}}, 0, 0, 0, 0} #define MeshPacket_init_default {0, 0, 0, 0, {Data_init_default}, 0, 0, 0, 0, 0, _MeshPacket_Priority_MIN, 0} -#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0} +#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0} #define MyNodeInfo_init_default {0, 0, 0, "", "", "", _CriticalErrorCode_MIN, 0, 0, 0, 0, 0, 0} #define LogRecord_init_default {"", 0, "", _LogRecord_Level_MIN} #define FromRadio_init_default {0, 0, {MyNodeInfo_init_default}} @@ -244,7 +246,7 @@ extern "C" { #define Routing_init_zero {0, {RouteDiscovery_init_zero}} #define Data_init_zero {_PortNum_MIN, {0, {0}}, 0, 0, 0, 0} #define MeshPacket_init_zero {0, 0, 0, 0, {Data_init_zero}, 0, 0, 0, 0, 0, _MeshPacket_Priority_MIN, 0} -#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0} +#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0} #define MyNodeInfo_init_zero {0, 0, 0, "", "", "", _CriticalErrorCode_MIN, 0, 0, 0, 0, 0, 0} #define LogRecord_init_zero {"", 0, "", _LogRecord_Level_MIN} #define FromRadio_init_zero {0, 0, {MyNodeInfo_init_zero}} @@ -300,6 +302,7 @@ extern "C" { #define NodeInfo_num_tag 1 #define NodeInfo_user_tag 2 #define NodeInfo_position_tag 3 +#define NodeInfo_last_heard_tag 4 #define NodeInfo_snr_tag 7 #define Routing_route_request_tag 1 #define Routing_route_reply_tag 2 @@ -313,6 +316,7 @@ extern "C" { #define FromRadio_packet_tag 11 #define ToRadio_packet_tag 2 #define ToRadio_want_config_id_tag 100 +#define ToRadio_disconnect_tag 104 /* Struct field encoding specification for nanopb */ #define Position_FIELDLIST(X, a) \ @@ -378,6 +382,7 @@ X(a, STATIC, SINGULAR, INT32, rx_rssi, 13) X(a, STATIC, SINGULAR, UINT32, num, 1) \ X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \ X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \ +X(a, STATIC, SINGULAR, FIXED32, last_heard, 4) \ X(a, STATIC, SINGULAR, FLOAT, snr, 7) #define NodeInfo_CALLBACK NULL #define NodeInfo_DEFAULT NULL @@ -426,7 +431,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payloadVariant,packet,packet), 11) #define ToRadio_FIELDLIST(X, a) \ X(a, STATIC, ONEOF, MESSAGE, (payloadVariant,packet,packet), 2) \ -X(a, STATIC, ONEOF, UINT32, (payloadVariant,want_config_id,want_config_id), 100) +X(a, STATIC, ONEOF, UINT32, (payloadVariant,want_config_id,want_config_id), 100) \ +X(a, STATIC, ONEOF, BOOL, (payloadVariant,disconnect,disconnect), 104) #define ToRadio_CALLBACK NULL #define ToRadio_DEFAULT NULL #define ToRadio_payloadVariant_packet_MSGTYPE MeshPacket @@ -463,7 +469,7 @@ extern const pb_msgdesc_t ToRadio_msg; #define Routing_size 42 #define Data_size 260 #define MeshPacket_size 309 -#define NodeInfo_size 126 +#define NodeInfo_size 131 #define MyNodeInfo_size 95 #define LogRecord_size 81 #define FromRadio_size 318 diff --git a/src/mesh/wifi/WiFiServerAPI.cpp b/src/mesh/wifi/WiFiServerAPI.cpp index 37183975e..c0be8bc4a 100644 --- a/src/mesh/wifi/WiFiServerAPI.cpp +++ b/src/mesh/wifi/WiFiServerAPI.cpp @@ -40,18 +40,20 @@ void WiFiServerAPI::onConnectionChanged(bool connected) } /// override close to also shutdown the TCP link -void WiFiServerAPI::close() { +void WiFiServerAPI::close() +{ client.stop(); // drop tcp connection StreamAPI::close(); } -bool WiFiServerAPI::loop() +int32_t WiFiServerAPI::runOnce() { if (client.connected()) { - StreamAPI::loop(); - return true; + return StreamAPI::runOnce(); } else { - return false; + DEBUG_MSG("Client dropped connection, suspending API service\n"); + enabled = false; // we no longer need to run + return 0; } } @@ -78,18 +80,5 @@ int32_t WiFiServerPort::runOnce() openAPI = new WiFiServerAPI(client); } - if (openAPI) { - // Allow idle processing so the API can read from its incoming stream - if(!openAPI->loop()) { - // If our API link was up, shut it down - - DEBUG_MSG("Client dropped connection, closing API client\n"); - // Note: we can't call delete here because this object includes other state - // besides the stream API. Instead kill it later when we start a new instance - delete openAPI; - openAPI = NULL; - } - return 0; // run fast while our API server is running - } else - return 100; // only check occasionally for incoming connections + return 100; // only check occasionally for incoming connections } \ No newline at end of file diff --git a/src/mesh/wifi/WiFiServerAPI.h b/src/mesh/wifi/WiFiServerAPI.h index 46e2fad93..96cfb2bb0 100644 --- a/src/mesh/wifi/WiFiServerAPI.h +++ b/src/mesh/wifi/WiFiServerAPI.h @@ -1,7 +1,6 @@ #pragma once #include "StreamAPI.h" -#include "concurrency/OSThread.h" #include /** @@ -18,15 +17,14 @@ class WiFiServerAPI : public StreamAPI virtual ~WiFiServerAPI(); - /// @return true if we want to keep running, or false if we are ready to be destroyed - virtual bool loop(); // Check for dropped client connections - /// override close to also shutdown the TCP link virtual void close(); protected: /// Hookable to find out when connection changes virtual void onConnectionChanged(bool connected); + + virtual int32_t runOnce(); // Check for dropped client connections }; /** diff --git a/src/nimble/BluetoothUtil.cpp b/src/nimble/BluetoothUtil.cpp index 1416453b1..0618c73e6 100644 --- a/src/nimble/BluetoothUtil.cpp +++ b/src/nimble/BluetoothUtil.cpp @@ -487,7 +487,6 @@ void reinitBluetooth() DEBUG_MSG("Starting bluetooth\n"); if (isFirstTime) { bluetoothPhoneAPI = new BluetoothPhoneAPI(); - bluetoothPhoneAPI->init(); } // FIXME - if waking from light sleep, only esp_nimble_hci_init? diff --git a/src/nrf52/NRF52Bluetooth.cpp b/src/nrf52/NRF52Bluetooth.cpp index bcd74e10c..e67607c94 100644 --- a/src/nrf52/NRF52Bluetooth.cpp +++ b/src/nrf52/NRF52Bluetooth.cpp @@ -2,9 +2,9 @@ #include "BluetoothCommon.h" #include "configuration.h" #include "main.h" -#include -#include "mesh/mesh-pb-constants.h" #include "mesh/PhoneAPI.h" +#include "mesh/mesh-pb-constants.h" +#include static BLEService meshBleService = BLEService(BLEUuid(MESH_SERVICE_UUID_16)); static BLECharacteristic fromNum = BLECharacteristic(BLEUuid(FROMNUM_UUID_16)); @@ -155,7 +155,6 @@ void fromNumAuthorizeCb(uint16_t conn_hdl, BLECharacteristic *chr, ble_gatts_evt void setupMeshService(void) { bluetoothPhoneAPI = new BluetoothPhoneAPI(); - bluetoothPhoneAPI->init(); meshBleService.begin(); diff --git a/src/plugins/AdminPlugin.cpp b/src/plugins/AdminPlugin.cpp index 69e96875c..30016484e 100644 --- a/src/plugins/AdminPlugin.cpp +++ b/src/plugins/AdminPlugin.cpp @@ -77,6 +77,13 @@ bool AdminPlugin::handleReceivedProtobuf(const MeshPacket &mp, const AdminMessag handleGetRadio(mp); break; + case AdminMessage_reboot_seconds_tag: { + int32_t s = r->reboot_seconds; + DEBUG_MSG("Rebooting in %d seconds\n", s); + rebootAtMsec = (s < 0) ? 0 : (millis() + s * 1000); + break; + } + #ifdef PORTDUINO case AdminMessage_exit_simulator_tag: DEBUG_MSG("Exiting simulator\n"); diff --git a/src/plugins/PositionPlugin.cpp b/src/plugins/PositionPlugin.cpp index b6f76ad72..2aaa31a5e 100644 --- a/src/plugins/PositionPlugin.cpp +++ b/src/plugins/PositionPlugin.cpp @@ -10,10 +10,8 @@ PositionPlugin *positionPlugin; PositionPlugin::PositionPlugin() : ProtobufPlugin("position", PortNum_POSITION_APP, Position_fields), concurrency::OSThread("PositionPlugin") { - isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others - setIntervalFromNow(60 * - 1000); // Send our initial position 60 seconds after we start (to give GPS time to setup) - + isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others + setIntervalFromNow(60 * 1000); // Send our initial position 60 seconds after we start (to give GPS time to setup) } bool PositionPlugin::handleReceivedProtobuf(const MeshPacket &mp, const Position *pptr) diff --git a/version.properties b/version.properties index 81be22bfa..71cb4d0eb 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 1 minor = 2 -build = 13 +build = 16