From 190a3c2d6bbc33153f3b3a461f4ca634b2100b3e Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 9 May 2020 20:27:08 -0700 Subject: [PATCH 01/25] filename typo --- docs/software/{cypto.md => crypto.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/software/{cypto.md => crypto.md} (100%) diff --git a/docs/software/cypto.md b/docs/software/crypto.md similarity index 100% rename from docs/software/cypto.md rename to docs/software/crypto.md From 2fa595523f53ba4389edc9aacdf582434e323237 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 9 May 2020 21:02:56 -0700 Subject: [PATCH 02/25] minor fixups to get nrf52 building again --- docs/README.md | 2 +- docs/software/crypto.md | 8 +++++--- src/mesh/FloodingRouter.cpp | 2 +- src/mesh/RadioLibInterface.cpp | 3 +++ src/mesh/StreamAPI.cpp | 2 +- src/nrf52/NRF52CryptoEngine.cpp | 5 +++++ 6 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 src/nrf52/NRF52CryptoEngine.cpp diff --git a/docs/README.md b/docs/README.md index a10ff9f6aa..47281dbebf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,7 +1,7 @@ # What is Meshtastic? Meshtastic is a project that lets you use -inexpensive (\$30 ish) GPS radios as an extensible, super long battery life mesh GPS communicator. These radios are great for hiking, skiing, paragliding - essentially any hobby where you don't have reliable internet access. Each member of your private mesh can always see the location and distance of all other members and any text messages sent to your group chat. +inexpensive (\$30 ish) GPS radios as an extensible, long battery life, secure, mesh GPS communicator. These radios are great for hiking, skiing, paragliding - essentially any hobby where you don't have reliable internet access. Each member of your private mesh can always see the location and distance of all other members and any text messages sent to your group chat. The radios automatically create a mesh to forward packets as needed, so everyone in the group can receive messages from even the furthest member. The radios will optionally work with your phone, but no phone is required. diff --git a/docs/software/crypto.md b/docs/software/crypto.md index f5f7d04a6c..7f5709c485 100644 --- a/docs/software/crypto.md +++ b/docs/software/crypto.md @@ -4,8 +4,8 @@ Cryptography is tricky, so we've tried to 'simply' apply standard crypto solutio the project developers are not cryptography experts. Therefore we ask two things: - If you are a cryptography expert, please review these notes and our questions below. Can you help us by reviewing our - notes below and offering advice? We will happily give as much or as little credit as you wish as our thanks ;-). -- Consider our existing solution 'alpha' and probably fairly secure against an not very aggressive adversary. But until + notes below and offering advice? We will happily give as much or as little credit as you wish ;-). +- Consider our existing solution 'alpha' and probably fairly secure against a not particularly aggressive adversary. But until it is reviewed by someone smarter than us, assume it might have flaws. ## Notes on implementation @@ -16,7 +16,7 @@ the project developers are not cryptography experts. Therefore we ask two things Parameters for our CTR implementation: -- Our AES key is 256 bits, shared as part of the 'Channel' specification. +- Our AES key is 128 or 256 bits, shared as part of the 'Channel' specification. - Each SubPacket will be sent as a series of 16 byte BLOCKS. - The node number concatenated with the packet number is used as the NONCE. This counter will be stored in flash in the device and should essentially never repeat. If the user makes a new 'Channel' (i.e. picking a new random 256 bit key), the packet number will start at zero. The packet number is sent in cleartext with each packet. The node number can be derived from the "from" field of each packet. @@ -35,4 +35,6 @@ Note that for both stategies, sizes are measured in blocks and that an AES block ## Remaining todo - Make the packet numbers 32 bit +- Confirm the packet #s are stored in flash across deep sleep (and otherwise in in RAM) +- Have the app change the crypto key when the user generates a new channel - Implement for NRF52 [NRF52](https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.0.0/lib_crypto_aes.html#sub_aes_ctr) diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index da7070172a..c1672c77e5 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -116,7 +116,7 @@ bool FloodingRouter::wasSeenRecently(const MeshPacket *p) } uint32_t now = millis(); - for (int i = 0; i < recentBroadcasts.size();) { + for (size_t i = 0; i < recentBroadcasts.size();) { BroadcastRecord &r = recentBroadcasts[i]; if ((now - r.rxTimeMsec) >= FLOOD_EXPIRE_TIME) { diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index dee630e0ce..e09c4f4a96 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -197,6 +197,9 @@ void RadioLibInterface::loop() #ifndef NO_ESP32 #define USE_HW_TIMER +#else +// Not needed on NRF52 +#define IRAM_ATTR #endif void IRAM_ATTR RadioLibInterface::timerCallback(void *p1, uint32_t p2) diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 00434f90b6..49ccb3d6b6 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -31,7 +31,7 @@ void StreamAPI::readStream() if (c != START2) rxPtr = 0; // failed to find framing } else if (ptr >= HEADER_LEN) { // we have at least read our 4 byte framing - uint16_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing + uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing if (ptr == HEADER_LEN) { // we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid diff --git a/src/nrf52/NRF52CryptoEngine.cpp b/src/nrf52/NRF52CryptoEngine.cpp new file mode 100644 index 0000000000..ee1650ea2a --- /dev/null +++ b/src/nrf52/NRF52CryptoEngine.cpp @@ -0,0 +1,5 @@ + +#include "CryptoEngine.h" + +// FIXME, do a NRF52 version +CryptoEngine *crypto = new CryptoEngine(); \ No newline at end of file From 8b911aba7ff5f0e5a4d33423ffdeee6cc0c2614f Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 10 May 2020 12:33:17 -0700 Subject: [PATCH 03/25] Cleanup build for NRF52 targets --- boards/nrf52840_dk_modified.json | 46 +++++++++++++++++ boards/ppr.json | 2 +- docs/software/mesh-alg.md | 5 +- docs/software/nrf52-TODO.md | 6 ++- platformio.ini | 26 +++++++--- src/configuration.h | 86 +++++++++++++++++++------------- src/main.cpp | 6 +++ variants/ppr/variant.cpp | 31 +++++------- variants/ppr/variant.h | 85 +++++++++++++++++++------------ 9 files changed, 194 insertions(+), 99 deletions(-) create mode 100644 boards/nrf52840_dk_modified.json diff --git a/boards/nrf52840_dk_modified.json b/boards/nrf52840_dk_modified.json new file mode 100644 index 0000000000..ce86e09f54 --- /dev/null +++ b/boards/nrf52840_dk_modified.json @@ -0,0 +1,46 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_PCA10056 -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [["0x239A", "0x4404"]], + "usb_product": "SimPPR", + "mcu": "nrf52840", + "variant": "pca10056-rc-clock", + "variants_dir": "variants", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": ["jlink"], + "svd_path": "nrf52840.svd" + }, + "frameworks": ["arduino"], + "name": "A modified NRF52840-DK devboard (Adafruit BSP)", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "require_upload_port": true, + "speed": 115200, + "protocol": "jlink", + "protocols": ["jlink", "nrfjprog", "stlink"] + }, + "url": "https://meshtastic.org/", + "vendor": "Nordic Semi" +} diff --git a/boards/ppr.json b/boards/ppr.json index 5050758f7d..5283fdc4e8 100644 --- a/boards/ppr.json +++ b/boards/ppr.json @@ -10,7 +10,7 @@ "hwids": [["0x239A", "0x4403"]], "usb_product": "PPR", "mcu": "nrf52840", - "variant": "pca10056-rc-clock", + "variant": "ppr", "variants_dir": "variants", "bsp": { "name": "adafruit" diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index d90d85c9ca..48bc6721f1 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -11,9 +11,9 @@ TODO: - DONE read about mesh routing solutions (DSR and AODV) - DONE read about general mesh flooding solutions (naive, MPR, geo assisted) - DONE reread the disaster radio protocol docs - seems based on Babel (which is AODVish) -- 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 +- 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 -- generalize naive flooding on top of radiohead or disaster.radio? (and fix radiohead to use my new driver) +- DONE generalize naive flooding 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 @@ -77,7 +77,6 @@ look into the literature for this idea specifically. FIXME, merge into the above: - good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 4f4b6c4092..69afcb9469 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -5,8 +5,6 @@ Minimum items needed to make sure hardware is good. - add a hard fault handler -- use "variants" to get all gpio bindings -- plug in correct variants for the real board - 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 @@ -62,6 +60,8 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At Nice ideas worth considering someday... +- 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 - make/find a multithread safe debug logging class (include remote logging and timestamps and levels). make each log event atomic. - turn on freertos stack size checking @@ -102,6 +102,8 @@ Nice ideas worth considering someday... - DONE remove unused sx1262 lib from github - at boot we are starting our message IDs at 1, rather we should start them at a random number. also, seed random based on timer. this could be the cause of our first message not seen bug. - 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 ``` diff --git a/platformio.ini b/platformio.ini index 61b61d6778..6441955008 100644 --- a/platformio.ini +++ b/platformio.ini @@ -122,11 +122,9 @@ board = ttgo-lora32-v1 build_flags = ${esp32_base.build_flags} -D TTGO_LORA_V2 - -; The NRF52840-dk development board -[env:nrf52dk] +; Common settings for NRF52 based targets +[nrf52_base] platform = nordicnrf52 -board = ppr framework = arduino debug_tool = jlink build_type = debug ; I'm debugging with ICE a lot now @@ -136,10 +134,6 @@ src_filter = ${env.src_filter} - lib_ignore = BluetoothOTA -lib_deps = - ${env.lib_deps} - UC1701 - https://github.com/meshtastic/BQ25703A.git monitor_port = /dev/ttyACM1 debug_extra_cmds = @@ -150,3 +144,19 @@ debug_init_break = ;debug_init_break = tbreak loop ;debug_init_break = tbreak Reset_Handler +; The NRF52840-dk development board +[env:nrf52dk] +extends = nrf52_base +board = nrf52840_dk_modified + +; The PPR board +[env:ppr] +extends = nrf52_base +board = ppr +lib_deps = + ${env.lib_deps} + UC1701 + https://github.com/meshtastic/BQ25703A.git + + + diff --git a/src/configuration.h b/src/configuration.h index b02a1a00f2..ca606c53ac 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -51,27 +51,31 @@ along with this program. If not, see . #define xstr(s) str(s) #define str(s) #s -// ----------------------------------------------------------------------------- -// OLED -// ----------------------------------------------------------------------------- +#ifdef NRF52840_XXAA // All of the NRF52 targets are configured using variant.h, so this section shouldn't need to be + // board specific -#define SSD1306_ADDRESS 0x3C +// +// Standard definitions for NRF52 targets +// -// Flip the screen upside down by default as it makes more sense on T-BEAM -// devices. Comment this out to not rotate screen 180 degrees. -#define FLIP_SCREEN_VERTICALLY +#define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth) -// DEBUG LED +// We bind to the GPS using variant.h instead for this platform (Serial1) -#define LED_INVERTED 0 // define as 1 if LED is active low (on) +// FIXME, not yet ready for NRF52 +#define RTC_DATA_ATTR -// ----------------------------------------------------------------------------- -// GPS -// ----------------------------------------------------------------------------- +#define LED_PIN PIN_LED1 // LED1 on nrf52840-DK +#define BUTTON_PIN PIN_BUTTON1 -#define GPS_SERIAL_NUM 1 -#define GPS_BAUDRATE 9600 +// FIXME, use variant.h defs for all of this!!! (even on the ESP32 targets) +#else + +// +// Standard definitions for ESP32 targets +// +#define GPS_SERIAL_NUM 1 #define GPS_RX_PIN 34 #ifdef USE_JTAG #define GPS_TX_PIN -1 @@ -88,6 +92,27 @@ along with this program. If not, see . #define MOSI_GPIO 27 #define NSS_GPIO 18 +#endif + +// ----------------------------------------------------------------------------- +// OLED +// ----------------------------------------------------------------------------- + +#define SSD1306_ADDRESS 0x3C + +// Flip the screen upside down by default as it makes more sense on T-BEAM +// devices. Comment this out to not rotate screen 180 degrees. +#define FLIP_SCREEN_VERTICALLY + +// DEBUG LED +#define LED_INVERTED 0 // define as 1 if LED is active low (on) + +// ----------------------------------------------------------------------------- +// GPS +// ----------------------------------------------------------------------------- + +#define GPS_BAUDRATE 9600 + #if defined(TBEAM_V10) // This string must exactly match the case used in release file names or the android updater won't work #define HW_VENDOR "tbeam" @@ -188,35 +213,22 @@ along with this program. If not, see . 0 // If defined, this will be used for user button presses, if your board doesn't have a physical switch, you can wire one // between this pin and ground -#define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio -#define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio -#define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number -#define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number -#elif defined(NRF52840_XXAA) // All of the NRF52 targets are configured using variant.h, so this section shouldn't need to be - // board specific +#define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio +#define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio +#define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number +#define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number +#endif -// FIXME, use variant.h defs for all of this!!! +#ifdef ARDUINO_NRF52840_PCA10056 // This string must exactly match the case used in release file names or the android updater won't work -#define HW_VENDOR "nrf52" - -#define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth) - -// We bind to the GPS using variant.h instead for this platform (Serial1) -#undef GPS_RX_PIN -#undef GPS_TX_PIN - -// FIXME, not yet ready for NRF52 -#define RTC_DATA_ATTR - -#define LED_PIN PIN_LED1 // LED1 on nrf52840-DK -#define BUTTON_PIN PIN_BUTTON1 +#define HW_VENDOR "nrf52dk" // This board uses 0 to be mean LED on #undef LED_INVERTED #define LED_INVERTED 1 -// Temporarily testing if we can build the RF95 driver for NRF52 +// Uncomment to confirm if we can build the RF95 driver for NRF52 #if 0 #define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio #define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio @@ -224,6 +236,10 @@ along with this program. If not, see . #define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number #endif +#elif defined(ARDUINO_NRF52840_PPR) + +#define HW_VENDOR "ppr" + #endif // ----------------------------------------------------------------------------- diff --git a/src/main.cpp b/src/main.cpp index b6c03846fd..a3c060b189 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -189,6 +189,8 @@ void setup() readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time) +// If we know we have a L80 GPS, don't try UBLOX +#ifndef L80_RESET // Init GPS - first try ublox gps = new UBloxGPS(); if (!gps->setup()) { @@ -199,6 +201,10 @@ void setup() gps = new NEMAGPS(); gps->setup(); } +#else + gps = new NEMAGPS(); + gps->setup(); +#endif service.init(); diff --git a/variants/ppr/variant.cpp b/variants/ppr/variant.cpp index bd85e97133..f5f219e9b0 100644 --- a/variants/ppr/variant.cpp +++ b/variants/ppr/variant.cpp @@ -19,31 +19,24 @@ */ #include "variant.h" +#include "nrf.h" #include "wiring_constants.h" #include "wiring_digital.h" -#include "nrf.h" -const uint32_t g_ADigitalPinMap[] = -{ - // P0 - 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , - 8 , 9 , 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, 26, 27, 28, 29, 30, 31, - - // P1 - 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 42, 43, 44, 45, 46, 47 -}; +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0xff, 12, 13, 0xff, 15, 0xff, 17, 18, 0xff, 20, 0xff, 22, 0xff, 24, 0xff, 26, 0xff, 28, 29, + 30, 31, + // P1 + 32, 0xff, 34, 0xff, 36, 0xff, 38, 0xff, 0xff, 41, 42, 43, 0xff, 45}; void initVariant() { - // LED1 & LED2 - pinMode(PIN_LED1, OUTPUT); - ledOff(PIN_LED1); + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2);; + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); } - diff --git a/variants/ppr/variant.h b/variants/ppr/variant.h index 5033a4ecc4..b792172ee7 100644 --- a/variants/ppr/variant.h +++ b/variants/ppr/variant.h @@ -39,40 +39,44 @@ extern "C" { #endif // __cplusplus // Number of pins defined in PinDescription array -#define PINS_COUNT (48) -#define NUM_DIGITAL_PINS (48) -#define NUM_ANALOG_INPUTS (6) +#define PINS_COUNT (46) +#define NUM_DIGITAL_PINS (46) +#define NUM_ANALOG_INPUTS (0) #define NUM_ANALOG_OUTPUTS (0) // LEDs -#define PIN_LED1 (13) -#define PIN_LED2 (14) +#define PIN_LED1 (0) +#define PIN_LED2 (1) #define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_RED PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_GREEN PIN_LED2 -#define LED_STATE_ON 0 // State when LED is litted +// FIXME, bluefruit automatically blinks this led while connected. call AdafruitBluefruit::autoConnLed to change this. +#define LED_BLUE LED_GREEN + +#define LED_STATE_ON 1 // State when LED is litted /* * Buttons */ -#define PIN_BUTTON1 11 -#define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 +#define PIN_BUTTON1 4 // center +#define PIN_BUTTON2 2 +#define PIN_BUTTON3 3 +#define PIN_BUTTON4 5 +#define PIN_BUTTON5 6 /* * Analog pins */ -#define PIN_A0 (3) -#define PIN_A1 (4) -#define PIN_A2 (28) -#define PIN_A3 (29) -#define PIN_A4 (30) -#define PIN_A5 (31) +#define PIN_A0 (0xff) +#define PIN_A1 (0xff) +#define PIN_A2 (0xff) +#define PIN_A3 (0xff) +#define PIN_A4 (0xff) +#define PIN_A5 (0xff) #define PIN_A6 (0xff) #define PIN_A7 (0xff) @@ -87,9 +91,9 @@ static const uint8_t A7 = PIN_A7; #define ADC_RESOLUTION 14 // Other pins -#define PIN_AREF (2) -#define PIN_NFC1 (9) -#define PIN_NFC2 (10) +#define PIN_AREF (0xff) +//#define PIN_NFC1 (9) +//#define PIN_NFC2 (10) static const uint8_t AREF = PIN_AREF; @@ -97,24 +101,24 @@ static const uint8_t AREF = PIN_AREF; * Serial interfaces */ -// Arduino Header D0, D1 -#define PIN_SERIAL1_RX (33) // P1.01 -#define PIN_SERIAL1_TX (34) // P1.02 +// GPS is on Serial1 +#define PIN_SERIAL1_RX (8) +#define PIN_SERIAL1_TX (9) // Connected to Jlink CDC -#define PIN_SERIAL2_RX (8) -#define PIN_SERIAL2_TX (6) +//#define PIN_SERIAL2_RX (8) +//#define PIN_SERIAL2_TX (6) /* * SPI Interfaces */ #define SPI_INTERFACES_COUNT 1 -#define PIN_SPI_MISO (46) -#define PIN_SPI_MOSI (45) -#define PIN_SPI_SCK (47) +#define PIN_SPI_MISO (15) +#define PIN_SPI_MOSI (13) +#define PIN_SPI_SCK (12) -static const uint8_t SS = 44; +// static const uint8_t SS = 44; static const uint8_t MOSI = PIN_SPI_MOSI; static const uint8_t MISO = PIN_SPI_MISO; static const uint8_t SCK = PIN_SPI_SCK; @@ -124,8 +128,27 @@ static const uint8_t SCK = PIN_SPI_SCK; */ #define WIRE_INTERFACES_COUNT 1 -#define PIN_WIRE_SDA (26) -#define PIN_WIRE_SCL (27) +#define PIN_WIRE_SDA (32 + 2) +#define PIN_WIRE_SCL (32) + +// CUSTOM GPIOs the SX1262 +#define SX1262_CS (10) +#define SX1262_DIO1 (20) +#define SX1262_DIO2 (26) +#define SX1262_BUSY (18) +#define SX1262_RESET (17) +// #define SX1262_ANT_SW (32 + 10) +#define SX1262_RXEN (22) +#define SX1262_TXEN (24) + +// ERC12864-10 LCD +#define ERC12864_CS (32 + 4) +#define ERC12864_RESET (32 + 6) +#define ERC12864_CD (32 + 9) + +// L80 GPS +#define L80_PPS (28) +#define L80_RESET (29) #ifdef __cplusplus } From c12fb69ca29126602a88dca61734d02676e557c6 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 10 May 2020 14:17:05 -0700 Subject: [PATCH 04/25] update protos --- docs/software/TODO.md | 20 +++++++++----------- proto | 2 +- src/mesh/mesh.pb.h | 38 +++++++++++++++++++------------------- variants/ppr/variant.h | 9 ++------- 4 files changed, 31 insertions(+), 38 deletions(-) diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 23efd6ee35..711f31f01f 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -5,21 +5,15 @@ Items to complete soon (next couple of alpha releases). - lower wait_bluetooth_secs to 30 seconds once we have the GPS power on (but GPS in sleep mode) across light sleep. For the time being I have it set at 2 minutes to ensure enough time for a GPS lock from scratch. -- remeasure wake time power draws now that we run CPU down at 80MHz - -# AXP192 tasks - -- figure out why this fixme is needed: "FIXME, disable wake due to PMU because it seems to fire all the time?" -- "AXP192 interrupt is not firing, remove this temporary polling of battery state" -- make debug info screen show real data (including battery level & charging) - close corresponding github issue - # Medium priority Items to complete before the first beta release. -- Don't store position packets in the to phone fifo if we are disconnected. The phone will get that info for 'free' when it -fetches the fresh nodedb. -- Use the RFM95 sequencer to stay in idle mode most of the time, then automatically go to receive mode and automatically go from transmit to receive mode. See 4.2.8.2 of manual. +- Use 32 bits for message IDs +- Use fixed32 for node IDs +- Don't store position packets in the to phone fifo if we are disconnected. The phone will get that info for 'free' when it + fetches the fresh nodedb. +- Use the RFM95 sequencer to stay in idle mode most of the time, then automatically go to receive mode and automatically go from transmit to receive mode. See 4.2.8.2 of manual. - possibly switch to https://github.com/SlashDevin/NeoGPS for gps comms - good source of battery/signal/gps icons https://materialdesignicons.com/ - research and implement better mesh algorithm - investigate changing routing to https://github.com/sudomesh/LoRaLayer2 ? @@ -204,3 +198,7 @@ Items after the first final candidate release. - enable fast lock and low power inside the gps chip - Make a FAQ - add a SF12 transmit option for _super_ long range +- figure out why this fixme is needed: "FIXME, disable wake due to PMU because it seems to fire all the time?" +- "AXP192 interrupt is not firing, remove this temporary polling of battery state" +- make debug info screen show real data (including battery level & charging) - close corresponding github issue +- remeasure wake time power draws now that we run CPU down at 80MHz diff --git a/proto b/proto index 5e2df6c998..4840493693 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 5e2df6c9986cd75f0af4eab1ba0d2aacf258aaab +Subproject commit 4840493693d5799ebd451f6857ecbbc5c9157348 diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index 9442bd25bf..be286b5031 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -69,9 +69,9 @@ typedef struct _MyNodeInfo { typedef struct _Position { int32_t altitude; int32_t battery_level; - uint32_t time; int32_t latitude_i; int32_t longitude_i; + uint32_t time; } Position; typedef struct _RadioConfig_UserPreferences { @@ -125,16 +125,16 @@ typedef struct _SubPacket { typedef PB_BYTES_ARRAY_T(256) MeshPacket_encrypted_t; typedef struct _MeshPacket { - int32_t from; - int32_t to; + uint32_t from; + uint32_t to; pb_size_t which_payload; union { SubPacket decoded; MeshPacket_encrypted_t encrypted; }; - uint32_t rx_time; uint32_t id; float rx_snr; + uint32_t rx_time; } MeshPacket; typedef struct _DeviceState { @@ -246,7 +246,7 @@ typedef struct _ToRadio { #define Position_longitude_i_tag 8 #define Position_altitude_tag 3 #define Position_battery_level_tag 4 -#define Position_time_tag 6 +#define Position_time_tag 9 #define RadioConfig_UserPreferences_position_broadcast_secs_tag 1 #define RadioConfig_UserPreferences_send_owner_interval_tag 2 #define RadioConfig_UserPreferences_num_missed_to_fail_tag 3 @@ -278,7 +278,7 @@ typedef struct _ToRadio { #define MeshPacket_encrypted_tag 8 #define MeshPacket_from_tag 1 #define MeshPacket_to_tag 2 -#define MeshPacket_rx_time_tag 4 +#define MeshPacket_rx_time_tag 9 #define MeshPacket_id_tag 6 #define MeshPacket_rx_snr_tag 7 #define DeviceState_radio_tag 1 @@ -305,9 +305,9 @@ typedef struct _ToRadio { #define Position_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, INT32, altitude, 3) \ X(a, STATIC, SINGULAR, INT32, battery_level, 4) \ -X(a, STATIC, SINGULAR, UINT32, time, 6) \ X(a, STATIC, SINGULAR, SINT32, latitude_i, 7) \ -X(a, STATIC, SINGULAR, SINT32, longitude_i, 8) +X(a, STATIC, SINGULAR, SINT32, longitude_i, 8) \ +X(a, STATIC, SINGULAR, FIXED32, time, 9) #define Position_CALLBACK NULL #define Position_DEFAULT NULL @@ -342,13 +342,13 @@ X(a, STATIC, SINGULAR, BOOL, want_response, 5) #define SubPacket_user_MSGTYPE User #define MeshPacket_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, INT32, from, 1) \ -X(a, STATIC, SINGULAR, INT32, to, 2) \ +X(a, STATIC, SINGULAR, UINT32, from, 1) \ +X(a, STATIC, SINGULAR, UINT32, to, 2) \ X(a, STATIC, ONEOF, MESSAGE, (payload,decoded,decoded), 3) \ X(a, STATIC, ONEOF, BYTES, (payload,encrypted,encrypted), 8) \ -X(a, STATIC, SINGULAR, UINT32, rx_time, 4) \ X(a, STATIC, SINGULAR, UINT32, id, 6) \ -X(a, STATIC, SINGULAR, FLOAT, rx_snr, 7) +X(a, STATIC, SINGULAR, FLOAT, rx_snr, 7) \ +X(a, STATIC, SINGULAR, FIXED32, rx_time, 9) #define MeshPacket_CALLBACK NULL #define MeshPacket_DEFAULT NULL #define MeshPacket_payload_decoded_MSGTYPE SubPacket @@ -493,21 +493,21 @@ extern const pb_msgdesc_t ToRadio_msg; #define ToRadio_fields &ToRadio_msg /* Maximum encoded size of messages (where known) */ -#define Position_size 40 +#define Position_size 39 #define Data_size 256 #define User_size 72 /* RouteDiscovery_size depends on runtime parameters */ -#define SubPacket_size 377 -#define MeshPacket_size 419 +#define SubPacket_size 376 +#define MeshPacket_size 407 #define ChannelSettings_size 60 #define RadioConfig_size 136 #define RadioConfig_UserPreferences_size 72 -#define NodeInfo_size 132 +#define NodeInfo_size 131 #define MyNodeInfo_size 85 -#define DeviceState_size 18552 +#define DeviceState_size 18124 #define DebugString_size 258 -#define FromRadio_size 428 -#define ToRadio_size 422 +#define FromRadio_size 416 +#define ToRadio_size 410 #ifdef __cplusplus } /* extern "C" */ diff --git a/variants/ppr/variant.h b/variants/ppr/variant.h index b792172ee7..d59e175038 100644 --- a/variants/ppr/variant.h +++ b/variants/ppr/variant.h @@ -16,15 +16,12 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef _VARIANT_PCA10056_ -#define _VARIANT_PCA10056_ +#pragma once /** Master clock frequency */ #define VARIANT_MCK (64000000ul) -// This file is the same as the standard pac10056 variant, except that @geeksville broke the xtal on his devboard so -// he has to use a RC clock. - +// This board does not have a 32khz crystal // #define USE_LFXO // Board uses 32khz crystal for LF #define USE_LFRC // Board uses RC for LF @@ -157,5 +154,3 @@ static const uint8_t SCK = PIN_SPI_SCK; /*---------------------------------------------------------------------------- * Arduino objects - C++ only *----------------------------------------------------------------------------*/ - -#endif From 86ae69d36093b8c2b5461ef0cd5ece6b08c1b8fb Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 11 May 2020 16:14:53 -0700 Subject: [PATCH 05/25] refactor so I can track and ignore recent packets of any type --- docs/software/TODO.md | 1 + docs/software/mesh-alg.md | 52 ++++++++++++++++++++++- proto | 2 +- src/mesh/FloodingRouter.cpp | 49 ---------------------- src/mesh/FloodingRouter.h | 22 +--------- src/mesh/PacketHistory.cpp | 52 +++++++++++++++++++++++ src/mesh/PacketHistory.h | 33 +++++++++++++++ src/mesh/mesh.pb.h | 84 +++++++++++++++++++++++++------------ 8 files changed, 196 insertions(+), 99 deletions(-) create mode 100644 src/mesh/PacketHistory.cpp create mode 100644 src/mesh/PacketHistory.h diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 711f31f01f..93ceb20d24 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -11,6 +11,7 @@ Items to complete before the first beta release. - Use 32 bits for message IDs - Use fixed32 for node IDs +- Remove the "want node" node number arbitration process - Don't store position packets in the to phone fifo if we are disconnected. The phone will get that info for 'free' when it fetches the fresh nodedb. - Use the RFM95 sequencer to stay in idle mode most of the time, then automatically go to receive mode and automatically go from transmit to receive mode. See 4.2.8.2 of manual. diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index 48bc6721f1..2b55a3a345 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -5,15 +5,63 @@ all else fails could always use the stock Radiohead solution - though super inef 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? +- 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. + +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. + +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 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 + +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. + TODO: -- DONE reread the radiohead mesh implementation - hop to hop acknoledgement seems VERY expensive but otherwise it seems like DSR +- 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) - 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 generalize naive flooding 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 4840493693..3bf195cb2d 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 4840493693d5799ebd451f6857ecbbc5c9157348 +Subproject commit 3bf195cb2d60f1d877a89bca87d0c70ea2d01177 diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index c1672c77e5..177a921698 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -2,15 +2,10 @@ #include "configuration.h" #include "mesh-pb-constants.h" -/// We clear our old flood record five minute after we see the last of it -#define FLOOD_EXPIRE_TIME (5 * 60 * 1000L) - static bool supportFlooding = true; // Sometimes to simplify debugging we want jusT simple broadcast only FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) { - recentBroadcasts.reserve(MAX_NUM_NODES); // Prealloc the worst case # of records - to prevent heap fragmentation - // setup our periodic task } /** @@ -101,47 +96,3 @@ void FloodingRouter::doTask() setPeriod(getRandomDelay()); } } - -/** - * Update recentBroadcasts and return true if we have already seen this packet - */ -bool FloodingRouter::wasSeenRecently(const MeshPacket *p) -{ - if (p->to != NODENUM_BROADCAST) - return false; // Not a broadcast, so we don't care - - if (p->id == 0) { - DEBUG_MSG("Ignoring message with zero id\n"); - return false; // Not a floodable message ID, so we don't care - } - - uint32_t now = millis(); - for (size_t i = 0; i < recentBroadcasts.size();) { - BroadcastRecord &r = recentBroadcasts[i]; - - if ((now - r.rxTimeMsec) >= FLOOD_EXPIRE_TIME) { - // DEBUG_MSG("Deleting old broadcast record %d\n", i); - recentBroadcasts.erase(recentBroadcasts.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); - - // Update the time on this record to now - r.rxTimeMsec = now; - return true; - } - - i++; - } - } - - // Didn't find an existing record, make one - BroadcastRecord r; - r.id = p->id; - r.sender = p->from; - r.rxTimeMsec = now; - recentBroadcasts.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/FloodingRouter.h b/src/mesh/FloodingRouter.h index 4ca759eccb..b8bfa0b9cc 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -1,17 +1,9 @@ #pragma once +#include "PacketHistory.h" #include "PeriodicTask.h" #include "Router.h" -#include -/** - * A record of a recent message broadcast - */ -struct BroadcastRecord { - NodeNum sender; - PacketId id; - uint32_t rxTimeMsec; // Unix time in msecs - the time we received it -}; /** * This is a mixin that extends Router with the ability to do Naive Flooding (in the standard mesh protocol sense) @@ -36,13 +28,9 @@ struct BroadcastRecord { 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, public PeriodicTask +class FloodingRouter : public Router, public PeriodicTask, private PacketHistory { private: - /** FIXME: really should be a std::unordered_set with the key being sender,id. - * This would make checking packets in wasSeenRecently faster. - */ - std::vector recentBroadcasts; /** * Packets we've received that we need to resend after a short delay @@ -74,10 +62,4 @@ class FloodingRouter : public Router, public PeriodicTask virtual void handleReceived(MeshPacket *p); virtual void doTask(); - - private: - /** - * Update recentBroadcasts and return true if we have already seen this packet - */ - bool wasSeenRecently(const MeshPacket *p); }; diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp new file mode 100644 index 0000000000..9a5704f669 --- /dev/null +++ b/src/mesh/PacketHistory.cpp @@ -0,0 +1,52 @@ +#include "PacketHistory.h" +#include "configuration.h" + +/// We clear our old flood record five minute after we see the last of it +#define FLOOD_EXPIRE_TIME (5 * 60 * 1000L) + +PacketHistory::PacketHistory() +{ + recentPackets.reserve(MAX_NUM_NODES); // Prealloc the worst case # of records - to prevent heap fragmentation + // setup our periodic task +} + +/** + * Update recentBroadcasts and return true if we have already seen this packet + */ +bool PacketHistory::wasSeenRecently(const MeshPacket *p) +{ + if (p->id == 0) { + DEBUG_MSG("Ignoring message with zero id\n"); + return false; // Not a floodable message ID, so we don't care + } + + uint32_t now = millis(); + for (size_t i = 0; i < recentPackets.size();) { + PacketRecord &r = recentPackets[i]; + + if ((now - r.rxTimeMsec) >= FLOOD_EXPIRE_TIME) { + // DEBUG_MSG("Deleting old broadcast record %d\n", i); + 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); + + // Update the time on this record to now + r.rxTimeMsec = now; + return true; + } + + i++; + } + } + + // 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); + + return false; +} \ No newline at end of file diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h new file mode 100644 index 0000000000..3a182caeb5 --- /dev/null +++ b/src/mesh/PacketHistory.h @@ -0,0 +1,33 @@ +#pragma once + +#include "Router.h" +#include + +/** + * A record of a recent message broadcast + */ +struct PacketRecord { + NodeNum sender; + PacketId id; + uint32_t rxTimeMsec; // Unix time in msecs - the time we received it +}; + +/** + * This is a mixin that adds a record of past packets we have seen + */ +class PacketHistory +{ + private: + /** FIXME: really should be a std::unordered_set with the key being sender,id. + * This would make checking packets in wasSeenRecently faster. + */ + std::vector recentPackets; + + public: + PacketHistory(); + + /** + * Update recentBroadcasts and return true if we have already seen this packet + */ + bool wasSeenRecently(const MeshPacket *p); +}; diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index be286b5031..d8a2e1fd54 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -32,10 +32,6 @@ typedef enum _ChannelSettings_ModemConfig { } ChannelSettings_ModemConfig; /* Struct definitions */ -typedef struct _RouteDiscovery { - pb_callback_t route; -} RouteDiscovery; - typedef PB_BYTES_ARRAY_T(32) ChannelSettings_psk_t; typedef struct _ChannelSettings { int32_t tx_power; @@ -90,6 +86,11 @@ typedef struct _RadioConfig_UserPreferences { bool promiscuous_mode; } RadioConfig_UserPreferences; +typedef struct _RouteDiscovery { + pb_size_t route_count; + int32_t route[8]; +} RouteDiscovery; + typedef struct _User { char id[16]; char long_name[40]; @@ -98,11 +99,12 @@ typedef struct _User { } User; typedef struct _NodeInfo { - int32_t num; + uint32_t num; bool has_user; User user; bool has_position; Position position; + uint32_t next_hop; float snr; } NodeInfo; @@ -121,6 +123,17 @@ typedef struct _SubPacket { bool has_user; User user; bool want_response; + pb_size_t which_route; + union { + RouteDiscovery request; + RouteDiscovery reply; + } route; + uint32_t dest; + pb_size_t which_ack; + union { + uint32_t success_id; + uint32_t fail_id; + } ack; } SubPacket; typedef PB_BYTES_ARRAY_T(256) MeshPacket_encrypted_t; @@ -135,6 +148,7 @@ typedef struct _MeshPacket { uint32_t id; float rx_snr; uint32_t rx_time; + uint32_t hop_limit; } MeshPacket; typedef struct _DeviceState { @@ -196,13 +210,13 @@ typedef struct _ToRadio { #define Position_init_default {0, 0, 0, 0, 0} #define Data_init_default {_Data_Type_MIN, {0, {0}}} #define User_init_default {"", "", "", {0}} -#define RouteDiscovery_init_default {{{NULL}, NULL}} -#define SubPacket_init_default {false, Position_init_default, false, Data_init_default, false, User_init_default, 0} -#define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0} +#define RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}} +#define SubPacket_init_default {false, Position_init_default, false, Data_init_default, false, User_init_default, 0, 0, {RouteDiscovery_init_default}, 0, 0, {0}} +#define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 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} -#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0} +#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0} #define MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0} #define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default}, false, MeshPacket_init_default, 0} #define DebugString_init_default {""} @@ -211,13 +225,13 @@ typedef struct _ToRadio { #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 {{{NULL}, NULL}} -#define SubPacket_init_zero {false, Position_init_zero, false, Data_init_zero, false, User_init_zero, 0} -#define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0} +#define RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}} +#define SubPacket_init_zero {false, Position_init_zero, false, Data_init_zero, false, User_init_zero, 0, 0, {RouteDiscovery_init_zero}, 0, 0, {0}} +#define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 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} -#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0} +#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0} #define MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0} #define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero}, false, MeshPacket_init_zero, 0} #define DebugString_init_zero {""} @@ -225,7 +239,6 @@ typedef struct _ToRadio { #define ToRadio_init_zero {0, {MeshPacket_init_zero}} /* Field tags (for use in manual encoding/decoding) */ -#define RouteDiscovery_route_tag 2 #define ChannelSettings_tx_power_tag 1 #define ChannelSettings_modem_config_tag 3 #define ChannelSettings_psk_tag 4 @@ -260,6 +273,7 @@ typedef struct _ToRadio { #define RadioConfig_UserPreferences_min_wake_secs_tag 11 #define RadioConfig_UserPreferences_keep_all_packets_tag 100 #define RadioConfig_UserPreferences_promiscuous_mode_tag 101 +#define RouteDiscovery_route_tag 2 #define User_id_tag 1 #define User_long_name_tag 2 #define User_short_name_tag 3 @@ -268,19 +282,26 @@ typedef struct _ToRadio { #define NodeInfo_user_tag 2 #define NodeInfo_position_tag 3 #define NodeInfo_snr_tag 7 +#define NodeInfo_next_hop_tag 5 #define RadioConfig_preferences_tag 1 #define RadioConfig_channel_settings_tag 2 +#define SubPacket_success_id_tag 10 +#define SubPacket_fail_id_tag 11 +#define SubPacket_request_tag 6 +#define SubPacket_reply_tag 7 #define SubPacket_position_tag 1 #define SubPacket_data_tag 3 #define SubPacket_user_tag 4 #define SubPacket_want_response_tag 5 +#define SubPacket_dest_tag 9 #define MeshPacket_decoded_tag 3 #define MeshPacket_encrypted_tag 8 #define MeshPacket_from_tag 1 #define MeshPacket_to_tag 2 -#define MeshPacket_rx_time_tag 9 #define MeshPacket_id_tag 6 +#define MeshPacket_rx_time_tag 9 #define MeshPacket_rx_snr_tag 7 +#define MeshPacket_hop_limit_tag 10 #define DeviceState_radio_tag 1 #define DeviceState_my_node_tag 2 #define DeviceState_owner_tag 3 @@ -326,20 +347,27 @@ X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, macaddr, 4) #define User_DEFAULT NULL #define RouteDiscovery_FIELDLIST(X, a) \ -X(a, CALLBACK, REPEATED, INT32, route, 2) -#define RouteDiscovery_CALLBACK pb_default_field_callback +X(a, STATIC, REPEATED, INT32, route, 2) +#define RouteDiscovery_CALLBACK NULL #define RouteDiscovery_DEFAULT NULL #define SubPacket_FIELDLIST(X, a) \ X(a, STATIC, OPTIONAL, MESSAGE, position, 1) \ X(a, STATIC, OPTIONAL, MESSAGE, data, 3) \ X(a, STATIC, OPTIONAL, MESSAGE, user, 4) \ -X(a, STATIC, SINGULAR, BOOL, want_response, 5) +X(a, STATIC, SINGULAR, BOOL, want_response, 5) \ +X(a, STATIC, ONEOF, MESSAGE, (route,request,route.request), 6) \ +X(a, STATIC, ONEOF, MESSAGE, (route,reply,route.reply), 7) \ +X(a, STATIC, SINGULAR, UINT32, dest, 9) \ +X(a, STATIC, ONEOF, UINT32, (ack,success_id,ack.success_id), 10) \ +X(a, STATIC, ONEOF, UINT32, (ack,fail_id,ack.fail_id), 11) #define SubPacket_CALLBACK NULL #define SubPacket_DEFAULT NULL #define SubPacket_position_MSGTYPE Position #define SubPacket_data_MSGTYPE Data #define SubPacket_user_MSGTYPE User +#define SubPacket_route_request_MSGTYPE RouteDiscovery +#define SubPacket_route_reply_MSGTYPE RouteDiscovery #define MeshPacket_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, from, 1) \ @@ -348,7 +376,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,decoded,decoded), 3) \ 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, FIXED32, rx_time, 9) \ +X(a, STATIC, SINGULAR, UINT32, hop_limit, 10) #define MeshPacket_CALLBACK NULL #define MeshPacket_DEFAULT NULL #define MeshPacket_payload_decoded_MSGTYPE SubPacket @@ -387,9 +416,10 @@ X(a, STATIC, SINGULAR, BOOL, promiscuous_mode, 101) #define RadioConfig_UserPreferences_DEFAULT NULL #define NodeInfo_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, INT32, num, 1) \ +X(a, STATIC, SINGULAR, UINT32, num, 1) \ X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \ X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \ +X(a, STATIC, SINGULAR, UINT32, next_hop, 5) \ X(a, STATIC, SINGULAR, FLOAT, snr, 7) #define NodeInfo_CALLBACK NULL #define NodeInfo_DEFAULT NULL @@ -496,18 +526,18 @@ extern const pb_msgdesc_t ToRadio_msg; #define Position_size 39 #define Data_size 256 #define User_size 72 -/* RouteDiscovery_size depends on runtime parameters */ -#define SubPacket_size 376 -#define MeshPacket_size 407 +#define RouteDiscovery_size 88 +#define SubPacket_size 478 +#define MeshPacket_size 515 #define ChannelSettings_size 60 #define RadioConfig_size 136 #define RadioConfig_UserPreferences_size 72 -#define NodeInfo_size 131 +#define NodeInfo_size 132 #define MyNodeInfo_size 85 -#define DeviceState_size 18124 +#define DeviceState_size 21720 #define DebugString_size 258 -#define FromRadio_size 416 -#define ToRadio_size 410 +#define FromRadio_size 524 +#define ToRadio_size 518 #ifdef __cplusplus } /* extern "C" */ From 9f05ad29270b84a370b7089b59d25e765388c32f Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 11 May 2020 16:19:44 -0700 Subject: [PATCH 06/25] remove random delay hack from broadcast, since we now do that for all transmits --- src/main.cpp | 2 -- src/mesh/FloodingRouter.cpp | 52 ++++--------------------------------- src/mesh/FloodingRouter.h | 6 +---- 3 files changed, 6 insertions(+), 54 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index a3c060b189..9ba569947a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -208,8 +208,6 @@ void setup() service.init(); - realRouter.setup(); // required for our periodic task (kinda skanky FIXME) - #ifdef SX1262_ANT_SW // make analog PA vs not PA switch on SX1262 eval board work properly pinMode(SX1262_ANT_SW, OUTPUT); diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 177a921698..6db03dd4f8 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -4,9 +4,7 @@ static bool supportFlooding = true; // Sometimes to simplify debugging we want jusT simple broadcast only -FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) -{ -} +FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) {} /** * Send a packet on a suitable interface. This routine will @@ -22,18 +20,6 @@ ErrorCode FloodingRouter::send(MeshPacket *p) return Router::send(p); } -// Return a delay in msec before sending the next packet -uint32_t getRandomDelay() -{ - return random(200, 10 * 1000L); // between 200ms and 10s -} - -/** - * Now that our generalized packet send code has a random delay - I don't think we need to wait here - * But I'm leaving this bool until I rip the code out for good. - */ -bool needDelay = false; - /** * Called from loop() * Handle any packet that is received by an interface on this node. @@ -52,21 +38,11 @@ void FloodingRouter::handleReceived(MeshPacket *p) if (p->id != 0) { MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it - if (needDelay) { - uint32_t delay = getRandomDelay(); - - DEBUG_MSG("Rebroadcasting received floodmsg to neighbors in %u msec, fr=0x%x,to=0x%x,id=%d\n", delay, - p->from, p->to, p->id); + 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); - toResend.enqueue(tosend); - setPeriod(delay); // This will work even if we were already waiting a random delay - } else { - 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); - } } else { DEBUG_MSG("Ignoring a simple (0 hop) broadcast\n"); } @@ -78,21 +54,3 @@ void FloodingRouter::handleReceived(MeshPacket *p) } else Router::handleReceived(p); } - -void FloodingRouter::doTask() -{ - MeshPacket *p = toResend.dequeuePtr(0); - - if (p) { - DEBUG_MSG("Sending delayed message!\n"); - // 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(p); - } - - if (toResend.isEmpty()) - disable(); // no more work right now - else { - setPeriod(getRandomDelay()); - } -} diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index b8bfa0b9cc..996e9f7aec 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -4,7 +4,6 @@ #include "PeriodicTask.h" #include "Router.h" - /** * This is a mixin that extends Router with the ability to do Naive Flooding (in the standard mesh protocol sense) * @@ -28,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, public PeriodicTask, private PacketHistory +class FloodingRouter : public Router, private PacketHistory { private: - /** * Packets we've received that we need to resend after a short delay */ @@ -60,6 +58,4 @@ class FloodingRouter : public Router, public PeriodicTask, private PacketHistory * Note: this method will free the provided packet */ virtual void handleReceived(MeshPacket *p); - - virtual void doTask(); }; From b6a202d68ee8cfaf7266daddcea8a8337f280a96 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 12 May 2020 13:35:22 -0700 Subject: [PATCH 07/25] runs again with new protobufs --- platformio.ini | 1 + proto | 2 +- src/mesh/MeshService.cpp | 12 ++++----- src/mesh/NodeDB.cpp | 11 +++++--- src/mesh/PacketHistory.h | 37 ++++++++++++++++++++++++-- src/mesh/mesh.pb.h | 57 +++++++++++++++++++--------------------- src/screen.cpp | 2 +- 7 files changed, 79 insertions(+), 43 deletions(-) diff --git a/platformio.ini b/platformio.ini index 6441955008..aead63d983 100644 --- a/platformio.ini +++ b/platformio.ini @@ -51,6 +51,7 @@ build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -Ilib/n ; the default is esptool ; upload_protocol = esp-prog +; monitor_speed = 115200 monitor_speed = 921600 # debug_tool = esp-prog diff --git a/proto b/proto index 3bf195cb2d..bc3ecd97e3 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 3bf195cb2d60f1d877a89bca87d0c70ea2d01177 +Subproject commit bc3ecd97e381b724c1a28acce0d12c688de73ba3 diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 992144b82c..5f4b51baba 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -93,7 +93,7 @@ void MeshService::sendOurOwner(NodeNum dest, bool wantReplies) MeshPacket *p = allocForSending(); p->to = dest; p->decoded.want_response = wantReplies; - p->decoded.has_user = true; + p->decoded.which_payload = SubPacket_user_tag; User &u = p->decoded.user; u = owner; DEBUG_MSG("sending owner %s/%s/%s\n", u.id, u.long_name, u.short_name); @@ -143,7 +143,7 @@ const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp) void MeshService::handleIncomingPosition(const MeshPacket *mp) { - if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.has_position) { + if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_position_tag) { DEBUG_MSG("handled incoming position time=%u\n", mp->decoded.position.time); if (mp->decoded.position.time) { @@ -171,7 +171,7 @@ int MeshService::handleFromRadio(const MeshPacket *mp) DEBUG_MSG("Ignoring incoming time, because we have a GPS\n"); } - if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.has_user) { + if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_user_tag) { mp = handleFromRadioUser(mp); } @@ -257,7 +257,7 @@ void MeshService::sendToMesh(MeshPacket *p) // Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other // nodes shouldn't trust it anyways) Note: for now, we allow a device with a local GPS to include the time, so that gpsless // devices can get time. - if (p->which_payload == MeshPacket_decoded_tag && p->decoded.has_position) { + if (p->which_payload == MeshPacket_decoded_tag && p->decoded.which_payload == SubPacket_position_tag) { if (!gps->isConnected) { DEBUG_MSG("Stripping time %u from position send\n", p->decoded.position.time); p->decoded.position.time = 0; @@ -312,7 +312,7 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies) // Update our local node info with our position (even if we don't decide to update anyone else) MeshPacket *p = allocForSending(); p->to = dest; - p->decoded.has_position = true; + p->decoded.which_payload = SubPacket_position_tag; p->decoded.position = node->position; p->decoded.want_response = wantReplies; p->decoded.position.time = getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid. @@ -325,7 +325,7 @@ int MeshService::onGPSChanged(void *unused) // Update our local node info with our position (even if we don't decide to update anyone else) MeshPacket *p = allocForSending(); - p->decoded.has_position = true; + p->decoded.which_payload = SubPacket_position_tag; Position &pos = p->decoded.position; // !zero or !zero lat/long means valid diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 8fe82b8229..5d2e8ecd3b 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -296,16 +296,18 @@ void NodeDB::updateFrom(const MeshPacket &mp) info->snr = mp.rx_snr; // keep the most recent SNR we received for this node. - if (p.has_position) { + switch (p.which_payload) { + case SubPacket_position_tag: { // we carefully preserve the old time, because we always trust our local timestamps more uint32_t oldtime = info->position.time; info->position = p.position; info->position.time = oldtime; info->has_position = true; updateGUIforNode = info; + break; } - if (p.has_data) { + case SubPacket_data_tag: { // Keep a copy of the most recent text message. if (p.data.typ == Data_Type_CLEAR_TEXT) { DEBUG_MSG("Received text msg from=0x%0x, id=%d, msg=%.*s\n", mp.from, mp.id, p.data.payload.size, @@ -318,9 +320,10 @@ void NodeDB::updateFrom(const MeshPacket &mp) powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG); } } + break; } - if (p.has_user) { + case SubPacket_user_tag: { DEBUG_MSG("old user %s/%s/%s\n", info->user.id, info->user.long_name, info->user.short_name); bool changed = memcmp(&info->user, &p.user, @@ -338,6 +341,8 @@ void NodeDB::updateFrom(const MeshPacket &mp) // We just changed something important about the user, store our DB // saveToDisk(); } + break; + } } } } diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h index 3a182caeb5..22470f4fc2 100644 --- a/src/mesh/PacketHistory.h +++ b/src/mesh/PacketHistory.h @@ -1,7 +1,10 @@ #pragma once #include "Router.h" -#include +#include +#include + +using namespace std; /** * A record of a recent message broadcast @@ -10,6 +13,34 @@ struct PacketRecord { NodeNum sender; PacketId id; uint32_t rxTimeMsec; // Unix time in msecs - the time we received it + + bool operator==(const PacketRecord &p) const { return sender == p.sender && id == p.id; } +}; + +class PacketRecordHashFunction +{ + public: + size_t operator()(const PacketRecord &p) const { return (hash()(p.sender)) ^ (hash()(p.id)); } +}; + +/// Order packet records by arrival time, we want the oldest packets to be in the front of our heap +class PacketRecordOrderFunction +{ + public: + size_t operator()(const PacketRecord &p1, const PacketRecord &p2) const + { + // If the timer ticks have rolled over the difference between times will be _enormous_. Handle that case specially + uint32_t t1 = p1.rxTimeMsec, t2 = p2.rxTimeMsec; + + if (abs(t1 - t2) > + UINT32_MAX / + 2) { // time must have rolled over, swap them because the new little number is 'bigger' than the old big number + t1 = t2; + t2 = p1.rxTimeMsec; + } + + return t1 > t2; + } }; /** @@ -21,7 +52,9 @@ class PacketHistory /** FIXME: really should be a std::unordered_set with the key being sender,id. * This would make checking packets in wasSeenRecently faster. */ - std::vector recentPackets; + vector recentPackets; + // priority_queue, PacketRecordOrderFunction> arrivalTimes; + // unordered_set recentPackets; public: PacketHistory(); diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index d8a2e1fd54..d193d38ee5 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -116,18 +116,15 @@ typedef struct _RadioConfig { } RadioConfig; typedef struct _SubPacket { - bool has_position; - Position position; - bool has_data; - Data data; - bool has_user; - User user; - bool want_response; - pb_size_t which_route; + pb_size_t which_payload; union { + Position position; + Data data; + User user; RouteDiscovery request; RouteDiscovery reply; - } route; + }; + bool want_response; uint32_t dest; pb_size_t which_ack; union { @@ -211,7 +208,7 @@ typedef struct _ToRadio { #define Data_init_default {_Data_Type_MIN, {0, {0}}} #define User_init_default {"", "", "", {0}} #define RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}} -#define SubPacket_init_default {false, Position_init_default, false, Data_init_default, false, User_init_default, 0, 0, {RouteDiscovery_init_default}, 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 ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""} #define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default} @@ -226,7 +223,7 @@ typedef struct _ToRadio { #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 {false, Position_init_zero, false, Data_init_zero, false, User_init_zero, 0, 0, {RouteDiscovery_init_zero}, 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 ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""} #define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero} @@ -285,13 +282,13 @@ typedef struct _ToRadio { #define NodeInfo_next_hop_tag 5 #define RadioConfig_preferences_tag 1 #define RadioConfig_channel_settings_tag 2 -#define SubPacket_success_id_tag 10 -#define SubPacket_fail_id_tag 11 -#define SubPacket_request_tag 6 -#define SubPacket_reply_tag 7 #define SubPacket_position_tag 1 #define SubPacket_data_tag 3 #define SubPacket_user_tag 4 +#define SubPacket_request_tag 6 +#define SubPacket_reply_tag 7 +#define SubPacket_success_id_tag 10 +#define SubPacket_fail_id_tag 11 #define SubPacket_want_response_tag 5 #define SubPacket_dest_tag 9 #define MeshPacket_decoded_tag 3 @@ -352,22 +349,22 @@ X(a, STATIC, REPEATED, INT32, route, 2) #define RouteDiscovery_DEFAULT NULL #define SubPacket_FIELDLIST(X, a) \ -X(a, STATIC, OPTIONAL, MESSAGE, position, 1) \ -X(a, STATIC, OPTIONAL, MESSAGE, data, 3) \ -X(a, STATIC, OPTIONAL, MESSAGE, user, 4) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,position,position), 1) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,data,data), 3) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,user,user), 4) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,request,request), 6) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,reply,reply), 7) \ X(a, STATIC, SINGULAR, BOOL, want_response, 5) \ -X(a, STATIC, ONEOF, MESSAGE, (route,request,route.request), 6) \ -X(a, STATIC, ONEOF, MESSAGE, (route,reply,route.reply), 7) \ X(a, STATIC, SINGULAR, UINT32, dest, 9) \ X(a, STATIC, ONEOF, UINT32, (ack,success_id,ack.success_id), 10) \ X(a, STATIC, ONEOF, UINT32, (ack,fail_id,ack.fail_id), 11) #define SubPacket_CALLBACK NULL #define SubPacket_DEFAULT NULL -#define SubPacket_position_MSGTYPE Position -#define SubPacket_data_MSGTYPE Data -#define SubPacket_user_MSGTYPE User -#define SubPacket_route_request_MSGTYPE RouteDiscovery -#define SubPacket_route_reply_MSGTYPE RouteDiscovery +#define SubPacket_payload_position_MSGTYPE Position +#define SubPacket_payload_data_MSGTYPE Data +#define SubPacket_payload_user_MSGTYPE User +#define SubPacket_payload_request_MSGTYPE RouteDiscovery +#define SubPacket_payload_reply_MSGTYPE RouteDiscovery #define MeshPacket_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, from, 1) \ @@ -527,17 +524,17 @@ extern const pb_msgdesc_t ToRadio_msg; #define Data_size 256 #define User_size 72 #define RouteDiscovery_size 88 -#define SubPacket_size 478 -#define MeshPacket_size 515 +#define SubPacket_size 273 +#define MeshPacket_size 310 #define ChannelSettings_size 60 #define RadioConfig_size 136 #define RadioConfig_UserPreferences_size 72 #define NodeInfo_size 132 #define MyNodeInfo_size 85 -#define DeviceState_size 21720 +#define DeviceState_size 14955 #define DebugString_size 258 -#define FromRadio_size 524 -#define ToRadio_size 518 +#define FromRadio_size 319 +#define ToRadio_size 313 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/screen.cpp b/src/screen.cpp index c60907aa83..dd2f99c07b 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -94,7 +94,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state // the max length of this buffer is much longer than we can possibly print static char tempBuf[96]; - assert(mp.decoded.has_data); + assert(mp.decoded.which_payload == SubPacket_data_tag); snprintf(tempBuf, sizeof(tempBuf), " %s", mp.decoded.data.payload.bytes); display->drawStringMaxWidth(4 + x, 10 + y, 128, tempBuf); From a0b43b9a95da35f95159f034963ad9726788f4c2 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 12 May 2020 17:57:51 -0700 Subject: [PATCH 08/25] Send "unset" for hwver and swver if they were unset --- src/configuration.h | 4 ++++ src/esp32/main-esp32.cpp | 4 ++-- src/main.cpp | 2 +- src/mesh/NodeDB.cpp | 11 +++++++---- src/sleep.cpp | 3 --- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index ca606c53ac..04f9d26a13 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -48,9 +48,13 @@ along with this program. If not, see . #define REQUIRE_RADIO true // If true, we will fail to start if the radio is not found +/// Convert a preprocessor name into a quoted string #define xstr(s) str(s) #define str(s) #s +/// Convert a preprocessor name into a quoted string and if that string is empty use "unset" +#define optstr(s) (xstr(s)[0] ? xstr(s) : "unset") + #ifdef NRF52840_XXAA // All of the NRF52 targets are configured using variant.h, so this section shouldn't need to be // board specific diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index 48af9b998b..b0e1406b5d 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -22,8 +22,8 @@ void reinitBluetooth() powerFSM.trigger(EVENT_BLUETOOTH_PAIR); screen.startBluetoothPinScreen(pin); }, - []() { screen.stopBluetoothPinScreen(); }, getDeviceName(), HW_VENDOR, xstr(APP_VERSION), - xstr(HW_VERSION)); // FIXME, use a real name based on the macaddr + []() { screen.stopBluetoothPinScreen(); }, getDeviceName(), HW_VENDOR, optstr(APP_VERSION), + optstr(HW_VERSION)); // FIXME, use a real name based on the macaddr createMeshBluetoothService(serve); // Start advertising - this must be done _after_ creating all services diff --git a/src/main.cpp b/src/main.cpp index 9ba569947a..2378458a69 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -167,7 +167,7 @@ void setup() ledPeriodic.setup(); // Hello - DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", xstr(APP_VERSION), xstr(HW_VERSION)); + DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", optstr(APP_VERSION), optstr(HW_VERSION)); #ifndef NO_ESP32 // Don't init display if we don't have one or we are waking headless due to a timer event diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 5d2e8ecd3b..cc83f2f472 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -109,10 +109,6 @@ void NodeDB::init() // default to no GPS, until one has been found by probing myNodeInfo.has_gps = false; - strncpy(myNodeInfo.region, xstr(HW_VERSION), sizeof(myNodeInfo.region)); - strncpy(myNodeInfo.firmware_version, xstr(APP_VERSION), sizeof(myNodeInfo.firmware_version)); - strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model)); - // Init our blank owner info to reasonable defaults getMacAddr(ourMacAddr); sprintf(owner.id, "!%02x%02x%02x%02x%02x%02x", ourMacAddr[0], ourMacAddr[1], ourMacAddr[2], ourMacAddr[3], ourMacAddr[4], @@ -135,6 +131,13 @@ void NodeDB::init() // saveToDisk(); loadFromDisk(); + + // We set these _after_ loading from disk - because they come from the build and are more trusted than + // what is stored in flash + strncpy(myNodeInfo.region, optstr(HW_VERSION), sizeof(myNodeInfo.region)); + strncpy(myNodeInfo.firmware_version, optstr(APP_VERSION), sizeof(myNodeInfo.firmware_version)); + strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model)); + resetRadioConfig(); // If bogus settings got saved, then fix them DEBUG_MSG("NODENUM=0x%x, dbsize=%d\n", myNodeInfo.my_node_num, *numNodes); diff --git a/src/sleep.cpp b/src/sleep.cpp index 4f5fa2fdc4..4b8db06b08 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -35,9 +35,6 @@ Observable notifySleep, notifyDeepSleep; // deep sleep support RTC_DATA_ATTR int bootCount = 0; -#define xstr(s) str(s) -#define str(s) #s - // ----------------------------------------------------------------------------- // Application // ----------------------------------------------------------------------------- From 140e29840a982d84927f01186eabf2ecbddb83fd Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 14 May 2020 12:46:29 -0700 Subject: [PATCH 09/25] fix rare gurumeditation if we are unlucky and some ISR code is in serial flash --- src/WorkerThread.cpp | 8 +------- src/WorkerThread.h | 7 ++++++- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/WorkerThread.cpp b/src/WorkerThread.cpp index ed11039119..f84d83be2a 100644 --- a/src/WorkerThread.cpp +++ b/src/WorkerThread.cpp @@ -28,13 +28,7 @@ void NotifiedWorkerThread::notify(uint32_t v, eNotifyAction action) xTaskNotify(taskHandle, v, action); } -/** - * Notify from an ISR - */ -void NotifiedWorkerThread::notifyFromISR(BaseType_t *highPriWoken, uint32_t v, eNotifyAction action) -{ - xTaskNotifyFromISR(taskHandle, v, action, highPriWoken); -} + void NotifiedWorkerThread::block() { diff --git a/src/WorkerThread.h b/src/WorkerThread.h index f951da32cb..86ec08e133 100644 --- a/src/WorkerThread.h +++ b/src/WorkerThread.h @@ -61,8 +61,13 @@ class NotifiedWorkerThread : public WorkerThread /** * Notify from an ISR + * + * This must be inline or IRAM_ATTR on ESP32 */ - void notifyFromISR(BaseType_t *highPriWoken, uint32_t v = 0, eNotifyAction action = eNoAction); + inline void notifyFromISR(BaseType_t *highPriWoken, uint32_t v = 0, eNotifyAction action = eNoAction) + { + xTaskNotifyFromISR(taskHandle, v, action, highPriWoken); + } protected: /** From 14fdd339724fb8470c97abecb72becb5fb2c744e Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 14 May 2020 14:20:05 -0700 Subject: [PATCH 10/25] move bluetooth OTA back into main tree for now --- .../src/BluetoothSoftwareUpdate.cpp | 99 +++++++++++-------- .../src/BluetoothSoftwareUpdate.h | 5 +- src/mesh/RadioLibInterface.h | 21 ++-- 3 files changed, 76 insertions(+), 49 deletions(-) diff --git a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp b/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp index 62bdd499da..f2fa7c9737 100644 --- a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp +++ b/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp @@ -1,21 +1,30 @@ -#include "BluetoothUtil.h" #include "BluetoothSoftwareUpdate.h" +#include "BluetoothUtil.h" +#include "CallbackCharacteristic.h" +#include "RadioLibInterface.h" #include "configuration.h" -#include -#include +#include "lock.h" #include -#include +#include #include -#include "CallbackCharacteristic.h" +#include +#include + +using namespace meshtastic; CRC32 crc; uint32_t rebootAtMsec = 0; // If not zero we will reboot at this time (used to reboot shortly after the update completes) +uint32_t updateExpectedSize, updateActualSize; + +Lock *updateLock; + class TotalSizeCharacteristic : public CallbackCharacteristic { -public: + public: TotalSizeCharacteristic() - : CallbackCharacteristic("e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ) + : CallbackCharacteristic("e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e", + BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ) { } @@ -23,8 +32,11 @@ class TotalSizeCharacteristic : public CallbackCharacteristic { BLEKeepAliveCallbacks::onWrite(c); + LockGuard g(updateLock); // Check if there is enough to OTA Update uint32_t len = getValue32(c, 0); + updateExpectedSize = len; + updateActualSize = 0; crc.reset(); bool canBegin = Update.begin(len); DEBUG_MSG("Setting update size %u, result %d\n", len, canBegin); @@ -34,32 +46,39 @@ class TotalSizeCharacteristic : public CallbackCharacteristic else { // This totally breaks abstraction to up up into the app layer for this, but quick hack to make sure we only // talk to one service during the sw update. - //DEBUG_MSG("FIXME, crufty shutdown of mesh bluetooth for sw update."); - //void stopMeshBluetoothService(); - //stopMeshBluetoothService(); + // DEBUG_MSG("FIXME, crufty shutdown of mesh bluetooth for sw update."); + // void stopMeshBluetoothService(); + // stopMeshBluetoothService(); + + if (RadioLibInterface::instance) + RadioLibInterface::instance->sleep(); // FIXME, nasty hack - the RF95 ISR/SPI code on ESP32 can fail while we are + // writing flash - shut the radio off during updates } } }; +#define MAX_BLOCKSIZE 512 + class DataCharacteristic : public CallbackCharacteristic { -public: - DataCharacteristic() - : CallbackCharacteristic( - "e272ebac-d463-4b98-bc84-5cc1a39ee517", BLECharacteristic::PROPERTY_WRITE) - { - } + public: + DataCharacteristic() : CallbackCharacteristic("e272ebac-d463-4b98-bc84-5cc1a39ee517", BLECharacteristic::PROPERTY_WRITE) {} void onWrite(BLECharacteristic *c) { BLEKeepAliveCallbacks::onWrite(c); + LockGuard g(updateLock); std::string value = c->getValue(); uint32_t len = value.length(); - uint8_t *data = c->getData(); + assert(len <= MAX_BLOCKSIZE); + static uint8_t + data[MAX_BLOCKSIZE]; // we temporarily copy here because I'm worried that a fast sender might be able overwrite srcbuf + memcpy(data, c->getData(), len); // DEBUG_MSG("Writing %u\n", len); crc.update(data, len); Update.write(data, len); + updateActualSize += len; } }; @@ -67,38 +86,36 @@ static BLECharacteristic *resultC; class CRC32Characteristic : public CallbackCharacteristic { -public: - CRC32Characteristic() - : CallbackCharacteristic( - "4826129c-c22a-43a3-b066-ce8f0d5bacc6", BLECharacteristic::PROPERTY_WRITE) - { - } + public: + CRC32Characteristic() : CallbackCharacteristic("4826129c-c22a-43a3-b066-ce8f0d5bacc6", BLECharacteristic::PROPERTY_WRITE) {} void onWrite(BLECharacteristic *c) { BLEKeepAliveCallbacks::onWrite(c); + LockGuard g(updateLock); uint32_t expectedCRC = getValue32(c, 0); + uint32_t actualCRC = crc.finalize(); DEBUG_MSG("expected CRC %u\n", expectedCRC); uint8_t result = 0xff; - // Check the CRC before asking the update to happen. - if (crc.finalize() != expectedCRC) + if (updateActualSize != updateExpectedSize) { + DEBUG_MSG("Expected %u bytes, but received %u bytes!\n", updateExpectedSize, updateActualSize); + result = 0xe1; // FIXME, use real error codes + } else if (actualCRC != expectedCRC) // Check the CRC before asking the update to happen. { - DEBUG_MSG("Invalid CRC!\n"); + DEBUG_MSG("Invalid CRC! expected=%u, actual=%u\n", expectedCRC, actualCRC); result = 0xe0; // FIXME, use real error codes - } - else - { - if (Update.end()) - { + } else { + if (Update.end()) { DEBUG_MSG("OTA done, rebooting in 5 seconds!\n"); rebootAtMsec = millis() + 5000; - } - else - { + } else { DEBUG_MSG("Error Occurred. Error #: %d\n", Update.getError()); + + if (RadioLibInterface::instance) + RadioLibInterface::instance->startReceive(); // Resume radio } result = Update.getError(); } @@ -108,8 +125,6 @@ class CRC32Characteristic : public CallbackCharacteristic } }; - - void bluetoothRebootCheck() { if (rebootAtMsec && millis() > rebootAtMsec) @@ -122,11 +137,15 @@ See bluetooth-api.md */ BLEService *createUpdateService(BLEServer *server, std::string hwVendor, std::string swVersion, std::string hwVersion) { + if (!updateLock) + updateLock = new Lock(); + // Create the BLE Service BLEService *service = server->createService(BLEUUID("cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30"), 25, 0); assert(!resultC); - resultC = new BLECharacteristic("5e134862-7411-4424-ac4a-210937432c77", BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + resultC = new BLECharacteristic("5e134862-7411-4424-ac4a-210937432c77", + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); addWithDesc(service, new TotalSizeCharacteristic, "total image size"); addWithDesc(service, new DataCharacteristic, "data"); @@ -135,7 +154,8 @@ BLEService *createUpdateService(BLEServer *server, std::string hwVendor, std::st resultC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification - BLECharacteristic *swC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ); + BLECharacteristic *swC = + new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ); swC->setValue(swVersion); service->addCharacteristic(addBLECharacteristic(swC)); @@ -143,7 +163,8 @@ BLEService *createUpdateService(BLEServer *server, std::string hwVendor, std::st mfC->setValue(hwVendor); service->addCharacteristic(addBLECharacteristic(mfC)); - BLECharacteristic *hwvC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ); + BLECharacteristic *hwvC = + new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ); hwvC->setValue(hwVersion); service->addCharacteristic(addBLECharacteristic(hwvC)); diff --git a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h b/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h index 60b1f66962..60517a7f2b 100644 --- a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h +++ b/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h @@ -1,8 +1,11 @@ #pragma once #include +#include +#include +#include -BLEService *createUpdateService(BLEServer* server, std::string hwVendor, std::string swVersion, std::string hwVersion); +BLEService *createUpdateService(BLEServer *server, std::string hwVendor, std::string swVersion, std::string hwVersion); void destroyUpdateService(); void bluetoothRebootCheck(); \ No newline at end of file diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index a090d132aa..5f9149d717 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -19,10 +19,6 @@ class RadioLibInterface : public RadioInterface volatile PendingISR pending = ISR_NONE; volatile bool timerRunning = false; - /** Our ISR code currently needs this to find our active instance - */ - static RadioLibInterface *instance; - /** * Raw ISR handler that just calls our polymorphic method */ @@ -57,6 +53,11 @@ class RadioLibInterface : public RadioInterface /// are _trying_ to receive a packet currently (note - we might just be waiting for one) bool isReceiving; + public: + /** Our ISR code currently needs this to find our active instance + */ + static RadioLibInterface *instance; + /** * Glue functions called from ISR land */ @@ -80,6 +81,13 @@ class RadioLibInterface : public RadioInterface */ virtual bool canSleep(); + /** + * Start waiting to receive a message + * + * External functions can call this method to wake the device from sleep. + */ + virtual void startReceive() = 0; + private: /** start an immediate transmit */ void startSend(MeshPacket *txp); @@ -110,11 +118,6 @@ class RadioLibInterface : public RadioInterface /** are we actively receiving a packet (only called during receiving state) */ virtual bool isActivelyReceiving() = 0; - /** - * Start waiting to receive a message - */ - virtual void startReceive() = 0; - /** * Raw ISR handler that just calls our polymorphic method */ From 5ec5248fe45f784736264ed0f9fdd01d268e0e3d Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 14 May 2020 14:22:11 -0700 Subject: [PATCH 11/25] complete ble ota move --- platformio.ini | 2 +- {lib/BluetoothOTA/src => src/esp32}/BluetoothSoftwareUpdate.cpp | 0 {lib/BluetoothOTA/src => src/esp32}/BluetoothSoftwareUpdate.h | 0 {lib/BluetoothOTA/src => src/esp32}/BluetoothUtil.cpp | 0 {lib/BluetoothOTA/src => src/esp32}/BluetoothUtil.h | 0 {lib/BluetoothOTA/src => src/esp32}/CallbackCharacteristic.h | 0 {lib/BluetoothOTA/src => src/esp32}/SimpleAllocator.cpp | 0 {lib/BluetoothOTA/src => src/esp32}/SimpleAllocator.h | 0 8 files changed, 1 insertion(+), 1 deletion(-) rename {lib/BluetoothOTA/src => src/esp32}/BluetoothSoftwareUpdate.cpp (100%) rename {lib/BluetoothOTA/src => src/esp32}/BluetoothSoftwareUpdate.h (100%) rename {lib/BluetoothOTA/src => src/esp32}/BluetoothUtil.cpp (100%) rename {lib/BluetoothOTA/src => src/esp32}/BluetoothUtil.h (100%) rename {lib/BluetoothOTA/src => src/esp32}/CallbackCharacteristic.h (100%) rename {lib/BluetoothOTA/src => src/esp32}/SimpleAllocator.cpp (100%) rename {lib/BluetoothOTA/src => src/esp32}/SimpleAllocator.h (100%) diff --git a/platformio.ini b/platformio.ini index aead63d983..ad27e29444 100644 --- a/platformio.ini +++ b/platformio.ini @@ -84,7 +84,7 @@ src_filter = upload_speed = 921600 debug_init_break = tbreak setup build_flags = - ${env.build_flags} -Wall -Wextra + ${env.build_flags} -Wall -Wextra -Isrc/esp32 lib_ignore = segger_rtt ; The 1.0 release of the TBEAM board diff --git a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp b/src/esp32/BluetoothSoftwareUpdate.cpp similarity index 100% rename from lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp rename to src/esp32/BluetoothSoftwareUpdate.cpp diff --git a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h b/src/esp32/BluetoothSoftwareUpdate.h similarity index 100% rename from lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h rename to src/esp32/BluetoothSoftwareUpdate.h diff --git a/lib/BluetoothOTA/src/BluetoothUtil.cpp b/src/esp32/BluetoothUtil.cpp similarity index 100% rename from lib/BluetoothOTA/src/BluetoothUtil.cpp rename to src/esp32/BluetoothUtil.cpp diff --git a/lib/BluetoothOTA/src/BluetoothUtil.h b/src/esp32/BluetoothUtil.h similarity index 100% rename from lib/BluetoothOTA/src/BluetoothUtil.h rename to src/esp32/BluetoothUtil.h diff --git a/lib/BluetoothOTA/src/CallbackCharacteristic.h b/src/esp32/CallbackCharacteristic.h similarity index 100% rename from lib/BluetoothOTA/src/CallbackCharacteristic.h rename to src/esp32/CallbackCharacteristic.h diff --git a/lib/BluetoothOTA/src/SimpleAllocator.cpp b/src/esp32/SimpleAllocator.cpp similarity index 100% rename from lib/BluetoothOTA/src/SimpleAllocator.cpp rename to src/esp32/SimpleAllocator.cpp diff --git a/lib/BluetoothOTA/src/SimpleAllocator.h b/src/esp32/SimpleAllocator.h similarity index 100% rename from lib/BluetoothOTA/src/SimpleAllocator.h rename to src/esp32/SimpleAllocator.h From 6961853ed792c45346b51728b01eb0d8fb8dd4be Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 15 May 2020 10:16:10 -0700 Subject: [PATCH 12/25] ble software update fixes --- src/esp32/BluetoothSoftwareUpdate.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/esp32/BluetoothSoftwareUpdate.cpp b/src/esp32/BluetoothSoftwareUpdate.cpp index f2fa7c9737..0f56cecaab 100644 --- a/src/esp32/BluetoothSoftwareUpdate.cpp +++ b/src/esp32/BluetoothSoftwareUpdate.cpp @@ -40,10 +40,11 @@ class TotalSizeCharacteristic : public CallbackCharacteristic crc.reset(); bool canBegin = Update.begin(len); DEBUG_MSG("Setting update size %u, result %d\n", len, canBegin); - if (!canBegin) + if (!canBegin) { // Indicate failure by forcing the size to 0 - c->setValue(0UL); - else { + uint32_t zero = 0; + c->setValue(zero); + } else { // This totally breaks abstraction to up up into the app layer for this, but quick hack to make sure we only // talk to one service during the sw update. // DEBUG_MSG("FIXME, crufty shutdown of mesh bluetooth for sw update."); @@ -113,12 +114,13 @@ class CRC32Characteristic : public CallbackCharacteristic rebootAtMsec = millis() + 5000; } else { DEBUG_MSG("Error Occurred. Error #: %d\n", Update.getError()); - - if (RadioLibInterface::instance) - RadioLibInterface::instance->startReceive(); // Resume radio } result = Update.getError(); } + + if (RadioLibInterface::instance) + RadioLibInterface::instance->startReceive(); // Resume radio + assert(resultC); resultC->setValue(&result, 1); resultC->notify(); From 95e952b896c39108a353606895d40dec831bf87e Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 16 May 2020 16:09:06 -0700 Subject: [PATCH 13/25] 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 edea28cd64..55d72fa873 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 69afcb9469..47a520c8d1 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 14/25] 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 ad27e29444..3706fa20c4 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 b0e1406b5d..03535b3854 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 15/25] 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 ed6eaf4fff..b60204fd5b 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 c94ffabf9a..ecaea70ac7 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 03535b3854..904c921e85 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 16/25] 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 634cdd10c8..be5444c1e1 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 17/25] 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 904c921e85..7f9780862c 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 53c3d9baa2be13efbc4af366d58661b4c227ae19 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 18 May 2020 17:02:51 -0700 Subject: [PATCH 18/25] 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 2b55a3a345..393abce67d 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 47a520c8d1..ae89750ddc 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 19/25] 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 | 48 ++++++++++++++++------------------ 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, 53 insertions(+), 35 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index 393abce67d..19e7e506c4 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 bc3ecd97e3..5799cb10b8 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 6db03dd4f8..e9941fb12b 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 - - 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); - - } else { - DEBUG_MSG("Ignoring a simple (0 hop) broadcast\n"); - } + 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 + + tosend->hop_limit--; // bump down the hop count + + 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 5f4b51baba..28aba7fe76 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 0d9783e146..04bb13ad66 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 3ad60005ca..123e128a53 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 6c7dbd79b0..8066175900 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 e09c4f4a96..78b9f661c1 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 20378371dc..77538a0bd8 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 20/25] 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 19e7e506c4..b809692a43 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 e9941fb12b..c40211a8ff 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 28aba7fe76..986deb3fde 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 04bb13ad66..f491ce508e 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 78b9f661c1..ba0c32f05a 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 3752a2fbde..21b928f229 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 77538a0bd8..03d75d33d8 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 cca4867987cdc2d3767ec1169f65bb36cb4cbe89 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 19 May 2020 10:27:28 -0700 Subject: [PATCH 21/25] 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 b809692a43..4bf2afa30a 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 5799cb10b8..e095ea92e6 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 c40211a8ff..f16405e46e 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 996e9f7aec..e7e1b96108 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 123e128a53..45ecc42a84 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 8066175900..4197637502 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 ba0c32f05a..5bf3b5eea4 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 0b2c5b8ce1..321576556b 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 d193d38ee5..ce9bc4e36d 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 22/25] 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 | 127 ++++++++++++++++++++++++++---------- src/mesh/Router.h | 24 ++++++- 8 files changed, 284 insertions(+), 90 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 2378458a69..3103335b5c 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 e7e1b96108..48a8f0bc76 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 986deb3fde..ee2905ad4a 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 f3328225be..f6e688e194 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 0000000000..6ea884ca23 --- /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 0000000000..75bedfb644 --- /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 21b928f229..a7d5707814 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) + // 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; + } 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 + 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 + // 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); + 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); + 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; - } + // 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; + 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 03d75d33d8..d0a8e029c4 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 23/25] 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 4bf2afa30a..fe5d9752ea 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 6ea884ca23..02833f8cea 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 75bedfb644..3798d9d68b 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 24/25] 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 560c52fa89..ca8f955c57 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 25/25] 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 fe5d9752ea..78f5eba86e 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 9a5704f669..30a448f975 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 22470f4fc2..38edf7d779 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 5bf3b5eea4..9d27107166 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 02833f8cea..eb6fc248ae 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);