From 95e952b896c39108a353606895d40dec831bf87e Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 16 May 2020 16:09:06 -0700 Subject: [PATCH 01/20] todo update --- bin/build-all.sh | 3 +++ docs/software/nrf52-TODO.md | 25 ++++++++++++++++++------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/bin/build-all.sh b/bin/build-all.sh index edea28cd..55d72fa8 100755 --- a/bin/build-all.sh +++ b/bin/build-all.sh @@ -39,6 +39,9 @@ function do_build { cp $SRCELF $OUTDIR/elfs/firmware-$ENV_NAME-$COUNTRY-$VERSION.elf } +# Make sure our submodules are current +git submodule update + # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale platformio lib update diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 69afcb94..47a520c8 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -1,21 +1,20 @@ # NRF52 TODO +## Misc work items + +* on node 0x1c transmit complete interrupt never comes in - though other nodes receive the packet + ## Initial work items Minimum items needed to make sure hardware is good. +- test my hackedup bootloader on the real hardware - add a hard fault handler - Use the PMU driver on real hardware - Use new radio driver on real hardware - Use UC1701 LCD driver on real hardware. Still need to create at startup and probe on SPI - test the LEDs - test the buttons -- make a new boarddef with a variant.h file. Fix pins in that file. In particular (at least): - #define PIN_SPI_MISO (46) - #define PIN_SPI_MOSI (45) - #define PIN_SPI_SCK (47) - #define PIN_WIRE_SDA (26) - #define PIN_WIRE_SCL (27) ## Secondary work items @@ -60,6 +59,7 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At Nice ideas worth considering someday... +- make a Mfg Controller and device under test classes as examples of custom app code for third party devs. Make a post about this. Use a custom payload type code. Have device under test send a broadcast with max hopcount of 0 for the 'mfgcontroller' payload type. mfg controller will read SNR and reply. DOT will declare failure/success and switch to the regular app screen. - Hook Segger RTT to the nordic logging framework. https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/debugging-with-real-time-terminal - Use nordic logging for DEBUG_MSG - use the Jumper simulator to run meshes of simulated hardware: https://docs.jumper.io/docs/install.html @@ -72,11 +72,14 @@ Nice ideas worth considering someday... - in addition to the main CPU watchdog, use the PMU watchdog as a really big emergency hammer - turn on 'shipping mode' in the PMU when device is 'off' - to cut battery draw to essentially zero - make Lorro_BQ25703A read/write operations atomic, current version could let other threads sneak in (once we start using threads) -- turn on DFU assistance in the appload using the nordic DFU helper lib call - make the segger logbuffer larger, move it to RAM that is preserved across reboots and support reading it out at runtime (to allow full log messages to be included in crash reports). Share this code with ESP32 (use gcc noinit attribute) - convert hardfaults/panics/asserts/wd exceptions into fault codes sent to phone - stop enumerating all i2c devices at boot, it wastes power & time - consider using "SYSTEMOFF" deep sleep mode, without RAM retension. Only useful for 'truly off - wake only by button press' only saves 1.5uA vs SYSTEMON. (SYSTEMON only costs 1.5uA). Possibly put PMU into shipping mode? +- change the BLE protocol to be more symmetric. Have the phone _also_ host a GATT service which receives writes to + 'fromradio'. This would allow removing the 'fromnum' mailbox/notify scheme of the current approach and decrease the number of packet handoffs when a packet is received. +- Using the preceeding, make a generalized 'nrf52/esp32 ble to internet' bridge service. To let nrf52 apps do MQTT/UDP/HTTP POST/HTTP GET operations to web services. +- lower advertise interval to save power, lower ble transmit power to save power ## Old unorganized notes @@ -104,6 +107,14 @@ Nice ideas worth considering someday... - add a NEMA based GPS driver to test GPS - DONE use "variants" to get all gpio bindings - DONE plug in correct variants for the real board +- turn on DFU assistance in the appload using the nordic DFU helper lib call +- make a new boarddef with a variant.h file. Fix pins in that file. In particular (at least): + #define PIN_SPI_MISO (46) + #define PIN_SPI_MOSI (45) + #define PIN_SPI_SCK (47) + #define PIN_WIRE_SDA (26) + #define PIN_WIRE_SCL (27) +- customize the bootloader to use proper button bindings ``` From ef1463a6a916e26bc0756cf3124187151d7ed0f6 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 17 May 2020 04:44:48 -0700 Subject: [PATCH 02/20] have tbeam charge at max rate (450mA) --- platformio.ini | 2 +- src/esp32/main-esp32.cpp | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/platformio.ini b/platformio.ini index ad27e294..3706fa20 100644 --- a/platformio.ini +++ b/platformio.ini @@ -93,7 +93,7 @@ extends = esp32_base board = ttgo-t-beam lib_deps = ${env.lib_deps} - AXP202X_Library + https://github.com/meshtastic/AXP202X_Library.git build_flags = ${esp32_base.build_flags} -D TBEAM_V10 diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index b0e1406b..03535b38 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -119,16 +119,8 @@ void axp192Init() DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE"); DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE"); + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1320MA); // actual limit (in HW) on the tbeam is 450mA #if 0 - // cribbing from https://github.com/m5stack/M5StickC/blob/master/src/AXP192.cpp to fix charger to be more like 300ms. - // I finally found an english datasheet. Will look at this later - but suffice it to say the default code from TTGO has 'issues' - - axp.adc1Enable(0xff, 1); // turn on all adcs - uint8_t val = 0xc2; - axp._writeByte(0x33, 1, &val); // Bat charge voltage to 4.2, Current 280mA - val = 0b11110010; - // Set ADC sample rate to 200hz - // axp._writeByte(0x84, 1, &val); // Not connected //val = 0xfc; @@ -191,6 +183,8 @@ uint32_t axpDebugRead() Periodic axpDebugOutput(axpDebugRead); #endif +#define MIN_BAT_MILLIVOLTS 3690 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/ + /// loop code specific to ESP32 targets void esp32Loop() { @@ -231,5 +225,11 @@ void esp32Loop() readPowerStatus(); axp.clearIRQ(); } + + float v = axp.getBattVoltage(); + DEBUG_MSG("Bat volt %f\n", v); + //if(v >= MIN_BAT_MILLIVOLTS / 2 && v < MIN_BAT_MILLIVOLTS) // If we have a battery at all and it is less than 10% full, force deep sleep + // powerFSM.trigger(EVENT_LOW_BATTERY); + #endif // T_BEAM_V10 } \ No newline at end of file From efc239533ca741790786cd6c9b639aeb21671222 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 17 May 2020 04:51:36 -0700 Subject: [PATCH 03/20] Fix #133 - force deep sleep if battery reaches 10% --- src/PowerFSM.cpp | 7 +++++++ src/PowerFSM.h | 1 + src/esp32/main-esp32.cpp | 9 ++++----- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index ed6eaf4f..b60204fd 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -153,6 +153,13 @@ void PowerFSM_setup() powerFSM.add_transition(&stateDARK, &stateON, EVENT_PRESS, NULL, "Press"); powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, screenPress, "Press"); // reenter On to restart our timers + // Handle critically low power battery by forcing deep sleep + powerFSM.add_transition(&stateBOOT, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateLS, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateNB, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateDARK, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateON, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing"); powerFSM.add_transition(&stateON, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing"); diff --git a/src/PowerFSM.h b/src/PowerFSM.h index c94ffabf..ecaea70a 100644 --- a/src/PowerFSM.h +++ b/src/PowerFSM.h @@ -13,6 +13,7 @@ #define EVENT_BLUETOOTH_PAIR 7 #define EVENT_NODEDB_UPDATED 8 // NodeDB has a big enough change that we think you should turn on the screen #define EVENT_CONTACT_FROM_PHONE 9 // the phone just talked to us over bluetooth +#define EVENT_LOW_BATTERY 10 // Battery is critically low, go to sleep extern Fsm powerFSM; diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index 03535b38..904c921e 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -183,7 +183,7 @@ uint32_t axpDebugRead() Periodic axpDebugOutput(axpDebugRead); #endif -#define MIN_BAT_MILLIVOLTS 3690 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/ +#define MIN_BAT_MILLIVOLTS 5000 // 3690 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/ /// loop code specific to ESP32 targets void esp32Loop() @@ -226,10 +226,9 @@ void esp32Loop() axp.clearIRQ(); } - float v = axp.getBattVoltage(); - DEBUG_MSG("Bat volt %f\n", v); - //if(v >= MIN_BAT_MILLIVOLTS / 2 && v < MIN_BAT_MILLIVOLTS) // If we have a battery at all and it is less than 10% full, force deep sleep - // powerFSM.trigger(EVENT_LOW_BATTERY); + if (powerStatus.haveBattery && !powerStatus.usb && + axp.getBattVoltage() < MIN_BAT_MILLIVOLTS) // If we have a battery at all and it is less than 10% full, force deep sleep + powerFSM.trigger(EVENT_LOW_BATTERY); #endif // T_BEAM_V10 } \ No newline at end of file From ef831a0b4d1a05298f01a55ebbe0e939c10ef5a6 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 17 May 2020 05:11:32 -0700 Subject: [PATCH 04/20] Fix leaving display on in deep sleep. We shutoff screen immediately, rather than waiting for our loop call() --- src/screen.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/screen.h b/src/screen.h index 634cdd10..be5444c1 100644 --- a/src/screen.h +++ b/src/screen.h @@ -100,7 +100,14 @@ class Screen : public PeriodicTask void setup(); /// Turns the screen on/off. - void setOn(bool on) { enqueueCmd(CmdItem{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF}); } + void setOn(bool on) + { + if (!on) + handleSetOn( + false); // We handle off commands immediately, because they might be called because the CPU is shutting down + else + enqueueCmd(CmdItem{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF}); + } /// Handles a button press. void onPress() { enqueueCmd(CmdItem{.cmd = Cmd::ON_PRESS}); } From 19f5a5ef79acdc29805e1b5edd4356e5f12e3c54 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 17 May 2020 05:12:16 -0700 Subject: [PATCH 05/20] oops - use correct battery shutoff voltage --- src/esp32/main-esp32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index 904c921e..7f978086 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -183,7 +183,7 @@ uint32_t axpDebugRead() Periodic axpDebugOutput(axpDebugRead); #endif -#define MIN_BAT_MILLIVOLTS 5000 // 3690 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/ +#define MIN_BAT_MILLIVOLTS 3690 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/ /// loop code specific to ESP32 targets void esp32Loop() From 5440cbec6abc3e5963400f9f614c2ac6a21cc099 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Mon, 18 May 2020 08:08:57 -0700 Subject: [PATCH 06/20] Update CNAME --- docs/CNAME | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CNAME b/docs/CNAME index 42401b5e..c9a8efa6 100644 --- a/docs/CNAME +++ b/docs/CNAME @@ -1 +1 @@ -www.meshtastic.org \ No newline at end of file +meshtastic.org \ No newline at end of file From 2a6858fa346c7007aaf8919d39547c20d7798da9 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Mon, 18 May 2020 08:10:51 -0700 Subject: [PATCH 07/20] Update CNAME --- docs/CNAME | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CNAME b/docs/CNAME index c9a8efa6..42401b5e 100644 --- a/docs/CNAME +++ b/docs/CNAME @@ -1 +1 @@ -meshtastic.org \ No newline at end of file +www.meshtastic.org \ No newline at end of file From 53c3d9baa2be13efbc4af366d58661b4c227ae19 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 18 May 2020 17:02:51 -0700 Subject: [PATCH 08/20] doc updates --- docs/software/mesh-alg.md | 6 +++--- docs/software/nrf52-TODO.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index 2b55a3a3..393abce6 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -8,13 +8,13 @@ great source of papers and class notes: http://www.cs.jhu.edu/~cs647/ reliable messaging tasks (stage one for DSR): - add a 'messagePeek' hook for all messages that pass through our node. -- use the same 'recentmessages' array used for broadcast msgs to detect duplicate retransmitted messages. -- keep possible retries in the list with rebroadcast messages? +- DONE use the same 'recentmessages' array used for broadcast msgs to detect duplicate retransmitted messages. +- keep possible retries in the list with to be rebroadcast messages? - for each message keep a count of # retries (max of three) - delay some random time for each retry (large enough to allow for acks to come in) - once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender - after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service) -- add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as two bits in the header. +- add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as three bits in the header. dsr tasks diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 47a520c8..ae89750d 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -2,8 +2,6 @@ ## Misc work items -* on node 0x1c transmit complete interrupt never comes in - though other nodes receive the packet - ## Initial work items Minimum items needed to make sure hardware is good. @@ -42,7 +40,6 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At - use SX126x::startReceiveDutyCycleAuto to save power by sleeping and briefly waking to check for preamble bits. Change xmit rules to have more preamble bits. - turn back on in-radio destaddr checking for RF95 -- remove the MeshRadio wrapper - we don't need it anymore, just do everythin in RadioInterface subclasses. - figure out what the correct current limit should be for the sx1262, currently we just use the default 100 - put sx1262 in sleepmode when processor gets shutdown (or rebooted), ideally even for critical faults (to keep power draw low). repurpose deepsleep state for this. - good power management tips: https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/optimizing-power-on-nrf52-designs @@ -59,6 +56,8 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At Nice ideas worth considering someday... +- Use flego to me an iOS/linux app? https://felgo.com/doc/qt/qtbluetooth-index/ or +- Use flutter to make an iOS/linux app? https://github.com/Polidea/FlutterBleLib - make a Mfg Controller and device under test classes as examples of custom app code for third party devs. Make a post about this. Use a custom payload type code. Have device under test send a broadcast with max hopcount of 0 for the 'mfgcontroller' payload type. mfg controller will read SNR and reply. DOT will declare failure/success and switch to the regular app screen. - Hook Segger RTT to the nordic logging framework. https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/debugging-with-real-time-terminal - Use nordic logging for DEBUG_MSG @@ -115,6 +114,7 @@ Nice ideas worth considering someday... #define PIN_WIRE_SDA (26) #define PIN_WIRE_SCL (27) - customize the bootloader to use proper button bindings +- remove the MeshRadio wrapper - we don't need it anymore, just do everything in RadioInterface subclasses. ``` From 26d3ef529e67cb7e354664d155659e59a9839e58 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 18 May 2020 17:35:23 -0700 Subject: [PATCH 09/20] Use the hop_limit field of MeshPacket to limit max delivery depth in the mesh. --- docs/software/mesh-alg.md | 8 +++---- proto | 2 +- src/mesh/FloodingRouter.cpp | 44 ++++++++++++++++------------------ src/mesh/MeshService.cpp | 1 + src/mesh/MeshTypes.h | 9 +++++++ src/mesh/RadioInterface.cpp | 3 ++- src/mesh/RadioInterface.h | 9 ++++++- src/mesh/RadioLibInterface.cpp | 5 +++- src/mesh/Router.h | 3 ++- 9 files changed, 51 insertions(+), 33 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index 393abce6..19e7e506 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -1,12 +1,10 @@ # Mesh broadcast algorithm -FIXME - instead look for standard solutions. this approach seems really suboptimal, because too many nodes will try to rebroast. If -all else fails could always use the stock Radiohead solution - though super inefficient. - great source of papers and class notes: http://www.cs.jhu.edu/~cs647/ reliable messaging tasks (stage one for DSR): +- fix FIXME - should snoop packet not sent to us - add a 'messagePeek' hook for all messages that pass through our node. - DONE use the same 'recentmessages' array used for broadcast msgs to detect duplicate retransmitted messages. - keep possible retries in the list with to be rebroadcast messages? @@ -14,7 +12,6 @@ reliable messaging tasks (stage one for DSR): - delay some random time for each retry (large enough to allow for acks to come in) - once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender - after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service) -- add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as three bits in the header. dsr tasks @@ -55,6 +52,8 @@ when we receive a routeError packet TODO: +- optimize our generalized flooding with heuristics, possibly have particular nodes self mark as 'router' nodes. + - DONE reread the radiohead mesh implementation - hop to hop acknowledgement seems VERY expensive but otherwise it seems like DSR - DONE read about mesh routing solutions (DSR and AODV) - DONE read about general mesh flooding solutions (naive, MPR, geo assisted) @@ -62,6 +61,7 @@ TODO: - REJECTED - seems dying - possibly dash7? https://www.slideshare.net/MaartenWeyn1/dash7-alliance-protocol-technical-presentation https://github.com/MOSAIC-LoPoW/dash7-ap-open-source-stack - does the opensource stack implement multihop routing? flooding? their discussion mailing list looks dead-dead - update duty cycle spreadsheet for our typical usecase - DONE generalize naive flooding +- DONE add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as three bits in the header. a description of DSR: https://tools.ietf.org/html/rfc4728 good slides here: https://www.slideshare.net/ashrafmath/dynamic-source-routing good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept diff --git a/proto b/proto index bc3ecd97..5799cb10 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit bc3ecd97e381b724c1a28acce0d12c688de73ba3 +Subproject commit 5799cb10b8f3cf353e7791d0609002cc93d9d13d diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 6db03dd4..e9941fb1 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -2,8 +2,6 @@ #include "configuration.h" #include "mesh-pb-constants.h" -static bool supportFlooding = true; // Sometimes to simplify debugging we want jusT simple broadcast only - FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) {} /** @@ -13,9 +11,7 @@ FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) {} */ ErrorCode FloodingRouter::send(MeshPacket *p) { - // We update our table of recent broadcasts, even for messages we send - if (supportFlooding) - wasSeenRecently(p); + wasSeenRecently(p); return Router::send(p); } @@ -29,28 +25,28 @@ ErrorCode FloodingRouter::send(MeshPacket *p) */ void FloodingRouter::handleReceived(MeshPacket *p) { - if (supportFlooding) { - if (wasSeenRecently(p)) { - DEBUG_MSG("Ignoring incoming floodmsg, because we've already seen it\n"); - packetPool.release(p); - } else { - if (p->to == NODENUM_BROADCAST) { - if (p->id != 0) { - MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it + if (wasSeenRecently(p)) { + DEBUG_MSG("Ignoring incoming msg, because we've already seen it\n"); + packetPool.release(p); + } else { + if (p->to == NODENUM_BROADCAST && p->hop_limit > 0) { + if (p->id != 0) { + MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it - DEBUG_MSG("Rebroadcasting received floodmsg to neighbors, fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - // Note: we are careful to resend using the original senders node id - // We are careful not to call our hooked version of send() - because we don't want to check this again - Router::send(tosend); + tosend->hop_limit--; // bump down the hop count - } else { - DEBUG_MSG("Ignoring a simple (0 hop) broadcast\n"); - } + DEBUG_MSG("Rebroadcasting received floodmsg to neighbors, fr=0x%x,to=0x%x,id=%d,hop_limit=%d\n", p->from, p->to, + p->id, tosend->hop_limit); + // Note: we are careful to resend using the original senders node id + // We are careful not to call our hooked version of send() - because we don't want to check this again + Router::send(tosend); + + } else { + DEBUG_MSG("Ignoring a simple (0 id) broadcast\n"); } - - // handle the packet as normal - Router::handleReceived(p); } - } else + + // handle the packet as normal Router::handleReceived(p); + } } diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 5f4b51ba..28aba7fe 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -285,6 +285,7 @@ MeshPacket *MeshService::allocForSending() p->which_payload = MeshPacket_decoded_tag; // Assume payload is decoded at start. p->from = nodeDB.getNodeNum(); p->to = NODENUM_BROADCAST; + p->hop_limit = HOP_MAX; p->id = generatePacketId(); p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index 0d9783e1..04bb13ad 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -14,6 +14,15 @@ typedef uint8_t PacketId; // A packet sequence number #define ERRNO_NO_INTERFACES 33 #define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER +/** + * the max number of hops a message can pass through, used as the default max for hop_limit in MeshPacket. + * + * We reserve 3 bits in the header so this could be up to 7, but given the high range of lora and typical usecases, keeping + * maxhops to 3 should be fine for a while. This also serves to prevent routing/flooding attempts to be attempted for + * too long. + **/ +#define HOP_MAX 3 + typedef int ErrorCode; /// Alloc and free packets to our global, ISR safe pool diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 3ad60005..123e128a 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -115,8 +115,9 @@ size_t RadioInterface::beginSending(MeshPacket *p) h->from = p->from; h->to = p->to; - h->flags = 0; h->id = p->id; + assert(p->hop_limit <= HOP_MAX); + h->flags = p->hop_limit; // if the sender nodenum is zero, that means uninitialized assert(h->from); diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 6c7dbd79..80661759 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -16,7 +16,14 @@ * wtih the old radiohead implementation. */ typedef struct { - uint8_t to, from, id, flags; + uint8_t to, from, id; + + /** + * Usage of flags: + * + * The bottom three bits of flags are use to store hop_limit when sent over the wire. + **/ + uint8_t flags; } PacketHeader; typedef enum { diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index e09c4f4a..78b9f661 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -288,13 +288,16 @@ void RadioLibInterface::handleReceiveInterrupt() rxGood++; if (h->to != 255 && h->to != ourAddr) { - DEBUG_MSG("ignoring packet not sent to us\n"); + DEBUG_MSG("FIXME - should snoop packet not sent to us\n"); } else { MeshPacket *mp = packetPool.allocZeroed(); mp->from = h->from; mp->to = h->to; mp->id = h->id; + assert(HOP_MAX <= 0x07); // If hopmax changes, carefully check this code + mp->hop_limit = h->flags & 0x07; + addReceiveMetadata(mp); mp->which_payload = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 20378371..77538a0b 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -59,7 +59,8 @@ class Router * Handle any packet that is received by an interface on this node. * Note: some packets may merely being passed through this node and will be forwarded elsewhere. * - * Note: this method will free the provided packet + * Note: this packet will never be called for messages sent/generated by this node. + * Note: this method will free the provided packet. */ virtual void handleReceived(MeshPacket *p); }; From 976bdad067825f59fb7eff40c43cd1e85bba2278 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 18 May 2020 17:57:58 -0700 Subject: [PATCH 10/20] sniffReceived now allows router to inspect packets not destined for this node --- docs/software/mesh-alg.md | 10 +++++----- src/mesh/FloodingRouter.cpp | 1 + src/mesh/MeshService.cpp | 2 +- src/mesh/MeshTypes.h | 5 ++++- src/mesh/RadioLibInterface.cpp | 36 ++++++++++++++++------------------ src/mesh/Router.cpp | 20 +++++++++++++++++-- src/mesh/Router.h | 6 ++++++ 7 files changed, 52 insertions(+), 28 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index 19e7e506..b809692a 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -4,11 +4,13 @@ great source of papers and class notes: http://www.cs.jhu.edu/~cs647/ reliable messaging tasks (stage one for DSR): -- fix FIXME - should snoop packet not sent to us -- add a 'messagePeek' hook for all messages that pass through our node. +- DONE generalize naive flooding +- DONE add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as three bits in the header. +- DONE add a 'snoopReceived' hook for all messages that pass through our node. - DONE use the same 'recentmessages' array used for broadcast msgs to detect duplicate retransmitted messages. - keep possible retries in the list with to be rebroadcast messages? -- for each message keep a count of # retries (max of three) +- for each message keep a count of # retries (max of three). allow this to _also_ work for broadcasts. +- Don't use broadcasts for the network pings (close open github issue) - delay some random time for each retry (large enough to allow for acks to come in) - once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender - after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service) @@ -60,8 +62,6 @@ TODO: - DONE reread the disaster radio protocol docs - seems based on Babel (which is AODVish) - REJECTED - seems dying - possibly dash7? https://www.slideshare.net/MaartenWeyn1/dash7-alliance-protocol-technical-presentation https://github.com/MOSAIC-LoPoW/dash7-ap-open-source-stack - does the opensource stack implement multihop routing? flooding? their discussion mailing list looks dead-dead - update duty cycle spreadsheet for our typical usecase -- DONE generalize naive flooding -- DONE add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as three bits in the header. a description of DSR: https://tools.ietf.org/html/rfc4728 good slides here: https://www.slideshare.net/ashrafmath/dynamic-source-routing good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index e9941fb1..c40211a8 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -29,6 +29,7 @@ void FloodingRouter::handleReceived(MeshPacket *p) DEBUG_MSG("Ignoring incoming msg, because we've already seen it\n"); packetPool.release(p); } else { + // If a broadcast, possibly _also_ send copies out into the mesh. (FIXME, do something smarter than naive flooding here) if (p->to == NODENUM_BROADCAST && p->hop_limit > 0) { if (p->id != 0) { MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 28aba7fe..986deb3f 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -285,7 +285,7 @@ MeshPacket *MeshService::allocForSending() p->which_payload = MeshPacket_decoded_tag; // Assume payload is decoded at start. p->from = nodeDB.getNodeNum(); p->to = NODENUM_BROADCAST; - p->hop_limit = HOP_MAX; + p->hop_limit = HOP_RELIABLE; p->id = generatePacketId(); p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index 04bb13ad..f491ce50 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -21,7 +21,10 @@ typedef uint8_t PacketId; // A packet sequence number * maxhops to 3 should be fine for a while. This also serves to prevent routing/flooding attempts to be attempted for * too long. **/ -#define HOP_MAX 3 +#define HOP_MAX 7 + +/// We normally just use max 3 hops for sending reliable messages +#define HOP_RELIABLE 3 typedef int ErrorCode; diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 78b9f661..ba0c32f0 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -2,7 +2,6 @@ #include "MeshTypes.h" #include "OSTimer.h" #include "mesh-pb-constants.h" -#include // FIXME, this class shouldn't need to look into nodedb #include #include #include @@ -284,31 +283,30 @@ void RadioLibInterface::handleReceiveInterrupt() rxBad++; } else { const PacketHeader *h = (PacketHeader *)radiobuf; - uint8_t ourAddr = nodeDB.getNodeNum(); rxGood++; - if (h->to != 255 && h->to != ourAddr) { - DEBUG_MSG("FIXME - should snoop packet not sent to us\n"); - } else { - MeshPacket *mp = packetPool.allocZeroed(); - mp->from = h->from; - mp->to = h->to; - mp->id = h->id; - assert(HOP_MAX <= 0x07); // If hopmax changes, carefully check this code - mp->hop_limit = h->flags & 0x07; + // Note: we deliver _all_ packets to our router (i.e. our interface is intentionally promiscuous). + // This allows the router and other apps on our node to sniff packets (usually routing) between other + // nodes. + MeshPacket *mp = packetPool.allocZeroed(); - addReceiveMetadata(mp); + mp->from = h->from; + mp->to = h->to; + mp->id = h->id; + assert(HOP_MAX <= 0x07); // If hopmax changes, carefully check this code + mp->hop_limit = h->flags & 0x07; - mp->which_payload = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point - assert(payloadLen <= sizeof(mp->encrypted.bytes)); - memcpy(mp->encrypted.bytes, payload, payloadLen); - mp->encrypted.size = payloadLen; + addReceiveMetadata(mp); - DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id); + mp->which_payload = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point + assert(payloadLen <= sizeof(mp->encrypted.bytes)); + memcpy(mp->encrypted.bytes, payload, payloadLen); + mp->encrypted.size = payloadLen; - deliverToReceiver(mp); - } + DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id); + + deliverToReceiver(mp); } } } diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 3752a2fb..21b928f2 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -3,6 +3,7 @@ #include "GPS.h" #include "configuration.h" #include "mesh-pb-constants.h" +#include /** * Router todo @@ -80,6 +81,15 @@ ErrorCode Router::send(MeshPacket *p) } } +/** + * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to + * update routing tables etc... based on what we overhear (even for messages not destined to our node) + */ +void Router::sniffReceived(MeshPacket *p) +{ + DEBUG_MSG("Sniffing packet not sent to us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); +} + /** * Handle any packet that is received by an interface on this node. * Note: some packets may merely being passed through this node and will be forwarded elsewhere. @@ -93,6 +103,8 @@ void Router::handleReceived(MeshPacket *p) assert(p->which_payload == MeshPacket_encrypted_tag); // I _think_ the only thing that pushes to us is raw devices that just received packets + // FIXME - someday don't send routing packets encrypted. That would allow us to route for other channels without + // being able to decrypt their data. // Try to decrypt the packet if we can static uint8_t bytes[MAX_RHPACKETLEN]; memcpy(bytes, p->encrypted.bytes, @@ -106,8 +118,12 @@ void Router::handleReceived(MeshPacket *p) // parsing was successful, queue for our recipient p->which_payload = MeshPacket_decoded_tag; - DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - notifyPacketReceived.notifyObservers(p); + sniffReceived(p); + uint8_t ourAddr = nodeDB.getNodeNum(); + if (p->to == NODENUM_BROADCAST || p->to == ourAddr) { + DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + notifyPacketReceived.notifyObservers(p); + } } packetPool.release(p); diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 77538a0b..03d75d33 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -63,6 +63,12 @@ class Router * Note: this method will free the provided packet. */ virtual void handleReceived(MeshPacket *p); + + /** + * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to + * update routing tables etc... based on what we overhear (even for messages not destined to our node) + */ + virtual void sniffReceived(MeshPacket *p); }; extern Router &router; \ No newline at end of file From 6b020149f3a3067f9a220e89813185eb4c3b2c92 Mon Sep 17 00:00:00 2001 From: Dafeman <47490997+Dafeman@users.noreply.github.com> Date: Tue, 19 May 2020 13:50:07 +1200 Subject: [PATCH 11/20] Update GUI Install --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 352a8e79..5fa2c871 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,8 @@ Please post comments on our [group chat](https://meshtastic.discourse.group/) if 6. In ESPHome Flasher, refresh the serial ports and select your board. 7. Browse to the previously downloaded firmware and select the correct firmware based on the board type, country and frequency. 8. Select Flash ESP. -9. Once finished, the terminal should start displaying debug messages including the Bluetooth passphrase when you try connect from your phone (handy if you don’t have a screen). +9. Once complete, “Done! Flashing is complete!” will be shown. +10. Debug messages sent from the Meshtastic device can be viewed with a terminal program such as [PuTTY](https://www.putty.org/) (Windows only). Within PuTTY, click “Serial”, enter the “Serial line” com port (can be found at step 4), enter “Speed” as 921600, then click “Open”. ### Installing from a commandline From cca4867987cdc2d3767ec1169f65bb36cb4cbe89 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 19 May 2020 10:27:28 -0700 Subject: [PATCH 12/20] want_ack flag added --- docs/software/mesh-alg.md | 11 ++++++--- proto | 2 +- src/mesh/FloodingRouter.cpp | 8 ++++--- src/mesh/FloodingRouter.h | 4 ---- src/mesh/RadioInterface.cpp | 2 +- src/mesh/RadioInterface.h | 3 +++ src/mesh/RadioLibInterface.cpp | 5 +++-- src/mesh/mesh.pb.c | 3 +++ src/mesh/mesh.pb.h | 41 ++++++++++++++++++++++++++++------ 9 files changed, 58 insertions(+), 21 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index b809692a..4bf2afa3 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -8,9 +8,9 @@ reliable messaging tasks (stage one for DSR): - DONE add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as three bits in the header. - DONE add a 'snoopReceived' hook for all messages that pass through our node. - DONE use the same 'recentmessages' array used for broadcast msgs to detect duplicate retransmitted messages. -- keep possible retries in the list with to be rebroadcast messages? -- for each message keep a count of # retries (max of three). allow this to _also_ work for broadcasts. -- Don't use broadcasts for the network pings (close open github issue) +- in the router receive path?, send an ack packet if want_ack was set and we are the final destination. FIXME, for now don't handle multihop or merging of data replies with these acks. +- keep a list of packets waiting for acks +- for each message keep a count of # retries (max of three). Local to the node, only for the most immediate hop, ignorant of multihop routing. - delay some random time for each retry (large enough to allow for acks to come in) - once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender - after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service) @@ -20,6 +20,11 @@ dsr tasks - do "hop by hop" routing - when sending, if destnodeinfo.next_hop is zero (and no message is already waiting for an arp for that node), startRouteDiscovery() for that node. Queue the message in the 'waiting for arp queue' so we can send it later when then the arp completes. - otherwise, use next_hop and start sending a message (with ack request) towards that node. +- Don't use broadcasts for the network pings (close open github issue) + +optimizations: + +- use a priority queue for the messages waiting to send. Send acks first, then routing messages, then data messages, then broadcasts? when we receive any packet diff --git a/proto b/proto index 5799cb10..e095ea92 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 5799cb10b8f3cf353e7791d0609002cc93d9d13d +Subproject commit e095ea92e62edc3f5dd6864c3d08d113fd8842e2 diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index c40211a8..f16405e4 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -2,7 +2,7 @@ #include "configuration.h" #include "mesh-pb-constants.h" -FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) {} +FloodingRouter::FloodingRouter() {} /** * Send a packet on a suitable interface. This routine will @@ -11,7 +11,8 @@ FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) {} */ ErrorCode FloodingRouter::send(MeshPacket *p) { - wasSeenRecently(p); + // Add any messages _we_ send to the seen message list + wasSeenRecently(p); // FIXME, move this to a sniffSent method return Router::send(p); } @@ -29,7 +30,8 @@ void FloodingRouter::handleReceived(MeshPacket *p) DEBUG_MSG("Ignoring incoming msg, because we've already seen it\n"); packetPool.release(p); } else { - // If a broadcast, possibly _also_ send copies out into the mesh. (FIXME, do something smarter than naive flooding here) + // If a broadcast, possibly _also_ send copies out into the mesh. + // (FIXME, do something smarter than naive flooding here) if (p->to == NODENUM_BROADCAST && p->hop_limit > 0) { if (p->id != 0) { MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index 996e9f7a..e7e1b961 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -30,10 +30,6 @@ class FloodingRouter : public Router, private PacketHistory { private: - /** - * Packets we've received that we need to resend after a short delay - */ - PointerQueue toResend; public: /** diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 123e128a..45ecc42a 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -117,7 +117,7 @@ size_t RadioInterface::beginSending(MeshPacket *p) h->to = p->to; h->id = p->id; assert(p->hop_limit <= HOP_MAX); - h->flags = p->hop_limit; + h->flags = p->hop_limit | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0); // if the sender nodenum is zero, that means uninitialized assert(h->from); diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 80661759..41976375 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -11,6 +11,9 @@ #define MAX_RHPACKETLEN 256 +#define PACKET_FLAGS_HOP_MASK 0x07 +#define PACKET_FLAGS_WANT_ACK_MASK 0x08 + /** * This structure has to exactly match the wire layout when sent over the radio link. Used to keep compatibility * wtih the old radiohead implementation. diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index ba0c32f0..5bf3b5ee 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -294,8 +294,9 @@ void RadioLibInterface::handleReceiveInterrupt() mp->from = h->from; mp->to = h->to; mp->id = h->id; - assert(HOP_MAX <= 0x07); // If hopmax changes, carefully check this code - mp->hop_limit = h->flags & 0x07; + assert(HOP_MAX <= PACKET_FLAGS_HOP_MASK); // If hopmax changes, carefully check this code + mp->hop_limit = h->flags & PACKET_FLAGS_HOP_MASK; + mp->want_ack = !!(h->flags & PACKET_FLAGS_WANT_ACK_MASK); addReceiveMetadata(mp); diff --git a/src/mesh/mesh.pb.c b/src/mesh/mesh.pb.c index 0b2c5b8c..32157655 100644 --- a/src/mesh/mesh.pb.c +++ b/src/mesh/mesh.pb.c @@ -51,6 +51,9 @@ PB_BIND(FromRadio, FromRadio, 2) PB_BIND(ToRadio, ToRadio, 2) +PB_BIND(ManufacturingData, ManufacturingData, AUTO) + + diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index d193d38e..ce9bc4e3 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -50,6 +50,13 @@ typedef struct _DebugString { char message[256]; } DebugString; +typedef struct _ManufacturingData { + uint32_t fradioFreq; + pb_callback_t hw_model; + pb_callback_t hw_version; + int32_t selftest_result; +} ManufacturingData; + typedef struct _MyNodeInfo { int32_t my_node_num; bool has_gps; @@ -146,6 +153,7 @@ typedef struct _MeshPacket { float rx_snr; uint32_t rx_time; uint32_t hop_limit; + bool want_ack; } MeshPacket; typedef struct _DeviceState { @@ -209,7 +217,7 @@ typedef struct _ToRadio { #define User_init_default {"", "", "", {0}} #define RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}} #define SubPacket_init_default {0, {Position_init_default}, 0, 0, 0, {0}} -#define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0} +#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}}, ""} #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, 0} @@ -219,12 +227,13 @@ typedef struct _ToRadio { #define DebugString_init_default {""} #define FromRadio_init_default {0, 0, {MeshPacket_init_default}} #define ToRadio_init_default {0, {MeshPacket_init_default}} +#define ManufacturingData_init_default {0, {{NULL}, NULL}, {{NULL}, NULL}, 0} #define Position_init_zero {0, 0, 0, 0, 0} #define Data_init_zero {_Data_Type_MIN, {0, {0}}} #define User_init_zero {"", "", "", {0}} #define RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}} #define SubPacket_init_zero {0, {Position_init_zero}, 0, 0, 0, {0}} -#define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0} +#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}}, ""} #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, 0} @@ -234,6 +243,7 @@ typedef struct _ToRadio { #define DebugString_init_zero {""} #define FromRadio_init_zero {0, 0, {MeshPacket_init_zero}} #define ToRadio_init_zero {0, {MeshPacket_init_zero}} +#define ManufacturingData_init_zero {0, {{NULL}, NULL}, {{NULL}, NULL}, 0} /* Field tags (for use in manual encoding/decoding) */ #define ChannelSettings_tx_power_tag 1 @@ -243,6 +253,10 @@ typedef struct _ToRadio { #define Data_typ_tag 1 #define Data_payload_tag 2 #define DebugString_message_tag 1 +#define ManufacturingData_fradioFreq_tag 1 +#define ManufacturingData_hw_model_tag 2 +#define ManufacturingData_hw_version_tag 3 +#define ManufacturingData_selftest_result_tag 4 #define MyNodeInfo_my_node_num_tag 1 #define MyNodeInfo_has_gps_tag 2 #define MyNodeInfo_num_channels_tag 3 @@ -299,6 +313,7 @@ typedef struct _ToRadio { #define MeshPacket_rx_time_tag 9 #define MeshPacket_rx_snr_tag 7 #define MeshPacket_hop_limit_tag 10 +#define MeshPacket_want_ack_tag 11 #define DeviceState_radio_tag 1 #define DeviceState_my_node_tag 2 #define DeviceState_owner_tag 3 @@ -374,7 +389,8 @@ X(a, STATIC, ONEOF, BYTES, (payload,encrypted,encrypted), 8) \ X(a, STATIC, SINGULAR, UINT32, id, 6) \ X(a, STATIC, SINGULAR, FLOAT, rx_snr, 7) \ X(a, STATIC, SINGULAR, FIXED32, rx_time, 9) \ -X(a, STATIC, SINGULAR, UINT32, hop_limit, 10) +X(a, STATIC, SINGULAR, UINT32, hop_limit, 10) \ +X(a, STATIC, SINGULAR, BOOL, want_ack, 11) #define MeshPacket_CALLBACK NULL #define MeshPacket_DEFAULT NULL #define MeshPacket_payload_decoded_MSGTYPE SubPacket @@ -486,6 +502,14 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,set_owner,variant.set_owner), 102) #define ToRadio_variant_set_radio_MSGTYPE RadioConfig #define ToRadio_variant_set_owner_MSGTYPE User +#define ManufacturingData_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, fradioFreq, 1) \ +X(a, CALLBACK, SINGULAR, STRING, hw_model, 2) \ +X(a, CALLBACK, SINGULAR, STRING, hw_version, 3) \ +X(a, STATIC, SINGULAR, SINT32, selftest_result, 4) +#define ManufacturingData_CALLBACK pb_default_field_callback +#define ManufacturingData_DEFAULT NULL + extern const pb_msgdesc_t Position_msg; extern const pb_msgdesc_t Data_msg; extern const pb_msgdesc_t User_msg; @@ -501,6 +525,7 @@ extern const pb_msgdesc_t DeviceState_msg; extern const pb_msgdesc_t DebugString_msg; extern const pb_msgdesc_t FromRadio_msg; extern const pb_msgdesc_t ToRadio_msg; +extern const pb_msgdesc_t ManufacturingData_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define Position_fields &Position_msg @@ -518,6 +543,7 @@ extern const pb_msgdesc_t ToRadio_msg; #define DebugString_fields &DebugString_msg #define FromRadio_fields &FromRadio_msg #define ToRadio_fields &ToRadio_msg +#define ManufacturingData_fields &ManufacturingData_msg /* Maximum encoded size of messages (where known) */ #define Position_size 39 @@ -525,16 +551,17 @@ extern const pb_msgdesc_t ToRadio_msg; #define User_size 72 #define RouteDiscovery_size 88 #define SubPacket_size 273 -#define MeshPacket_size 310 +#define MeshPacket_size 312 #define ChannelSettings_size 60 #define RadioConfig_size 136 #define RadioConfig_UserPreferences_size 72 #define NodeInfo_size 132 #define MyNodeInfo_size 85 -#define DeviceState_size 14955 +#define DeviceState_size 15021 #define DebugString_size 258 -#define FromRadio_size 319 -#define ToRadio_size 313 +#define FromRadio_size 321 +#define ToRadio_size 315 +/* ManufacturingData_size depends on runtime parameters */ #ifdef __cplusplus } /* extern "C" */ From 8bf4919576e62df8454e435d3abd644ad8a462e1 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 19 May 2020 11:56:17 -0700 Subject: [PATCH 13/20] wip reliable unicast (1 hop) --- src/main.cpp | 4 +- src/mesh/FloodingRouter.h | 3 +- src/mesh/MeshService.cpp | 51 ++------------ src/mesh/MeshService.h | 3 - src/mesh/ReliableRouter.cpp | 99 ++++++++++++++++++++++++++ src/mesh/ReliableRouter.h | 63 +++++++++++++++++ src/mesh/Router.cpp | 137 +++++++++++++++++++++++++----------- src/mesh/Router.h | 24 ++++++- 8 files changed, 289 insertions(+), 95 deletions(-) create mode 100644 src/mesh/ReliableRouter.cpp create mode 100644 src/mesh/ReliableRouter.h diff --git a/src/main.cpp b/src/main.cpp index 2378458a..3103335b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,7 +33,7 @@ #include "error.h" #include "power.h" // #include "rom/rtc.h" -#include "FloodingRouter.h" +#include "ReliableRouter.h" #include "main.h" #include "screen.h" #include "sleep.h" @@ -53,7 +53,7 @@ meshtastic::PowerStatus powerStatus; bool ssd1306_found; bool axp192_found; -FloodingRouter realRouter; +ReliableRouter realRouter; Router &router = realRouter; // Users of router don't care what sort of subclass implements that API // ----------------------------------------------------------------------------- diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index e7e1b961..48a8f0bc 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -27,10 +27,9 @@ Any entries in recentBroadcasts that are older than X seconds (longer than the max time a flood can take) will be discarded. */ -class FloodingRouter : public Router, private PacketHistory +class FloodingRouter : public Router, protected PacketHistory { private: - public: /** * Constructor diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 986deb3f..ee2905ad 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -46,8 +46,6 @@ MeshService service; #include "Router.h" -#define NUM_PACKET_ID 255 // 0 is consider invalid - static uint32_t sendOwnerCb() { service.sendOurOwner(); @@ -57,23 +55,6 @@ static uint32_t sendOwnerCb() static Periodic sendOwnerPeriod(sendOwnerCb); -/// Generate a unique packet id -// FIXME, move this someplace better -PacketId generatePacketId() -{ - static uint32_t i; // Note: trying to keep this in noinit didn't help for working across reboots - static bool didInit = false; - - if (!didInit) { - didInit = true; - i = random(0, NUM_PACKET_ID + - 1); // pick a random initial sequence number at boot (to prevent repeated reboots always starting at 0) - } - - i++; - return (i % NUM_PACKET_ID) + 1; // return number between 1 and 255 -} - MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE) { // assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro @@ -90,7 +71,7 @@ void MeshService::init() void MeshService::sendOurOwner(NodeNum dest, bool wantReplies) { - MeshPacket *p = allocForSending(); + MeshPacket *p = router.allocForSending(); p->to = dest; p->decoded.want_response = wantReplies; p->decoded.which_payload = SubPacket_user_tag; @@ -265,33 +246,13 @@ void MeshService::sendToMesh(MeshPacket *p) DEBUG_MSG("Providing time to mesh %u\n", p->decoded.position.time); } - // If the phone sent a packet just to us, don't send it out into the network - if (p->to == nodeDB.getNodeNum()) { - DEBUG_MSG("Dropping locally processed message\n"); + // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it + if (router.send(p) != ERRNO_OK) { + DEBUG_MSG("No radio was able to send packet, discarding...\n"); releaseToPool(p); - } else { - // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it - if (router.send(p) != ERRNO_OK) { - DEBUG_MSG("No radio was able to send packet, discarding...\n"); - releaseToPool(p); - } } } -MeshPacket *MeshService::allocForSending() -{ - MeshPacket *p = packetPool.allocZeroed(); - - p->which_payload = MeshPacket_decoded_tag; // Assume payload is decoded at start. - p->from = nodeDB.getNodeNum(); - p->to = NODENUM_BROADCAST; - p->hop_limit = HOP_RELIABLE; - p->id = generatePacketId(); - p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp - - return p; -} - void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies) { NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum()); @@ -311,7 +272,7 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies) assert(node->has_position); // Update our local node info with our position (even if we don't decide to update anyone else) - MeshPacket *p = allocForSending(); + MeshPacket *p = router.allocForSending(); p->to = dest; p->decoded.which_payload = SubPacket_position_tag; p->decoded.position = node->position; @@ -325,7 +286,7 @@ int MeshService::onGPSChanged(void *unused) // DEBUG_MSG("got gps notify\n"); // Update our local node info with our position (even if we don't decide to update anyone else) - MeshPacket *p = allocForSending(); + MeshPacket *p = router.allocForSending(); p->decoded.which_payload = SubPacket_position_tag; Position &pos = p->decoded.position; diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index f3328225..f6e688e1 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -67,9 +67,6 @@ class MeshService /// The owner User record just got updated, update our node DB and broadcast the info into the mesh void reloadOwner() { sendOurOwner(); } - /// Allocate and return a meshpacket which defaults as send to broadcast from the current node. - MeshPacket *allocForSending(); - /// Called when the user wakes up our GUI, normally sends our latest location to the mesh (if we have it), otherwise at least /// sends our owner void sendNetworkPing(NodeNum dest, bool wantReplies = false); diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp new file mode 100644 index 00000000..6ea884ca --- /dev/null +++ b/src/mesh/ReliableRouter.cpp @@ -0,0 +1,99 @@ +#include "ReliableRouter.h" +#include "MeshTypes.h" +#include "configuration.h" +#include "mesh-pb-constants.h" + +// ReliableRouter::ReliableRouter() {} + +/** + * If the message is want_ack, then add it to a list of packets to retransmit. + * If we run out of retransmissions, send a nak packet towards the original client to indicate failure. + */ +ErrorCode ReliableRouter::send(MeshPacket *p) +{ + if (p->want_ack) { + auto copy = packetPool.allocCopy(*p); + startRetransmission(copy); + } + + return FloodingRouter::send(p); +} + +/** + * If we receive a want_ack packet (do not check for wasSeenRecently), send back an ack (this might generate multiple ack sends in + * case the our first ack gets lost) + * + * If we receive an ack packet (do check wasSeenRecently), clear out any retransmissions and + * forward the ack to the application layer. + * + * If we receive a nak packet (do check wasSeenRecently), clear out any retransmissions + * and forward the nak to the application layer. + * + * Otherwise, let superclass handle it. + */ +void ReliableRouter::handleReceived(MeshPacket *p) +{ + if (p->to == getNodeNum()) { // ignore ack/nak/want_ack packets that are not address to us (for now) + if (p->want_ack) { + sendAckNak(true, p->from, p->id); + } + + if (perhapsDecode(p)) { + // If the payload is valid, look for ack/nak + + PacketId ackId = p->decoded.which_ack == SubPacket_success_id_tag ? p->decoded.ack.success_id : 0; + PacketId nakId = p->decoded.which_ack == SubPacket_fail_id_tag ? p->decoded.ack.fail_id : 0; + + // we are careful to only read/update wasSeenRecently _after_ confirming this is an ack (to not mess + // up broadcasts) + if ((ackId || nakId) && !wasSeenRecently(p)) { + if (ackId) { + DEBUG_MSG("Received a ack=%d, stopping retransmissions\n", ackId); + stopRetransmission(p->to, ackId); + } else { + DEBUG_MSG("Received a nak=%d, stopping retransmissions\n", nakId); + stopRetransmission(p->to, nakId); + } + } + } + } + + // handle the packet as normal + FloodingRouter::handleReceived(p); +} + +/** + * Send an ack or a nak packet back towards whoever sent idFrom + */ +void ReliableRouter::sendAckNak(bool isAck, NodeNum to, PacketId idFrom) +{ + DEBUG_MSG("Sending an ack=%d,to=%d,idFrom=%d", isAck, to, idFrom); + auto p = allocForSending(); + p->hop_limit = 0; // Assume just immediate neighbors for now + p->to = to; + + if (isAck) { + p->decoded.ack.success_id = idFrom; + p->decoded.which_ack = SubPacket_success_id_tag; + } else { + p->decoded.ack.fail_id = idFrom; + p->decoded.which_ack = SubPacket_fail_id_tag; + } + + send(p); +} + +/** + * Stop any retransmissions we are doing of the specified node/packet ID pair + */ +void ReliableRouter::stopRetransmission(NodeNum from, PacketId id) {} + +/** + * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. + */ +void ReliableRouter::startRetransmission(MeshPacket *p) {} + +/** + * Do any retransmissions that are scheduled (FIXME - for the time being called from loop) + */ +void ReliableRouter::doRetransmissions() {} \ No newline at end of file diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h new file mode 100644 index 00000000..75bedfb6 --- /dev/null +++ b/src/mesh/ReliableRouter.h @@ -0,0 +1,63 @@ +#pragma once + +#include "FloodingRouter.h" +#include "PeriodicTask.h" + +/** + * This is a mixin that extends Router with the ability to do (one hop only) reliable message sends. + */ +class ReliableRouter : public FloodingRouter +{ + private: + public: + /** + * Constructor + * + */ + // ReliableRouter(); + + /** + * Send a packet on a suitable interface. This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ + virtual ErrorCode send(MeshPacket *p); + + /** Do our retransmission handling */ + virtual void loop() + { + doRetransmissions(); + FloodingRouter::loop(); + } + + protected: + /** + * Called from loop() + * Handle any packet that is received by an interface on this node. + * Note: some packets may merely being passed through this node and will be forwarded elsewhere. + * + * Note: this method will free the provided packet + */ + virtual void handleReceived(MeshPacket *p); + + private: + /** + * Send an ack or a nak packet back towards whoever sent idFrom + */ + void sendAckNak(bool isAck, NodeNum to, PacketId idFrom); + + /** + * Stop any retransmissions we are doing of the specified node/packet ID pair + */ + void stopRetransmission(NodeNum from, PacketId id); + + /** + * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. + */ + void startRetransmission(MeshPacket *p); + + /** + * Do any retransmissions that are scheduled (FIXME - for the time being called from loop) + */ + void doRetransmissions(); +}; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 21b928f2..a7d57078 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -44,6 +44,39 @@ void Router::loop() } } +#define NUM_PACKET_ID 255 // 0 is consider invalid + +/// Generate a unique packet id +// FIXME, move this someplace better +PacketId generatePacketId() +{ + static uint32_t i; // Note: trying to keep this in noinit didn't help for working across reboots + static bool didInit = false; + + if (!didInit) { + didInit = true; + i = random(0, NUM_PACKET_ID + + 1); // pick a random initial sequence number at boot (to prevent repeated reboots always starting at 0) + } + + i++; + return (i % NUM_PACKET_ID) + 1; // return number between 1 and 255 +} + +MeshPacket *Router::allocForSending() +{ + MeshPacket *p = packetPool.allocZeroed(); + + p->which_payload = MeshPacket_decoded_tag; // Assume payload is decoded at start. + p->from = nodeDB.getNodeNum(); + p->to = NODENUM_BROADCAST; + p->hop_limit = HOP_RELIABLE; + p->id = generatePacketId(); + p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp + + return p; +} + /** * Send a packet on a suitable interface. This routine will * later free() the packet to pool. This routine is not allowed to stall. @@ -51,33 +84,40 @@ void Router::loop() */ ErrorCode Router::send(MeshPacket *p) { - // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) - - assert(p->which_payload == MeshPacket_encrypted_tag || - p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now - - // First convert from protobufs to raw bytes - if (p->which_payload == MeshPacket_decoded_tag) { - static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union - - size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded); - - assert(numbytes <= MAX_RHPACKETLEN); - crypto->encrypt(p->from, p->id, numbytes, bytes); - - // Copy back into the packet and set the variant type - memcpy(p->encrypted.bytes, bytes, numbytes); - p->encrypted.size = numbytes; - p->which_payload = MeshPacket_encrypted_tag; - } - - if (iface) { - // DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - return iface->send(p); - } else { - DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + // If this packet was destined only to apps on our node, don't send it out into the network + if (p->to == nodeDB.getNodeNum()) { + DEBUG_MSG("Dropping locally processed message\n"); packetPool.release(p); - return ERRNO_NO_INTERFACES; + return ERRNO_OK; + } else { + // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) + + assert(p->which_payload == MeshPacket_encrypted_tag || + p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now + + // First convert from protobufs to raw bytes + if (p->which_payload == MeshPacket_decoded_tag) { + static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union + + size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded); + + assert(numbytes <= MAX_RHPACKETLEN); + crypto->encrypt(p->from, p->id, numbytes, bytes); + + // Copy back into the packet and set the variant type + memcpy(p->encrypted.bytes, bytes, numbytes); + p->encrypted.size = numbytes; + p->which_payload = MeshPacket_encrypted_tag; + } + + if (iface) { + // DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + return iface->send(p); + } else { + DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + packetPool.release(p); + return ERRNO_NO_INTERFACES; + } } } @@ -90,18 +130,12 @@ void Router::sniffReceived(MeshPacket *p) DEBUG_MSG("Sniffing packet not sent to us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); } -/** - * Handle any packet that is received by an interface on this node. - * Note: some packets may merely being passed through this node and will be forwarded elsewhere. - */ -void Router::handleReceived(MeshPacket *p) +bool Router::perhapsDecode(MeshPacket *p) { - // FIXME, this class shouldn't EVER need to know about the GPS, move getValidTime() into a non gps dependent function - // Also, we should set the time from the ISR and it should have msec level resolution - p->rx_time = getValidTime(); // store the arrival timestamp for the phone + if (p->which_payload == MeshPacket_decoded_tag) + return true; // If packet was already decoded just return - assert(p->which_payload == - MeshPacket_encrypted_tag); // I _think_ the only thing that pushes to us is raw devices that just received packets + assert(p->which_payload == MeshPacket_encrypted_tag); // FIXME - someday don't send routing packets encrypted. That would allow us to route for other channels without // being able to decrypt their data. @@ -113,14 +147,37 @@ void Router::handleReceived(MeshPacket *p) // Take those raw bytes and convert them back into a well structured protobuf we can understand if (!pb_decode_from_bytes(bytes, p->encrypted.size, SubPacket_fields, &p->decoded)) { - DEBUG_MSG("Invalid protobufs in received mesh packet, discarding.\n"); + DEBUG_MSG("Invalid protobufs in received mesh packet!\n"); + return false; } else { - // parsing was successful, queue for our recipient + // parsing was successful p->which_payload = MeshPacket_decoded_tag; + return true; + } +} + +NodeNum Router::getNodeNum() +{ + return nodeDB.getNodeNum(); +} + +/** + * Handle any packet that is received by an interface on this node. + * Note: some packets may merely being passed through this node and will be forwarded elsewhere. + */ +void Router::handleReceived(MeshPacket *p) +{ + // FIXME, this class shouldn't EVER need to know about the GPS, move getValidTime() into a non gps dependent function + // Also, we should set the time from the ISR and it should have msec level resolution + p->rx_time = getValidTime(); // store the arrival timestamp for the phone + + // Take those raw bytes and convert them back into a well structured protobuf we can understand + if (perhapsDecode(p)) { + // parsing was successful, queue for our recipient sniffReceived(p); - uint8_t ourAddr = nodeDB.getNodeNum(); - if (p->to == NODENUM_BROADCAST || p->to == ourAddr) { + + if (p->to == NODENUM_BROADCAST || p->to == getNodeNum()) { DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); notifyPacketReceived.notifyObservers(p); } diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 03d75d33..d0a8e029 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -44,7 +44,7 @@ class Router * do idle processing * Mostly looking in our incoming rxPacket queue and calling handleReceived. */ - void loop(); + virtual void loop(); /** * Send a packet on a suitable interface. This routine will @@ -53,6 +53,13 @@ class Router */ virtual ErrorCode send(MeshPacket *p); + /// Allocate and return a meshpacket which defaults as send to broadcast from the current node. + MeshPacket *allocForSending(); + + /** + * @return our local nodenum */ + NodeNum getNodeNum(); + protected: /** * Called from loop() @@ -65,10 +72,21 @@ class Router virtual void handleReceived(MeshPacket *p); /** - * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to + * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to * update routing tables etc... based on what we overhear (even for messages not destined to our node) */ virtual void sniffReceived(MeshPacket *p); + + /** + * Remove any encryption and decode the protobufs inside this packet (if necessary). + * + * @return true for success, false for corrupt packet. + */ + bool perhapsDecode(MeshPacket *p); }; -extern Router &router; \ No newline at end of file +extern Router &router; + +/// Generate a unique packet id +// FIXME, move this someplace better +PacketId generatePacketId(); \ No newline at end of file From 6ba960ce47229a1d34235cd718a0c66d6073f7f6 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 19 May 2020 14:54:47 -0700 Subject: [PATCH 14/20] one hop reliable ready for testing --- docs/software/mesh-alg.md | 21 ++++++++----- src/mesh/ReliableRouter.cpp | 61 +++++++++++++++++++++++++++++++++++-- src/mesh/ReliableRouter.h | 51 +++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 10 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index 4bf2afa3..fe5d9752 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -8,12 +8,13 @@ reliable messaging tasks (stage one for DSR): - DONE add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as three bits in the header. - DONE add a 'snoopReceived' hook for all messages that pass through our node. - DONE use the same 'recentmessages' array used for broadcast msgs to detect duplicate retransmitted messages. -- in the router receive path?, send an ack packet if want_ack was set and we are the final destination. FIXME, for now don't handle multihop or merging of data replies with these acks. -- keep a list of packets waiting for acks -- for each message keep a count of # retries (max of three). Local to the node, only for the most immediate hop, ignorant of multihop routing. -- delay some random time for each retry (large enough to allow for acks to come in) -- once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender -- after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service) +- DONE in the router receive path?, send an ack packet if want_ack was set and we are the final destination. FIXME, for now don't handle multihop or merging of data replies with these acks. +- DONE keep a list of packets waiting for acks +- DONE for each message keep a count of # retries (max of three). Local to the node, only for the most immediate hop, ignorant of multihop routing. +- DONE delay some random time for each retry (large enough to allow for acks to come in) +- DONE once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender +- DONE after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service) +- test one hop ack/nak with the python framework dsr tasks @@ -21,9 +22,15 @@ dsr tasks - when sending, if destnodeinfo.next_hop is zero (and no message is already waiting for an arp for that node), startRouteDiscovery() for that node. Queue the message in the 'waiting for arp queue' so we can send it later when then the arp completes. - otherwise, use next_hop and start sending a message (with ack request) towards that node. - Don't use broadcasts for the network pings (close open github issue) +- add ignoreSenders to myNodeInfo to allow testing different mesh topologies by refusing to see certain senders +- test multihop delivery with the python framework -optimizations: +optimizations / low priority: +- low priority: think more careful about reliable retransmit intervals +- make ReliableRouter.pending threadsafe +- bump up PacketPool size for all the new ack/nak/routing packets +- handle 51 day rollover in doRetransmissions - use a priority queue for the messages waiting to send. Send acks first, then routing messages, then data messages, then broadcasts? when we receive any packet diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 6ea884ca..02833f8c 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -83,17 +83,72 @@ void ReliableRouter::sendAckNak(bool isAck, NodeNum to, PacketId idFrom) send(p); } +#define NUM_RETRANSMISSIONS 3 + +PendingPacket::PendingPacket(MeshPacket *p) +{ + packet = p; + numRetransmissions = NUM_RETRANSMISSIONS - 1; // We subtract one, because we assume the user just did the first send + setNextTx(); +} + /** * Stop any retransmissions we are doing of the specified node/packet ID pair */ -void ReliableRouter::stopRetransmission(NodeNum from, PacketId id) {} +void ReliableRouter::stopRetransmission(NodeNum from, PacketId id) +{ + auto key = GlobalPacketId(from, id); + stopRetransmission(key); +} +void ReliableRouter::stopRetransmission(GlobalPacketId key) +{ + auto old = pending.find(key); // If we have an old record, someone messed up because id got reused + if (old != pending.end()) { + auto numErased = pending.erase(key); + assert(numErased == 1); + packetPool.release(old->second.packet); + } +} /** * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. */ -void ReliableRouter::startRetransmission(MeshPacket *p) {} +void ReliableRouter::startRetransmission(MeshPacket *p) +{ + auto id = GlobalPacketId(p); + auto rec = PendingPacket(p); + + stopRetransmission(p->from, p->id); + pending[id] = rec; +} /** * Do any retransmissions that are scheduled (FIXME - for the time being called from loop) */ -void ReliableRouter::doRetransmissions() {} \ No newline at end of file +void ReliableRouter::doRetransmissions() +{ + uint32_t now = millis(); + + // FIXME, we should use a better datastructure rather than walking through this map. + // for(auto el: pending) { + for (auto it = pending.begin(), nextIt = it; it != pending.end(); it = nextIt) { + ++nextIt; // we use this odd pattern because we might be deleting it... + auto &p = it->second; + + // FIXME, handle 51 day rolloever here!!! + if (p.nextTxMsec <= now) { + if (p.numRetransmissions == 0) { + DEBUG_MSG("Reliable send failed, returning a nak\n"); + sendAckNak(false, p.packet->from, p.packet->id); + stopRetransmission(it->first); + } else { + DEBUG_MSG("Sending reliable retransmission\n"); + send(packetPool.allocCopy(*p.packet)); + + // Queue again + --p.numRetransmissions; + p.setNextTx(); + } + } + } +} \ No newline at end of file diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h index 75bedfb6..3798d9d6 100644 --- a/src/mesh/ReliableRouter.h +++ b/src/mesh/ReliableRouter.h @@ -2,6 +2,54 @@ #include "FloodingRouter.h" #include "PeriodicTask.h" +#include + +/** + * An identifier for a globalally unique message - a pair of the sending nodenum and the packet id assigned + * to that message + */ +struct GlobalPacketId { + NodeNum node; + PacketId id; + + bool operator==(const GlobalPacketId &p) const { return node == p.node && id == p.id; } + + GlobalPacketId(const MeshPacket *p) + { + node = p->from; + id = p->id; + } + + GlobalPacketId(NodeNum _from, PacketId _id) + { + node = _from; + id = _id; + } +}; + +/** + * A packet queued for retransmission + */ +struct PendingPacket { + MeshPacket *packet; + + /** The next time we should try to retransmit this packet */ + uint32_t nextTxMsec; + + /** Starts at NUM_RETRANSMISSIONS -1(normally 3) and counts down. Once zero it will be removed from the list */ + uint8_t numRetransmissions; + + PendingPacket() {} + PendingPacket(MeshPacket *p); + + void setNextTx() { nextTxMsec = millis() + random(10 * 1000, 12 * 1000); } +}; + +class GlobalPacketIdHashFunction +{ + public: + size_t operator()(const GlobalPacketId &p) const { return (hash()(p.node)) ^ (hash()(p.id)); } +}; /** * This is a mixin that extends Router with the ability to do (one hop only) reliable message sends. @@ -9,6 +57,8 @@ class ReliableRouter : public FloodingRouter { private: + unordered_map pending; + public: /** * Constructor @@ -50,6 +100,7 @@ class ReliableRouter : public FloodingRouter * Stop any retransmissions we are doing of the specified node/packet ID pair */ void stopRetransmission(NodeNum from, PacketId id); + void stopRetransmission(GlobalPacketId p); /** * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. From c65b518432a091367f93ced26e63a630cc3b7e0c Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 19 May 2020 14:54:58 -0700 Subject: [PATCH 15/20] less logspam --- src/gps/UBloxGPS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gps/UBloxGPS.cpp b/src/gps/UBloxGPS.cpp index 560c52fa..ca8f955c 100644 --- a/src/gps/UBloxGPS.cpp +++ b/src/gps/UBloxGPS.cpp @@ -87,7 +87,7 @@ void UBloxGPS::doTask() // 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 // fixtype = ublox.getFixType(); - DEBUG_MSG("fix type %d\n", fixtype); + // DEBUG_MSG("fix type %d\n", fixtype); // DEBUG_MSG("sec %d\n", ublox.getSecond()); // DEBUG_MSG("lat %d\n", ublox.getLatitude()); From 71041e86742baf93b363620e15334db0c3ff897b Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 19 May 2020 15:51:07 -0700 Subject: [PATCH 16/20] reliable unicast 1 hop works! --- docs/software/mesh-alg.md | 5 +++-- src/mesh/PacketHistory.cpp | 19 +++++++++++-------- src/mesh/PacketHistory.h | 4 +++- src/mesh/RadioLibInterface.cpp | 2 +- src/mesh/ReliableRouter.cpp | 2 +- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index fe5d9752..78f5eba8 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -14,7 +14,8 @@ reliable messaging tasks (stage one for DSR): - DONE delay some random time for each retry (large enough to allow for acks to come in) - DONE once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender - DONE after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service) -- test one hop ack/nak with the python framework +- DONE test one hop ack/nak with the python framework +- Do stress test with acks dsr tasks @@ -22,7 +23,7 @@ dsr tasks - when sending, if destnodeinfo.next_hop is zero (and no message is already waiting for an arp for that node), startRouteDiscovery() for that node. Queue the message in the 'waiting for arp queue' so we can send it later when then the arp completes. - otherwise, use next_hop and start sending a message (with ack request) towards that node. - Don't use broadcasts for the network pings (close open github issue) -- add ignoreSenders to myNodeInfo to allow testing different mesh topologies by refusing to see certain senders +- add ignoreSenders to radioconfig to allow testing different mesh topologies by refusing to see certain senders - test multihop delivery with the python framework optimizations / low priority: diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index 9a5704f6..30a448f9 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -13,7 +13,7 @@ PacketHistory::PacketHistory() /** * Update recentBroadcasts and return true if we have already seen this packet */ -bool PacketHistory::wasSeenRecently(const MeshPacket *p) +bool PacketHistory::wasSeenRecently(const MeshPacket *p, bool withUpdate) { if (p->id == 0) { DEBUG_MSG("Ignoring message with zero id\n"); @@ -32,7 +32,8 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p) DEBUG_MSG("Found existing broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); // Update the time on this record to now - r.rxTimeMsec = now; + if (withUpdate) + r.rxTimeMsec = now; return true; } @@ -41,12 +42,14 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p) } // Didn't find an existing record, make one - PacketRecord r; - r.id = p->id; - r.sender = p->from; - r.rxTimeMsec = now; - recentPackets.push_back(r); - DEBUG_MSG("Adding broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + if (withUpdate) { + PacketRecord r; + r.id = p->id; + r.sender = p->from; + r.rxTimeMsec = now; + recentPackets.push_back(r); + DEBUG_MSG("Adding broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + } return false; } \ No newline at end of file diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h index 22470f4f..38edf7d7 100644 --- a/src/mesh/PacketHistory.h +++ b/src/mesh/PacketHistory.h @@ -61,6 +61,8 @@ class PacketHistory /** * Update recentBroadcasts and return true if we have already seen this packet + * + * @param withUpdate if true and not found we add an entry to recentPackets */ - bool wasSeenRecently(const MeshPacket *p); + bool wasSeenRecently(const MeshPacket *p, bool withUpdate = true); }; diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 5bf3b5ee..9d271071 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -315,7 +315,7 @@ void RadioLibInterface::handleReceiveInterrupt() /** start an immediate transmit */ void RadioLibInterface::startSend(MeshPacket *txp) { - DEBUG_MSG("Starting low level send from=0x%x, id=%u!\n", txp->from, txp->id); + DEBUG_MSG("Starting low level send from=0x%x, id=%u, want_ack=%d\n", txp->from, txp->id, txp->want_ack); setStandby(); // Cancel any already in process receives size_t numbytes = beginSending(txp); diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 02833f8c..eb6fc248 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -46,7 +46,7 @@ void ReliableRouter::handleReceived(MeshPacket *p) // we are careful to only read/update wasSeenRecently _after_ confirming this is an ack (to not mess // up broadcasts) - if ((ackId || nakId) && !wasSeenRecently(p)) { + if ((ackId || nakId) && !wasSeenRecently(p, false)) { if (ackId) { DEBUG_MSG("Received a ack=%d, stopping retransmissions\n", ackId); stopRetransmission(p->to, ackId); From 0271df0657ca28195644fd1306130c33aa13c41a Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 21 May 2020 12:47:08 -0700 Subject: [PATCH 17/20] add beginnings of full DSR routing --- src/mesh/DSRRouter.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++ src/mesh/DSRRouter.h | 39 ++++++++++++++++++++ src/mesh/Router.cpp | 9 +++-- src/mesh/Router.h | 2 +- 4 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 src/mesh/DSRRouter.cpp create mode 100644 src/mesh/DSRRouter.h diff --git a/src/mesh/DSRRouter.cpp b/src/mesh/DSRRouter.cpp new file mode 100644 index 00000000..00c2a80d --- /dev/null +++ b/src/mesh/DSRRouter.cpp @@ -0,0 +1,80 @@ +#include "DSRRouter.h" +#include "configuration.h" + +/* when we receive any packet + +- sniff and update tables (especially useful to find adjacent nodes). Update user, network and position info. +- if we need to route() that packet, resend it to the next_hop based on our nodedb. +- if it is broadcast or destined for our node, deliver locally +- handle routereply/routeerror/routediscovery messages as described below +- then free it + +routeDiscovery + +- if we've already passed through us (or is from us), then it ignore it +- use the nodes already mentioned in the request to update our routing table +- if they were looking for us, send back a routereply +- if max_hops is zero and they weren't looking for us, drop (FIXME, send back error - I think not though?) +- if we receive a discovery packet, we use it to populate next_hop (if needed) towards the requester (after decrementing max_hops) +- if we receive a discovery packet, and we have a next_hop in our nodedb for that destination we send a (reliable) we send a route +reply towards the requester + +when sending any reliable packet + +- if timeout doing retries, send a routeError (nak) message back towards the original requester. all nodes eavesdrop on that +packet and update their route caches. + +when we receive a routereply packet + +- update next_hop on the node, if the new reply needs fewer hops than the existing one (we prefer shorter paths). fixme, someday +use a better heuristic + +when we receive a routeError packet + +- delete the route for that failed recipient, restartRouteDiscovery() +- if we receive routeerror in response to a discovery, +- fixme, eventually keep caches of possible other routes. +*/ + +void DSRRouter::sniffReceived(const MeshPacket *p) +{ + + // FIXME, update nodedb + + // Handle route discovery packets (will be a broadcast message) + if (p->decoded.which_payload == SubPacket_request_tag) { + // FIXME - always start request with the senders nodenum + + if (weAreInRoute(p->decoded.request)) { + DEBUG_MSG("Ignoring a route request that contains us\n"); + } else { + updateRoutes(p->decoded.request, false); // Update our routing tables based on the route that came in so far on this request + + if (p->decoded.dest == getNodeNum()) { + // They were looking for us, send back a route reply (the sender address will be first in the list) + sendRouteReply(p->decoded.request); + } else { + // They were looking for someone else, forward it along (as a zero hop broadcast) + NodeNum nextHop = getNextHop(p->decoded.dest); + if (nextHop) { + // in our route cache, reply to the requester (the sender address will be first in the list) + sendRouteReply(p->decoded.request, nextHop); + } else { + // Not in our route cache, rebroadcast on their behalf (after adding ourselves to the request route) + resendRouteRequest(p); + } + } + } + } + + // Handle regular packets + if (p->to == getNodeNum()) { // Destined for us (at least for this hop) + + // We need to route this packet + if (p->decoded.dest != p->to) { + // FIXME + } + } + + return ReliableRouter::sniffReceived(p); +} \ No newline at end of file diff --git a/src/mesh/DSRRouter.h b/src/mesh/DSRRouter.h new file mode 100644 index 00000000..5ecbdc8e --- /dev/null +++ b/src/mesh/DSRRouter.h @@ -0,0 +1,39 @@ +#include "ReliableRouter.h" + +class DSRRouter : public ReliableRouter +{ + + protected: + /** + * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to + * update routing tables etc... based on what we overhear (even for messages not destined to our node) + */ + virtual void sniffReceived(const MeshPacket *p); + + private: + /** + * Does our node appear in the specified route + */ + bool weAreInRoute(const RouteDiscovery &route); + + /** + * Given a DSR route, use that route to update our DB of possible routes + **/ + void updateRoutes(const RouteDiscovery &route, bool reverse); + + /** + * send back a route reply (the sender address will be first in the list) + */ + void sendRouteReply(const RouteDiscovery &route, NodeNum toAppend = 0); + + /** + * Given a nodenum return the next node we should forward to if we want to reach that node. + * + * @return 0 if no route found + */ + NodeNum getNextHop(NodeNum dest); + + /** Not in our route cache, rebroadcast on their behalf (after adding ourselves to the request route) + */ + void resendRouteRequest(const MeshPacket *p); +}; \ No newline at end of file diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index a7d57078..428f19fe 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -90,6 +90,10 @@ ErrorCode Router::send(MeshPacket *p) packetPool.release(p); return ERRNO_OK; } else { + // Never set the want_ack flag on broadcast packets sent over the air. + if (p->to == NODENUM_BROADCAST) + p->want_ack = false; + // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) assert(p->which_payload == MeshPacket_encrypted_tag || @@ -125,9 +129,10 @@ ErrorCode Router::send(MeshPacket *p) * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to * update routing tables etc... based on what we overhear (even for messages not destined to our node) */ -void Router::sniffReceived(MeshPacket *p) +void Router::sniffReceived(const MeshPacket *p) { - DEBUG_MSG("Sniffing packet not sent to us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + DEBUG_MSG("FIXME-update-db Sniffing packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + } bool Router::perhapsDecode(MeshPacket *p) diff --git a/src/mesh/Router.h b/src/mesh/Router.h index d0a8e029..0f06ce3e 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -75,7 +75,7 @@ class Router * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to * update routing tables etc... based on what we overhear (even for messages not destined to our node) */ - virtual void sniffReceived(MeshPacket *p); + virtual void sniffReceived(const MeshPacket *p); /** * Remove any encryption and decode the protobufs inside this packet (if necessary). From e2cbccb1336dee6c850ef73d8938e58b4dea96ac Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 21 May 2020 12:47:41 -0700 Subject: [PATCH 18/20] add want_ack support for broadcast packets --- docs/software/mesh-alg.md | 19 +++++++++++++------ proto | 2 +- src/mesh/FloodingRouter.cpp | 2 +- src/mesh/ReliableRouter.cpp | 20 ++++++++++++++++---- src/mesh/ReliableRouter.h | 12 ++++++++++-- 5 files changed, 41 insertions(+), 14 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index 78f5eba8..d4ae7121 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -2,6 +2,10 @@ great source of papers and class notes: http://www.cs.jhu.edu/~cs647/ +flood routing improvements + +- DONE if we don't see anyone rebroadcast our want_ack=true broadcasts, retry as needed. + reliable messaging tasks (stage one for DSR): - DONE generalize naive flooding @@ -19,9 +23,6 @@ reliable messaging tasks (stage one for DSR): dsr tasks -- do "hop by hop" routing -- when sending, if destnodeinfo.next_hop is zero (and no message is already waiting for an arp for that node), startRouteDiscovery() for that node. Queue the message in the 'waiting for arp queue' so we can send it later when then the arp completes. -- otherwise, use next_hop and start sending a message (with ack request) towards that node. - Don't use broadcasts for the network pings (close open github issue) - add ignoreSenders to radioconfig to allow testing different mesh topologies by refusing to see certain senders - test multihop delivery with the python framework @@ -34,6 +35,12 @@ optimizations / low priority: - handle 51 day rollover in doRetransmissions - use a priority queue for the messages waiting to send. Send acks first, then routing messages, then data messages, then broadcasts? +when we send a packet + +- do "hop by hop" routing +- when sending, if destnodeinfo.next_hop is zero (and no message is already waiting for an arp for that node), startRouteDiscovery() for that node. Queue the message in the 'waiting for arp queue' so we can send it later when then the arp completes. +- otherwise, use next_hop and start sending a message (with ack request) towards that node (starting with next_hop). + when we receive any packet - sniff and update tables (especially useful to find adjacent nodes). Update user, network and position info. @@ -47,13 +54,13 @@ routeDiscovery - if we've already passed through us (or is from us), then it ignore it - use the nodes already mentioned in the request to update our routing table - if they were looking for us, send back a routereply -- if max_hops is zero and they weren't looking for us, drop (FIXME, send back error - I think not though?) -- if we receive a discovery packet, we use it to populate next_hop (if needed) towards the requester (after decrementing max_hops) +- NOT DOING FOR NOW -if max_hops is zero and they weren't looking for us, drop (FIXME, send back error - I think not though?) +- if we receive a discovery packet, and we don't have next_hop set in our nodedb, we use it to populate next_hop (if needed) towards the requester (after decrementing max_hops) - if we receive a discovery packet, and we have a next_hop in our nodedb for that destination we send a (reliable) we send a route reply towards the requester when sending any reliable packet -- if we get back a nak, send a routeError message back towards the original requester. all nodes eavesdrop on that packet and update their route caches +- if timeout doing retries, send a routeError (nak) message back towards the original requester. all nodes eavesdrop on that packet and update their route caches. when we receive a routereply packet diff --git a/proto b/proto index e095ea92..bfae47bd 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit e095ea92e62edc3f5dd6864c3d08d113fd8842e2 +Subproject commit bfae47bdc0da23bb1e53fed054d3de2d161389bc diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index f16405e4..d3cc5cbd 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -11,7 +11,7 @@ FloodingRouter::FloodingRouter() {} */ ErrorCode FloodingRouter::send(MeshPacket *p) { - // Add any messages _we_ send to the seen message list + // Add any messages _we_ send to the seen message list (so we will ignore all retransmissions we see) wasSeenRecently(p); // FIXME, move this to a sniffSent method return Router::send(p); diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index eb6fc248..e14355ec 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -33,7 +33,17 @@ ErrorCode ReliableRouter::send(MeshPacket *p) */ void ReliableRouter::handleReceived(MeshPacket *p) { - if (p->to == getNodeNum()) { // ignore ack/nak/want_ack packets that are not address to us (for now) + NodeNum ourNode = getNodeNum(); + + if (p->from == ourNode && p->to == NODENUM_BROADCAST) { + // We are seeing someone rebroadcast one of our broadcast attempts. + // If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for + // the original sending process. + if (stopRetransmission(p->from, p->id)) { + DEBUG_MSG("Someone is retransmitting for us, generate implicit ack"); + sendAckNak(true, p->from, p->id); + } + } else if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (for now) if (p->want_ack) { sendAckNak(true, p->from, p->id); } @@ -95,20 +105,22 @@ PendingPacket::PendingPacket(MeshPacket *p) /** * Stop any retransmissions we are doing of the specified node/packet ID pair */ -void ReliableRouter::stopRetransmission(NodeNum from, PacketId id) +bool ReliableRouter::stopRetransmission(NodeNum from, PacketId id) { auto key = GlobalPacketId(from, id); stopRetransmission(key); } -void ReliableRouter::stopRetransmission(GlobalPacketId key) +bool ReliableRouter::stopRetransmission(GlobalPacketId key) { auto old = pending.find(key); // If we have an old record, someone messed up because id got reused if (old != pending.end()) { auto numErased = pending.erase(key); assert(numErased == 1); packetPool.release(old->second.packet); - } + return true; + } else + return false; } /** * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h index 3798d9d6..2217c92c 100644 --- a/src/mesh/ReliableRouter.h +++ b/src/mesh/ReliableRouter.h @@ -39,6 +39,12 @@ struct PendingPacket { /** Starts at NUM_RETRANSMISSIONS -1(normally 3) and counts down. Once zero it will be removed from the list */ uint8_t numRetransmissions; + /** True if we have started trying to find a route - for DSR usage + * While trying to find a route we don't actually send the data packet. We just leave it here pending until + * we have a route or we've failed to find one. + */ + bool wantRoute = false; + PendingPacket() {} PendingPacket(MeshPacket *p); @@ -98,9 +104,11 @@ class ReliableRouter : public FloodingRouter /** * Stop any retransmissions we are doing of the specified node/packet ID pair + * + * @return true if we found and removed a transmission with this ID */ - void stopRetransmission(NodeNum from, PacketId id); - void stopRetransmission(GlobalPacketId p); + bool stopRetransmission(NodeNum from, PacketId id); + bool stopRetransmission(GlobalPacketId p); /** * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. From e75561016b5f94850abbae90b0498aeac061c5d8 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 21 May 2020 15:55:57 -0700 Subject: [PATCH 19/20] retransmissions work again --- src/mesh/PacketHistory.cpp | 4 ++-- src/mesh/ReliableRouter.cpp | 17 ++++++++++++----- src/mesh/ReliableRouter.h | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index 30a448f9..7361daad 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -29,7 +29,7 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p, bool withUpdate) recentPackets.erase(recentPackets.begin() + i); // delete old record } else { if (r.id == p->id && r.sender == p->from) { - DEBUG_MSG("Found existing broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + DEBUG_MSG("Found existing packet record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); // Update the time on this record to now if (withUpdate) @@ -48,7 +48,7 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p, bool withUpdate) r.sender = p->from; r.rxTimeMsec = now; recentPackets.push_back(r); - DEBUG_MSG("Adding broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + DEBUG_MSG("Adding packet record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); } return false; diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index e14355ec..78c6b93a 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -36,6 +36,8 @@ void ReliableRouter::handleReceived(MeshPacket *p) NodeNum ourNode = getNodeNum(); if (p->from == ourNode && p->to == NODENUM_BROADCAST) { + DEBUG_MSG("Received someone rebroadcasting for us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + // We are seeing someone rebroadcast one of our broadcast attempts. // If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for // the original sending process. @@ -77,7 +79,7 @@ void ReliableRouter::handleReceived(MeshPacket *p) */ void ReliableRouter::sendAckNak(bool isAck, NodeNum to, PacketId idFrom) { - DEBUG_MSG("Sending an ack=%d,to=%d,idFrom=%d", isAck, to, idFrom); + DEBUG_MSG("Sending an ack=%d,to=%d,idFrom=%d\n", isAck, to, idFrom); auto p = allocForSending(); p->hop_limit = 0; // Assume just immediate neighbors for now p->to = to; @@ -108,7 +110,7 @@ PendingPacket::PendingPacket(MeshPacket *p) bool ReliableRouter::stopRetransmission(NodeNum from, PacketId id) { auto key = GlobalPacketId(from, id); - stopRetransmission(key); + return stopRetransmission(key); } bool ReliableRouter::stopRetransmission(GlobalPacketId key) @@ -150,12 +152,17 @@ void ReliableRouter::doRetransmissions() // FIXME, handle 51 day rolloever here!!! if (p.nextTxMsec <= now) { if (p.numRetransmissions == 0) { - DEBUG_MSG("Reliable send failed, returning a nak\n"); + DEBUG_MSG("Reliable send failed, returning a nak fr=0x%x,to=0x%x,id=%d\n", p.packet->from, p.packet->to, + p.packet->id); sendAckNak(false, p.packet->from, p.packet->id); stopRetransmission(it->first); } else { - DEBUG_MSG("Sending reliable retransmission\n"); - send(packetPool.allocCopy(*p.packet)); + DEBUG_MSG("Sending reliable retransmission fr=0x%x,to=0x%x,id=%d, tries left=%d\n", p.packet->from, p.packet->to, + p.packet->id, p.numRetransmissions); + + // Note: we call the superclass version because we don't want to have our version of send() add a new + // retransmission record + FloodingRouter::send(packetPool.allocCopy(*p.packet)); // Queue again --p.numRetransmissions; diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h index 2217c92c..e63806af 100644 --- a/src/mesh/ReliableRouter.h +++ b/src/mesh/ReliableRouter.h @@ -48,7 +48,7 @@ struct PendingPacket { PendingPacket() {} PendingPacket(MeshPacket *p); - void setNextTx() { nextTxMsec = millis() + random(10 * 1000, 12 * 1000); } + void setNextTx() { nextTxMsec = millis() + random(30 * 1000, 22 * 1000); } }; class GlobalPacketIdHashFunction From 9dd88281afb4bd7da721ee72b03fe5ba4fdf7d32 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 21 May 2020 16:34:16 -0700 Subject: [PATCH 20/20] reliable broadcast now works --- src/mesh/MeshService.cpp | 2 +- src/mesh/ReliableRouter.cpp | 11 +++-- src/mesh/ReliableRouter.h | 2 +- src/mesh/Router.cpp | 80 +++++++++++++++++++------------------ src/mesh/Router.h | 13 ++++-- 5 files changed, 61 insertions(+), 47 deletions(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index ee2905ad..540ca7cb 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -247,7 +247,7 @@ void MeshService::sendToMesh(MeshPacket *p) } // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it - if (router.send(p) != ERRNO_OK) { + if (router.sendLocal(p) != ERRNO_OK) { DEBUG_MSG("No radio was able to send packet, discarding...\n"); releaseToPool(p); } diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 78c6b93a..0500f279 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -12,6 +12,11 @@ ErrorCode ReliableRouter::send(MeshPacket *p) { if (p->want_ack) { + // If someone asks for acks on broadcast, we need the hop limit to be at least one, so that first node that receives our + // message will rebroadcast + if (p->to == NODENUM_BROADCAST && p->hop_limit == 0) + p->hop_limit = 1; + auto copy = packetPool.allocCopy(*p); startRetransmission(copy); } @@ -42,7 +47,7 @@ void ReliableRouter::handleReceived(MeshPacket *p) // If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for // the original sending process. if (stopRetransmission(p->from, p->id)) { - DEBUG_MSG("Someone is retransmitting for us, generate implicit ack"); + DEBUG_MSG("Someone is retransmitting for us, generate implicit ack\n"); sendAckNak(true, p->from, p->id); } } else if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (for now) @@ -79,10 +84,10 @@ void ReliableRouter::handleReceived(MeshPacket *p) */ void ReliableRouter::sendAckNak(bool isAck, NodeNum to, PacketId idFrom) { - DEBUG_MSG("Sending an ack=%d,to=%d,idFrom=%d\n", isAck, to, idFrom); auto p = allocForSending(); p->hop_limit = 0; // Assume just immediate neighbors for now p->to = to; + DEBUG_MSG("Sending an ack=0x%x,to=0x%x,idFrom=%d,id=%d\n", isAck, to, idFrom, p->id); if (isAck) { p->decoded.ack.success_id = idFrom; @@ -92,7 +97,7 @@ void ReliableRouter::sendAckNak(bool isAck, NodeNum to, PacketId idFrom) p->decoded.which_ack = SubPacket_fail_id_tag; } - send(p); + sendLocal(p); // we sometimes send directly to the local node } #define NUM_RETRANSMISSIONS 3 diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h index e63806af..7030793a 100644 --- a/src/mesh/ReliableRouter.h +++ b/src/mesh/ReliableRouter.h @@ -48,7 +48,7 @@ struct PendingPacket { PendingPacket() {} PendingPacket(MeshPacket *p); - void setNextTx() { nextTxMsec = millis() + random(30 * 1000, 22 * 1000); } + void setNextTx() { nextTxMsec = millis() + random(20 * 1000, 22 * 1000); } }; class GlobalPacketIdHashFunction diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 428f19fe..0ef7b833 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -77,6 +77,16 @@ MeshPacket *Router::allocForSending() return p; } +ErrorCode Router::sendLocal(MeshPacket *p) +{ + if (p->to == nodeDB.getNodeNum()) { + DEBUG_MSG("Enqueuing internal message for the receive queue\n"); + fromRadioQueue.enqueue(p); + return ERRNO_OK; + } else + return send(p); +} + /** * Send a packet on a suitable interface. This routine will * later free() the packet to pool. This routine is not allowed to stall. @@ -84,44 +94,39 @@ MeshPacket *Router::allocForSending() */ ErrorCode Router::send(MeshPacket *p) { - // If this packet was destined only to apps on our node, don't send it out into the network - if (p->to == nodeDB.getNodeNum()) { - DEBUG_MSG("Dropping locally processed message\n"); - packetPool.release(p); - return ERRNO_OK; + assert(p->to != nodeDB.getNodeNum()); // should have already been handled by sendLocal + + // Never set the want_ack flag on broadcast packets sent over the air. + if (p->to == NODENUM_BROADCAST) + p->want_ack = false; + + // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) + + assert(p->which_payload == MeshPacket_encrypted_tag || + p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now + + // First convert from protobufs to raw bytes + if (p->which_payload == MeshPacket_decoded_tag) { + static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union + + size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded); + + assert(numbytes <= MAX_RHPACKETLEN); + crypto->encrypt(p->from, p->id, numbytes, bytes); + + // Copy back into the packet and set the variant type + memcpy(p->encrypted.bytes, bytes, numbytes); + p->encrypted.size = numbytes; + p->which_payload = MeshPacket_encrypted_tag; + } + + if (iface) { + // DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + return iface->send(p); } else { - // Never set the want_ack flag on broadcast packets sent over the air. - if (p->to == NODENUM_BROADCAST) - p->want_ack = false; - - // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) - - assert(p->which_payload == MeshPacket_encrypted_tag || - p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now - - // First convert from protobufs to raw bytes - if (p->which_payload == MeshPacket_decoded_tag) { - static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union - - size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded); - - assert(numbytes <= MAX_RHPACKETLEN); - crypto->encrypt(p->from, p->id, numbytes, bytes); - - // Copy back into the packet and set the variant type - memcpy(p->encrypted.bytes, bytes, numbytes); - p->encrypted.size = numbytes; - p->which_payload = MeshPacket_encrypted_tag; - } - - if (iface) { - // DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - return iface->send(p); - } else { - DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - packetPool.release(p); - return ERRNO_NO_INTERFACES; - } + DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + packetPool.release(p); + return ERRNO_NO_INTERFACES; } } @@ -132,7 +137,6 @@ ErrorCode Router::send(MeshPacket *p) void Router::sniffReceived(const MeshPacket *p) { DEBUG_MSG("FIXME-update-db Sniffing packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - } bool Router::perhapsDecode(MeshPacket *p) diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 0f06ce3e..8c811667 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -47,11 +47,9 @@ class Router virtual void loop(); /** - * Send a packet on a suitable interface. This routine will - * later free() the packet to pool. This routine is not allowed to stall. - * If the txmit queue is full it might return an error + * Works like send, but if we are sending to the local node, we directly put the message in the receive queue */ - virtual ErrorCode send(MeshPacket *p); + ErrorCode sendLocal(MeshPacket *p); /// Allocate and return a meshpacket which defaults as send to broadcast from the current node. MeshPacket *allocForSending(); @@ -61,6 +59,13 @@ class Router NodeNum getNodeNum(); protected: + /** + * Send a packet on a suitable interface. This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ + virtual ErrorCode send(MeshPacket *p); + /** * Called from loop() * Handle any packet that is received by an interface on this node.