From 190a3c2d6bbc33153f3b3a461f4ca634b2100b3e Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 9 May 2020 20:27:08 -0700 Subject: [PATCH 001/131] 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 002/131] 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 a10ff9f6a..47281dbeb 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 f5f7d04a6..7f5709c48 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 da7070172..c1672c77e 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 dee630e0c..e09c4f4a9 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 00434f90b..49ccb3d6b 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 000000000..ee1650ea2 --- /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 003/131] 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 000000000..ce86e09f5 --- /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 5050758f7..5283fdc4e 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 d90d85c9c..48bc6721f 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 4f4b6c409..69afcb946 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 61b61d677..644195500 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 b02a1a00f..ca606c53a 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 + +// 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_BAUDRATE 9600 - #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 b6c03846f..a3c060b18 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 bd85e9713..f5f219e9b 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 5033a4ecc..b792172ee 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 004/131] 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 23efd6ee3..711f31f01 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 5e2df6c99..484049369 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 9442bd25b..be286b503 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 b792172ee..d59e17503 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 005/131] 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 711f31f01..93ceb20d2 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 48bc6721f..2b55a3a34 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 484049369..3bf195cb2 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 c1672c77e..177a92169 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 4ca759ecc..b8bfa0b9c 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 000000000..9a5704f66 --- /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 000000000..3a182caeb --- /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 be286b503..d8a2e1fd5 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 006/131] 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 a3c060b18..9ba569947 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 177a92169..6db03dd4f 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, 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); - 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); - - 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 b8bfa0b9c..996e9f7ae 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 007/131] 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 644195500..aead63d98 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 3bf195cb2..bc3ecd97e 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 992144b82..5f4b51bab 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 8fe82b822..5d2e8ecd3 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 3a182caeb..22470f4fc 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 d8a2e1fd5..d193d38ee 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 c60907aa8..dd2f99c07 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 008/131] 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 ca606c53a..04f9d26a1 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 48af9b998..b0e1406b5 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 9ba569947..2378458a6 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 5d2e8ecd3..cc83f2f47 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 4f5fa2fdc..4b8db06b0 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 009/131] 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 ed1103911..f84d83be2 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 f951da32c..86ec08e13 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 010/131] move bluetooth OTA back into main tree for now --- .../src/BluetoothSoftwareUpdate.cpp | 103 +++++++++++------- .../src/BluetoothSoftwareUpdate.h | 5 +- src/mesh/RadioLibInterface.h | 21 ++-- 3 files changed, 78 insertions(+), 51 deletions(-) diff --git a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp b/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp index 62bdd499d..f2fa7c973 100644 --- a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp +++ b/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp @@ -1,21 +1,30 @@ -#include "BluetoothUtil.h" #include "BluetoothSoftwareUpdate.h" -#include "configuration.h" -#include -#include -#include -#include -#include +#include "BluetoothUtil.h" #include "CallbackCharacteristic.h" +#include "RadioLibInterface.h" +#include "configuration.h" +#include "lock.h" +#include +#include +#include +#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 @@ public: { 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 @@ public: 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 @@ public: } }; - - 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 60b1f6696..60517a7f2 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 a090d132a..5f9149d71 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 011/131] 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 aead63d98..ad27e2944 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 012/131] 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 f2fa7c973..0f56cecaa 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 013/131] 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 edea28cd6..55d72fa87 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 69afcb946..47a520c8d 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 014/131] 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 ad27e2944..3706fa20c 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 b0e1406b5..03535b385 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 015/131] 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 ed6eaf4ff..b60204fd5 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 c94ffabf9..ecaea70ac 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 03535b385..904c921e8 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 016/131] 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 634cdd10c..be5444c1e 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 017/131] 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 904c921e8..7f9780862 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 018/131] 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 2b55a3a34..393abce67 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 47a520c8d..ae89750dd 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 019/131] Use the hop_limit field of MeshPacket to limit max delivery depth in the mesh. --- docs/software/mesh-alg.md | 8 +++---- proto | 2 +- src/mesh/FloodingRouter.cpp | 44 ++++++++++++++++------------------ src/mesh/MeshService.cpp | 1 + src/mesh/MeshTypes.h | 9 +++++++ src/mesh/RadioInterface.cpp | 3 ++- src/mesh/RadioInterface.h | 9 ++++++- src/mesh/RadioLibInterface.cpp | 5 +++- src/mesh/Router.h | 3 ++- 9 files changed, 51 insertions(+), 33 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index 393abce67..19e7e506c 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 bc3ecd97e..5799cb10b 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 6db03dd4f..e9941fb12 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -2,8 +2,6 @@ #include "configuration.h" #include "mesh-pb-constants.h" -static bool supportFlooding = true; // Sometimes to simplify debugging we want jusT simple broadcast only - FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) {} /** @@ -13,9 +11,7 @@ FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) {} */ ErrorCode FloodingRouter::send(MeshPacket *p) { - // We update our table of recent broadcasts, even for messages we send - if (supportFlooding) - wasSeenRecently(p); + wasSeenRecently(p); return Router::send(p); } @@ -29,28 +25,28 @@ ErrorCode FloodingRouter::send(MeshPacket *p) */ void FloodingRouter::handleReceived(MeshPacket *p) { - if (supportFlooding) { - if (wasSeenRecently(p)) { - DEBUG_MSG("Ignoring incoming floodmsg, because we've already seen it\n"); - packetPool.release(p); - } else { - if (p->to == NODENUM_BROADCAST) { - if (p->id != 0) { - MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it + if (wasSeenRecently(p)) { + DEBUG_MSG("Ignoring incoming msg, because we've already seen it\n"); + packetPool.release(p); + } else { + if (p->to == NODENUM_BROADCAST && p->hop_limit > 0) { + if (p->id != 0) { + MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it - DEBUG_MSG("Rebroadcasting received floodmsg to neighbors, fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - // Note: we are careful to resend using the original senders node id - // We are careful not to call our hooked version of send() - because we don't want to check this again - Router::send(tosend); + tosend->hop_limit--; // bump down the hop count - } else { - DEBUG_MSG("Ignoring a simple (0 hop) broadcast\n"); - } + DEBUG_MSG("Rebroadcasting received floodmsg to neighbors, fr=0x%x,to=0x%x,id=%d,hop_limit=%d\n", p->from, p->to, + p->id, tosend->hop_limit); + // Note: we are careful to resend using the original senders node id + // We are careful not to call our hooked version of send() - because we don't want to check this again + Router::send(tosend); + + } else { + DEBUG_MSG("Ignoring a simple (0 id) broadcast\n"); } - - // handle the packet as normal - Router::handleReceived(p); } - } else + + // handle the packet as normal Router::handleReceived(p); + } } diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 5f4b51bab..28aba7fe7 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 0d9783e14..04bb13ad6 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 3ad60005c..123e128a5 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 6c7dbd79b..806617590 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 e09c4f4a9..78b9f661c 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 20378371d..77538a0bd 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 020/131] 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 19e7e506c..b809692a4 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 e9941fb12..c40211a8f 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 28aba7fe7..986deb3fd 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 04bb13ad6..f491ce508 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 78b9f661c..ba0c32f05 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 3752a2fbd..21b928f22 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 77538a0bd..03d75d33d 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 021/131] 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 b809692a4..4bf2afa30 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 5799cb10b..e095ea92e 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 c40211a8f..f16405e46 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 996e9f7ae..e7e1b9610 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 123e128a5..45ecc42a8 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 806617590..419763750 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 ba0c32f05..5bf3b5eea 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 0b2c5b8ce..321576556 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 d193d38ee..ce9bc4e36 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 022/131] wip reliable unicast (1 hop) --- src/main.cpp | 4 +- src/mesh/FloodingRouter.h | 3 +- src/mesh/MeshService.cpp | 51 ++------------ src/mesh/MeshService.h | 3 - src/mesh/ReliableRouter.cpp | 99 ++++++++++++++++++++++++++ src/mesh/ReliableRouter.h | 63 +++++++++++++++++ src/mesh/Router.cpp | 137 +++++++++++++++++++++++++----------- src/mesh/Router.h | 24 ++++++- 8 files changed, 289 insertions(+), 95 deletions(-) create mode 100644 src/mesh/ReliableRouter.cpp create mode 100644 src/mesh/ReliableRouter.h diff --git a/src/main.cpp b/src/main.cpp index 2378458a6..3103335b5 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 e7e1b9610..48a8f0bc7 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 986deb3fd..ee2905ad4 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 f3328225b..f6e688e19 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 000000000..6ea884ca2 --- /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 000000000..75bedfb64 --- /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 21b928f22..a7d570781 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -44,6 +44,39 @@ void Router::loop() } } +#define NUM_PACKET_ID 255 // 0 is consider invalid + +/// Generate a unique packet id +// FIXME, move this someplace better +PacketId generatePacketId() +{ + static uint32_t i; // Note: trying to keep this in noinit didn't help for working across reboots + static bool didInit = false; + + if (!didInit) { + didInit = true; + i = random(0, NUM_PACKET_ID + + 1); // pick a random initial sequence number at boot (to prevent repeated reboots always starting at 0) + } + + i++; + return (i % NUM_PACKET_ID) + 1; // return number between 1 and 255 +} + +MeshPacket *Router::allocForSending() +{ + MeshPacket *p = packetPool.allocZeroed(); + + p->which_payload = MeshPacket_decoded_tag; // Assume payload is decoded at start. + p->from = nodeDB.getNodeNum(); + p->to = NODENUM_BROADCAST; + p->hop_limit = HOP_RELIABLE; + p->id = generatePacketId(); + p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp + + return p; +} + /** * Send a packet on a suitable interface. This routine will * later free() the packet to pool. This routine is not allowed to stall. @@ -51,33 +84,40 @@ void Router::loop() */ ErrorCode Router::send(MeshPacket *p) { - // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) - - assert(p->which_payload == MeshPacket_encrypted_tag || - p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now - - // First convert from protobufs to raw bytes - if (p->which_payload == MeshPacket_decoded_tag) { - static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union - - size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded); - - assert(numbytes <= MAX_RHPACKETLEN); - crypto->encrypt(p->from, p->id, numbytes, bytes); - - // Copy back into the packet and set the variant type - memcpy(p->encrypted.bytes, bytes, numbytes); - p->encrypted.size = numbytes; - p->which_payload = MeshPacket_encrypted_tag; - } - - if (iface) { - // DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - return iface->send(p); - } else { - DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + // If this packet was destined only to apps on our node, don't send it out into the network + if (p->to == nodeDB.getNodeNum()) { + DEBUG_MSG("Dropping locally processed message\n"); packetPool.release(p); - return ERRNO_NO_INTERFACES; + return ERRNO_OK; + } else { + // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) + + assert(p->which_payload == MeshPacket_encrypted_tag || + p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now + + // First convert from protobufs to raw bytes + if (p->which_payload == MeshPacket_decoded_tag) { + static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union + + size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded); + + assert(numbytes <= MAX_RHPACKETLEN); + crypto->encrypt(p->from, p->id, numbytes, bytes); + + // Copy back into the packet and set the variant type + memcpy(p->encrypted.bytes, bytes, numbytes); + p->encrypted.size = numbytes; + p->which_payload = MeshPacket_encrypted_tag; + } + + if (iface) { + // DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + return iface->send(p); + } else { + DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + packetPool.release(p); + return ERRNO_NO_INTERFACES; + } } } @@ -90,18 +130,12 @@ void Router::sniffReceived(MeshPacket *p) DEBUG_MSG("Sniffing packet not sent to us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); } -/** - * Handle any packet that is received by an interface on this node. - * Note: some packets may merely being passed through this node and will be forwarded elsewhere. - */ -void Router::handleReceived(MeshPacket *p) +bool Router::perhapsDecode(MeshPacket *p) { - // FIXME, this class shouldn't EVER need to know about the GPS, move getValidTime() into a non gps dependent function - // Also, we should set the time from the ISR and it should have msec level resolution - p->rx_time = getValidTime(); // store the arrival timestamp for the phone + if (p->which_payload == MeshPacket_decoded_tag) + return true; // If packet was already decoded just return - assert(p->which_payload == - MeshPacket_encrypted_tag); // I _think_ the only thing that pushes to us is raw devices that just received packets + assert(p->which_payload == MeshPacket_encrypted_tag); // FIXME - someday don't send routing packets encrypted. That would allow us to route for other channels without // being able to decrypt their data. @@ -113,14 +147,37 @@ void Router::handleReceived(MeshPacket *p) // Take those raw bytes and convert them back into a well structured protobuf we can understand if (!pb_decode_from_bytes(bytes, p->encrypted.size, SubPacket_fields, &p->decoded)) { - DEBUG_MSG("Invalid protobufs in received mesh packet, discarding.\n"); + DEBUG_MSG("Invalid protobufs in received mesh packet!\n"); + return false; } else { - // parsing was successful, queue for our recipient + // parsing was successful p->which_payload = MeshPacket_decoded_tag; + return true; + } +} + +NodeNum Router::getNodeNum() +{ + return nodeDB.getNodeNum(); +} + +/** + * Handle any packet that is received by an interface on this node. + * Note: some packets may merely being passed through this node and will be forwarded elsewhere. + */ +void Router::handleReceived(MeshPacket *p) +{ + // FIXME, this class shouldn't EVER need to know about the GPS, move getValidTime() into a non gps dependent function + // Also, we should set the time from the ISR and it should have msec level resolution + p->rx_time = getValidTime(); // store the arrival timestamp for the phone + + // Take those raw bytes and convert them back into a well structured protobuf we can understand + if (perhapsDecode(p)) { + // parsing was successful, queue for our recipient sniffReceived(p); - uint8_t ourAddr = nodeDB.getNodeNum(); - if (p->to == NODENUM_BROADCAST || p->to == ourAddr) { + + if (p->to == NODENUM_BROADCAST || p->to == getNodeNum()) { DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); notifyPacketReceived.notifyObservers(p); } diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 03d75d33d..d0a8e029c 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 023/131] 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 4bf2afa30..fe5d9752e 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 6ea884ca2..02833f8ce 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 75bedfb64..3798d9d68 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 024/131] 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 560c52fa8..ca8f955c5 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 025/131] 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 fe5d9752e..78f5eba86 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 9a5704f66..30a448f97 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 22470f4fc..38edf7d77 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 5bf3b5eea..9d2710716 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 02833f8ce..eb6fc248a 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -46,7 +46,7 @@ void ReliableRouter::handleReceived(MeshPacket *p) // we are careful to only read/update wasSeenRecently _after_ confirming this is an ack (to not mess // up broadcasts) - if ((ackId || nakId) && !wasSeenRecently(p)) { + if ((ackId || nakId) && !wasSeenRecently(p, false)) { if (ackId) { DEBUG_MSG("Received a ack=%d, stopping retransmissions\n", ackId); stopRetransmission(p->to, ackId); From 0271df0657ca28195644fd1306130c33aa13c41a Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 21 May 2020 12:47:08 -0700 Subject: [PATCH 026/131] add beginnings of full DSR routing --- src/mesh/DSRRouter.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++ src/mesh/DSRRouter.h | 39 ++++++++++++++++++++ src/mesh/Router.cpp | 9 +++-- src/mesh/Router.h | 2 +- 4 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 src/mesh/DSRRouter.cpp create mode 100644 src/mesh/DSRRouter.h diff --git a/src/mesh/DSRRouter.cpp b/src/mesh/DSRRouter.cpp new file mode 100644 index 000000000..00c2a80d3 --- /dev/null +++ b/src/mesh/DSRRouter.cpp @@ -0,0 +1,80 @@ +#include "DSRRouter.h" +#include "configuration.h" + +/* when we receive any packet + +- sniff and update tables (especially useful to find adjacent nodes). Update user, network and position info. +- if we need to route() that packet, resend it to the next_hop based on our nodedb. +- if it is broadcast or destined for our node, deliver locally +- handle routereply/routeerror/routediscovery messages as described below +- then free it + +routeDiscovery + +- if we've already passed through us (or is from us), then it ignore it +- use the nodes already mentioned in the request to update our routing table +- if they were looking for us, send back a routereply +- if max_hops is zero and they weren't looking for us, drop (FIXME, send back error - I think not though?) +- if we receive a discovery packet, we use it to populate next_hop (if needed) towards the requester (after decrementing max_hops) +- if we receive a discovery packet, and we have a next_hop in our nodedb for that destination we send a (reliable) we send a route +reply towards the requester + +when sending any reliable packet + +- if timeout doing retries, send a routeError (nak) message back towards the original requester. all nodes eavesdrop on that +packet and update their route caches. + +when we receive a routereply packet + +- update next_hop on the node, if the new reply needs fewer hops than the existing one (we prefer shorter paths). fixme, someday +use a better heuristic + +when we receive a routeError packet + +- delete the route for that failed recipient, restartRouteDiscovery() +- if we receive routeerror in response to a discovery, +- fixme, eventually keep caches of possible other routes. +*/ + +void DSRRouter::sniffReceived(const MeshPacket *p) +{ + + // FIXME, update nodedb + + // Handle route discovery packets (will be a broadcast message) + if (p->decoded.which_payload == SubPacket_request_tag) { + // FIXME - always start request with the senders nodenum + + if (weAreInRoute(p->decoded.request)) { + DEBUG_MSG("Ignoring a route request that contains us\n"); + } else { + updateRoutes(p->decoded.request, false); // Update our routing tables based on the route that came in so far on this request + + if (p->decoded.dest == getNodeNum()) { + // They were looking for us, send back a route reply (the sender address will be first in the list) + sendRouteReply(p->decoded.request); + } else { + // They were looking for someone else, forward it along (as a zero hop broadcast) + NodeNum nextHop = getNextHop(p->decoded.dest); + if (nextHop) { + // in our route cache, reply to the requester (the sender address will be first in the list) + sendRouteReply(p->decoded.request, nextHop); + } else { + // Not in our route cache, rebroadcast on their behalf (after adding ourselves to the request route) + resendRouteRequest(p); + } + } + } + } + + // Handle regular packets + if (p->to == getNodeNum()) { // Destined for us (at least for this hop) + + // We need to route this packet + if (p->decoded.dest != p->to) { + // FIXME + } + } + + return ReliableRouter::sniffReceived(p); +} \ No newline at end of file diff --git a/src/mesh/DSRRouter.h b/src/mesh/DSRRouter.h new file mode 100644 index 000000000..5ecbdc8e5 --- /dev/null +++ b/src/mesh/DSRRouter.h @@ -0,0 +1,39 @@ +#include "ReliableRouter.h" + +class DSRRouter : public ReliableRouter +{ + + protected: + /** + * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to + * update routing tables etc... based on what we overhear (even for messages not destined to our node) + */ + virtual void sniffReceived(const MeshPacket *p); + + private: + /** + * Does our node appear in the specified route + */ + bool weAreInRoute(const RouteDiscovery &route); + + /** + * Given a DSR route, use that route to update our DB of possible routes + **/ + void updateRoutes(const RouteDiscovery &route, bool reverse); + + /** + * send back a route reply (the sender address will be first in the list) + */ + void sendRouteReply(const RouteDiscovery &route, NodeNum toAppend = 0); + + /** + * Given a nodenum return the next node we should forward to if we want to reach that node. + * + * @return 0 if no route found + */ + NodeNum getNextHop(NodeNum dest); + + /** Not in our route cache, rebroadcast on their behalf (after adding ourselves to the request route) + */ + void resendRouteRequest(const MeshPacket *p); +}; \ No newline at end of file diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index a7d570781..428f19fee 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -90,6 +90,10 @@ ErrorCode Router::send(MeshPacket *p) packetPool.release(p); return ERRNO_OK; } else { + // Never set the want_ack flag on broadcast packets sent over the air. + if (p->to == NODENUM_BROADCAST) + p->want_ack = false; + // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) assert(p->which_payload == MeshPacket_encrypted_tag || @@ -125,9 +129,10 @@ ErrorCode Router::send(MeshPacket *p) * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to * update routing tables etc... based on what we overhear (even for messages not destined to our node) */ -void Router::sniffReceived(MeshPacket *p) +void Router::sniffReceived(const MeshPacket *p) { - DEBUG_MSG("Sniffing packet not sent to us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + DEBUG_MSG("FIXME-update-db Sniffing packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + } bool Router::perhapsDecode(MeshPacket *p) diff --git a/src/mesh/Router.h b/src/mesh/Router.h index d0a8e029c..0f06ce3e9 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -75,7 +75,7 @@ class Router * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to * update routing tables etc... based on what we overhear (even for messages not destined to our node) */ - virtual void sniffReceived(MeshPacket *p); + virtual void sniffReceived(const MeshPacket *p); /** * Remove any encryption and decode the protobufs inside this packet (if necessary). From e2cbccb1336dee6c850ef73d8938e58b4dea96ac Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 21 May 2020 12:47:41 -0700 Subject: [PATCH 027/131] add want_ack support for broadcast packets --- docs/software/mesh-alg.md | 19 +++++++++++++------ proto | 2 +- src/mesh/FloodingRouter.cpp | 2 +- src/mesh/ReliableRouter.cpp | 20 ++++++++++++++++---- src/mesh/ReliableRouter.h | 12 ++++++++++-- 5 files changed, 41 insertions(+), 14 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index 78f5eba86..d4ae71215 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -2,6 +2,10 @@ great source of papers and class notes: http://www.cs.jhu.edu/~cs647/ +flood routing improvements + +- DONE if we don't see anyone rebroadcast our want_ack=true broadcasts, retry as needed. + reliable messaging tasks (stage one for DSR): - DONE generalize naive flooding @@ -19,9 +23,6 @@ reliable messaging tasks (stage one for DSR): dsr tasks -- do "hop by hop" routing -- when sending, if destnodeinfo.next_hop is zero (and no message is already waiting for an arp for that node), startRouteDiscovery() for that node. Queue the message in the 'waiting for arp queue' so we can send it later when then the arp completes. -- otherwise, use next_hop and start sending a message (with ack request) towards that node. - Don't use broadcasts for the network pings (close open github issue) - add ignoreSenders to radioconfig to allow testing different mesh topologies by refusing to see certain senders - test multihop delivery with the python framework @@ -34,6 +35,12 @@ optimizations / low priority: - handle 51 day rollover in doRetransmissions - use a priority queue for the messages waiting to send. Send acks first, then routing messages, then data messages, then broadcasts? +when we send a packet + +- do "hop by hop" routing +- when sending, if destnodeinfo.next_hop is zero (and no message is already waiting for an arp for that node), startRouteDiscovery() for that node. Queue the message in the 'waiting for arp queue' so we can send it later when then the arp completes. +- otherwise, use next_hop and start sending a message (with ack request) towards that node (starting with next_hop). + when we receive any packet - sniff and update tables (especially useful to find adjacent nodes). Update user, network and position info. @@ -47,13 +54,13 @@ routeDiscovery - if we've already passed through us (or is from us), then it ignore it - use the nodes already mentioned in the request to update our routing table - if they were looking for us, send back a routereply -- if max_hops is zero and they weren't looking for us, drop (FIXME, send back error - I think not though?) -- if we receive a discovery packet, we use it to populate next_hop (if needed) towards the requester (after decrementing max_hops) +- NOT DOING FOR NOW -if max_hops is zero and they weren't looking for us, drop (FIXME, send back error - I think not though?) +- if we receive a discovery packet, and we don't have next_hop set in our nodedb, we use it to populate next_hop (if needed) towards the requester (after decrementing max_hops) - if we receive a discovery packet, and we have a next_hop in our nodedb for that destination we send a (reliable) we send a route reply towards the requester when sending any reliable packet -- if we get back a nak, send a routeError message back towards the original requester. all nodes eavesdrop on that packet and update their route caches +- if timeout doing retries, send a routeError (nak) message back towards the original requester. all nodes eavesdrop on that packet and update their route caches. when we receive a routereply packet diff --git a/proto b/proto index e095ea92e..bfae47bdc 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit e095ea92e62edc3f5dd6864c3d08d113fd8842e2 +Subproject commit bfae47bdc0da23bb1e53fed054d3de2d161389bc diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index f16405e46..d3cc5cbde 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -11,7 +11,7 @@ FloodingRouter::FloodingRouter() {} */ ErrorCode FloodingRouter::send(MeshPacket *p) { - // Add any messages _we_ send to the seen message list + // Add any messages _we_ send to the seen message list (so we will ignore all retransmissions we see) wasSeenRecently(p); // FIXME, move this to a sniffSent method return Router::send(p); diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index eb6fc248a..e14355ece 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -33,7 +33,17 @@ ErrorCode ReliableRouter::send(MeshPacket *p) */ void ReliableRouter::handleReceived(MeshPacket *p) { - if (p->to == getNodeNum()) { // ignore ack/nak/want_ack packets that are not address to us (for now) + NodeNum ourNode = getNodeNum(); + + if (p->from == ourNode && p->to == NODENUM_BROADCAST) { + // We are seeing someone rebroadcast one of our broadcast attempts. + // If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for + // the original sending process. + if (stopRetransmission(p->from, p->id)) { + DEBUG_MSG("Someone is retransmitting for us, generate implicit ack"); + sendAckNak(true, p->from, p->id); + } + } else if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (for now) if (p->want_ack) { sendAckNak(true, p->from, p->id); } @@ -95,20 +105,22 @@ PendingPacket::PendingPacket(MeshPacket *p) /** * Stop any retransmissions we are doing of the specified node/packet ID pair */ -void ReliableRouter::stopRetransmission(NodeNum from, PacketId id) +bool ReliableRouter::stopRetransmission(NodeNum from, PacketId id) { auto key = GlobalPacketId(from, id); stopRetransmission(key); } -void ReliableRouter::stopRetransmission(GlobalPacketId key) +bool ReliableRouter::stopRetransmission(GlobalPacketId key) { auto old = pending.find(key); // If we have an old record, someone messed up because id got reused if (old != pending.end()) { auto numErased = pending.erase(key); assert(numErased == 1); packetPool.release(old->second.packet); - } + return true; + } else + return false; } /** * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h index 3798d9d68..2217c92c0 100644 --- a/src/mesh/ReliableRouter.h +++ b/src/mesh/ReliableRouter.h @@ -39,6 +39,12 @@ struct PendingPacket { /** Starts at NUM_RETRANSMISSIONS -1(normally 3) and counts down. Once zero it will be removed from the list */ uint8_t numRetransmissions; + /** True if we have started trying to find a route - for DSR usage + * While trying to find a route we don't actually send the data packet. We just leave it here pending until + * we have a route or we've failed to find one. + */ + bool wantRoute = false; + PendingPacket() {} PendingPacket(MeshPacket *p); @@ -98,9 +104,11 @@ class ReliableRouter : public FloodingRouter /** * Stop any retransmissions we are doing of the specified node/packet ID pair + * + * @return true if we found and removed a transmission with this ID */ - void stopRetransmission(NodeNum from, PacketId id); - void stopRetransmission(GlobalPacketId p); + bool stopRetransmission(NodeNum from, PacketId id); + bool stopRetransmission(GlobalPacketId p); /** * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. From e75561016b5f94850abbae90b0498aeac061c5d8 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 21 May 2020 15:55:57 -0700 Subject: [PATCH 028/131] retransmissions work again --- src/mesh/PacketHistory.cpp | 4 ++-- src/mesh/ReliableRouter.cpp | 17 ++++++++++++----- src/mesh/ReliableRouter.h | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index 30a448f97..7361daad8 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -29,7 +29,7 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p, bool withUpdate) recentPackets.erase(recentPackets.begin() + i); // delete old record } else { if (r.id == p->id && r.sender == p->from) { - DEBUG_MSG("Found existing broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + DEBUG_MSG("Found existing packet record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); // Update the time on this record to now if (withUpdate) @@ -48,7 +48,7 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p, bool withUpdate) r.sender = p->from; r.rxTimeMsec = now; recentPackets.push_back(r); - DEBUG_MSG("Adding broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + DEBUG_MSG("Adding packet record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); } return false; diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index e14355ece..78c6b93a0 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -36,6 +36,8 @@ void ReliableRouter::handleReceived(MeshPacket *p) NodeNum ourNode = getNodeNum(); if (p->from == ourNode && p->to == NODENUM_BROADCAST) { + DEBUG_MSG("Received someone rebroadcasting for us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + // We are seeing someone rebroadcast one of our broadcast attempts. // If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for // the original sending process. @@ -77,7 +79,7 @@ void ReliableRouter::handleReceived(MeshPacket *p) */ void ReliableRouter::sendAckNak(bool isAck, NodeNum to, PacketId idFrom) { - DEBUG_MSG("Sending an ack=%d,to=%d,idFrom=%d", isAck, to, idFrom); + DEBUG_MSG("Sending an ack=%d,to=%d,idFrom=%d\n", isAck, to, idFrom); auto p = allocForSending(); p->hop_limit = 0; // Assume just immediate neighbors for now p->to = to; @@ -108,7 +110,7 @@ PendingPacket::PendingPacket(MeshPacket *p) bool ReliableRouter::stopRetransmission(NodeNum from, PacketId id) { auto key = GlobalPacketId(from, id); - stopRetransmission(key); + return stopRetransmission(key); } bool ReliableRouter::stopRetransmission(GlobalPacketId key) @@ -150,12 +152,17 @@ void ReliableRouter::doRetransmissions() // FIXME, handle 51 day rolloever here!!! if (p.nextTxMsec <= now) { if (p.numRetransmissions == 0) { - DEBUG_MSG("Reliable send failed, returning a nak\n"); + DEBUG_MSG("Reliable send failed, returning a nak fr=0x%x,to=0x%x,id=%d\n", p.packet->from, p.packet->to, + p.packet->id); sendAckNak(false, p.packet->from, p.packet->id); stopRetransmission(it->first); } else { - DEBUG_MSG("Sending reliable retransmission\n"); - send(packetPool.allocCopy(*p.packet)); + DEBUG_MSG("Sending reliable retransmission fr=0x%x,to=0x%x,id=%d, tries left=%d\n", p.packet->from, p.packet->to, + p.packet->id, p.numRetransmissions); + + // Note: we call the superclass version because we don't want to have our version of send() add a new + // retransmission record + FloodingRouter::send(packetPool.allocCopy(*p.packet)); // Queue again --p.numRetransmissions; diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h index 2217c92c0..e63806af5 100644 --- a/src/mesh/ReliableRouter.h +++ b/src/mesh/ReliableRouter.h @@ -48,7 +48,7 @@ struct PendingPacket { PendingPacket() {} PendingPacket(MeshPacket *p); - void setNextTx() { nextTxMsec = millis() + random(10 * 1000, 12 * 1000); } + void setNextTx() { nextTxMsec = millis() + random(30 * 1000, 22 * 1000); } }; class GlobalPacketIdHashFunction From 9dd88281afb4bd7da721ee72b03fe5ba4fdf7d32 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 21 May 2020 16:34:16 -0700 Subject: [PATCH 029/131] reliable broadcast now works --- src/mesh/MeshService.cpp | 2 +- src/mesh/ReliableRouter.cpp | 11 +++-- src/mesh/ReliableRouter.h | 2 +- src/mesh/Router.cpp | 80 +++++++++++++++++++------------------ src/mesh/Router.h | 13 ++++-- 5 files changed, 61 insertions(+), 47 deletions(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index ee2905ad4..540ca7cb1 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -247,7 +247,7 @@ void MeshService::sendToMesh(MeshPacket *p) } // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it - if (router.send(p) != ERRNO_OK) { + if (router.sendLocal(p) != ERRNO_OK) { DEBUG_MSG("No radio was able to send packet, discarding...\n"); releaseToPool(p); } diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 78c6b93a0..0500f2799 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -12,6 +12,11 @@ ErrorCode ReliableRouter::send(MeshPacket *p) { if (p->want_ack) { + // If someone asks for acks on broadcast, we need the hop limit to be at least one, so that first node that receives our + // message will rebroadcast + if (p->to == NODENUM_BROADCAST && p->hop_limit == 0) + p->hop_limit = 1; + auto copy = packetPool.allocCopy(*p); startRetransmission(copy); } @@ -42,7 +47,7 @@ void ReliableRouter::handleReceived(MeshPacket *p) // If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for // the original sending process. if (stopRetransmission(p->from, p->id)) { - DEBUG_MSG("Someone is retransmitting for us, generate implicit ack"); + DEBUG_MSG("Someone is retransmitting for us, generate implicit ack\n"); sendAckNak(true, p->from, p->id); } } else if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (for now) @@ -79,10 +84,10 @@ void ReliableRouter::handleReceived(MeshPacket *p) */ void ReliableRouter::sendAckNak(bool isAck, NodeNum to, PacketId idFrom) { - DEBUG_MSG("Sending an ack=%d,to=%d,idFrom=%d\n", isAck, to, idFrom); auto p = allocForSending(); p->hop_limit = 0; // Assume just immediate neighbors for now p->to = to; + DEBUG_MSG("Sending an ack=0x%x,to=0x%x,idFrom=%d,id=%d\n", isAck, to, idFrom, p->id); if (isAck) { p->decoded.ack.success_id = idFrom; @@ -92,7 +97,7 @@ void ReliableRouter::sendAckNak(bool isAck, NodeNum to, PacketId idFrom) p->decoded.which_ack = SubPacket_fail_id_tag; } - send(p); + sendLocal(p); // we sometimes send directly to the local node } #define NUM_RETRANSMISSIONS 3 diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h index e63806af5..7030793ae 100644 --- a/src/mesh/ReliableRouter.h +++ b/src/mesh/ReliableRouter.h @@ -48,7 +48,7 @@ struct PendingPacket { PendingPacket() {} PendingPacket(MeshPacket *p); - void setNextTx() { nextTxMsec = millis() + random(30 * 1000, 22 * 1000); } + void setNextTx() { nextTxMsec = millis() + random(20 * 1000, 22 * 1000); } }; class GlobalPacketIdHashFunction diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 428f19fee..0ef7b8333 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -77,6 +77,16 @@ MeshPacket *Router::allocForSending() return p; } +ErrorCode Router::sendLocal(MeshPacket *p) +{ + if (p->to == nodeDB.getNodeNum()) { + DEBUG_MSG("Enqueuing internal message for the receive queue\n"); + fromRadioQueue.enqueue(p); + return ERRNO_OK; + } else + return send(p); +} + /** * Send a packet on a suitable interface. This routine will * later free() the packet to pool. This routine is not allowed to stall. @@ -84,44 +94,39 @@ MeshPacket *Router::allocForSending() */ ErrorCode Router::send(MeshPacket *p) { - // If this packet was destined only to apps on our node, don't send it out into the network - if (p->to == nodeDB.getNodeNum()) { - DEBUG_MSG("Dropping locally processed message\n"); - packetPool.release(p); - return ERRNO_OK; + assert(p->to != nodeDB.getNodeNum()); // should have already been handled by sendLocal + + // Never set the want_ack flag on broadcast packets sent over the air. + if (p->to == NODENUM_BROADCAST) + p->want_ack = false; + + // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) + + assert(p->which_payload == MeshPacket_encrypted_tag || + p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now + + // First convert from protobufs to raw bytes + if (p->which_payload == MeshPacket_decoded_tag) { + static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union + + size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded); + + assert(numbytes <= MAX_RHPACKETLEN); + crypto->encrypt(p->from, p->id, numbytes, bytes); + + // Copy back into the packet and set the variant type + memcpy(p->encrypted.bytes, bytes, numbytes); + p->encrypted.size = numbytes; + p->which_payload = MeshPacket_encrypted_tag; + } + + if (iface) { + // DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + return iface->send(p); } else { - // Never set the want_ack flag on broadcast packets sent over the air. - if (p->to == NODENUM_BROADCAST) - p->want_ack = false; - - // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) - - assert(p->which_payload == MeshPacket_encrypted_tag || - p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now - - // First convert from protobufs to raw bytes - if (p->which_payload == MeshPacket_decoded_tag) { - static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union - - size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded); - - assert(numbytes <= MAX_RHPACKETLEN); - crypto->encrypt(p->from, p->id, numbytes, bytes); - - // Copy back into the packet and set the variant type - memcpy(p->encrypted.bytes, bytes, numbytes); - p->encrypted.size = numbytes; - p->which_payload = MeshPacket_encrypted_tag; - } - - if (iface) { - // DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - return iface->send(p); - } else { - DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - packetPool.release(p); - return ERRNO_NO_INTERFACES; - } + DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + packetPool.release(p); + return ERRNO_NO_INTERFACES; } } @@ -132,7 +137,6 @@ ErrorCode Router::send(MeshPacket *p) void Router::sniffReceived(const MeshPacket *p) { DEBUG_MSG("FIXME-update-db Sniffing packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - } bool Router::perhapsDecode(MeshPacket *p) diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 0f06ce3e9..8c811667e 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -47,11 +47,9 @@ class Router virtual void loop(); /** - * Send a packet on a suitable interface. This routine will - * later free() the packet to pool. This routine is not allowed to stall. - * If the txmit queue is full it might return an error + * Works like send, but if we are sending to the local node, we directly put the message in the receive queue */ - virtual ErrorCode send(MeshPacket *p); + ErrorCode sendLocal(MeshPacket *p); /// Allocate and return a meshpacket which defaults as send to broadcast from the current node. MeshPacket *allocForSending(); @@ -61,6 +59,13 @@ class Router NodeNum getNodeNum(); protected: + /** + * Send a packet on a suitable interface. This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ + virtual ErrorCode send(MeshPacket *p); + /** * Called from loop() * Handle any packet that is received by an interface on this node. From d2de04d5b2a3252a961fe81916069c7d7901ce47 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 21 May 2020 17:21:44 -0700 Subject: [PATCH 030/131] Fix #59 no need for broadcasts when showing new node pane --- src/PowerFSM.cpp | 5 +++-- src/mesh/NodeDB.cpp | 6 ++++++ src/mesh/NodeDB.h | 6 ++++++ src/mesh/RadioLibInterface.cpp | 2 +- src/screen.cpp | 10 ++++++++++ 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index b60204fd5..22b2ccd72 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -113,8 +113,9 @@ static void onEnter() uint32_t now = millis(); - if (now - lastPingMs > 60 * 1000) { // if more than a minute since our last press, ask other nodes to update their state - service.sendNetworkPing(NODENUM_BROADCAST, true); + if (now - lastPingMs > 30 * 1000) { // if more than a minute since our last press, ask other nodes to update their state + if (displayedNodeNum) + service.sendNetworkPing(displayedNodeNum, true); // Refresh the currently displayed node lastPingMs = now; } } diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index cc83f2f47..d9e45dd54 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -48,6 +48,12 @@ User &owner = devicestate.owner; static uint8_t ourMacAddr[6]; +/** + * The node number the user is currently looking at + * 0 if none + */ +NodeNum displayedNodeNum; + NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_count) {} void NodeDB::resetRadioConfig() diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index ddabeb236..0171228a8 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -95,4 +95,10 @@ class NodeDB void loadFromDisk(); }; +/** + * The node number the user is currently looking at + * 0 if none + */ +extern NodeNum displayedNodeNum; + extern NodeDB nodeDB; diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 9d2710716..1471e3a98 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, want_ack=%d\n", txp->from, txp->id, txp->want_ack); + DEBUG_MSG("Starting low level send from=0x%x, to=0x%x, id=%u, want_ack=%d\n", txp->from, txp->to, txp->id, txp->want_ack); setStandby(); // Cancel any already in process receives size_t numbytes = beginSending(txp); diff --git a/src/screen.cpp b/src/screen.cpp index dd2f99c07..47f4f1c53 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -23,6 +23,7 @@ along with this program. If not, see . #include #include "GPS.h" +#include "MeshService.h" #include "NodeDB.h" #include "configuration.h" #include "fonts.h" @@ -78,6 +79,8 @@ static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, /// Draw the last text message we received static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { + displayedNodeNum = 0; // Not currently showing a node pane + MeshPacket &mp = devicestate.rx_text_message; NodeInfo *node = nodeDB.getNode(mp.from); // DEBUG_MSG("drawing text message from 0x%x: %s\n", mp.from, @@ -303,7 +306,12 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ if (n->num == nodeDB.getNodeNum()) { // Don't show our node, just skip to next nodeIndex = (nodeIndex + 1) % nodeDB.getNumNodes(); + n = nodeDB.getNodeByIndex(nodeIndex); } + + // We just changed to a new node screen, ask that node for updated state + displayedNodeNum = n->num; + service.sendNetworkPing(displayedNodeNum, true); } NodeInfo *node = nodeDB.getNodeByIndex(nodeIndex); @@ -629,6 +637,8 @@ void Screen::handleOnPress() void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { + displayedNodeNum = 0; // Not currently showing a node pane + display->setFont(ArialMT_Plain_10); // The coordinates define the left starting point of the text From 3d919b21f60f96d7a5bc8c2d798a5ba18c2da540 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 21 May 2020 17:51:35 -0700 Subject: [PATCH 031/131] 0.6.4 --- bin/version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/version.sh b/bin/version.sh index fc9ebf0f0..2887e511d 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=0.6.3 \ No newline at end of file +export VERSION=0.6.4 \ No newline at end of file From 2dadb4d7a25ddc24a696f3e24cc749eefd4e8f06 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 21 May 2020 20:31:22 -0700 Subject: [PATCH 032/131] make nrf52dk build again --- docs/software/nrf52-TODO.md | 25 ++++++++---------- platformio.ini | 2 +- proto | 2 +- src/mesh/PacketHistory.cpp | 1 + src/mesh/RadioLibInterface.cpp | 2 +- src/mesh/mesh.pb.c | 3 +++ src/mesh/mesh.pb.h | 47 ++++++++++++++++++++++++++-------- src/nrf52/PmuBQ25703A.cpp | 3 +++ src/nrf52/main-nrf52.cpp | 2 ++ 9 files changed, 60 insertions(+), 27 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 69afcb946..01fbefeb1 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -4,36 +4,31 @@ Minimum items needed to make sure hardware is good. +- test new bootloader on 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 +- Use UC1701 LCD driver on real hardware. Still need to create at startup and probe on SPI. Make sure SPI is atomic. - 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) +- DONE make a new boarddef with a variant.h file. Fix pins in that file. ## Secondary work items Needed to be fully functional at least at the same level of the ESP32 boards. At this point users would probably want them. -- stop polling for GPS characters, instead stay blocked on read in a thread -- increase preamble length? - will break other clients? so all devices must update -- enable BLE DFU somehow +- get full BLE api working +- make a file system implementation (preferably one that can see the files the bootloader also sees) - use https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.3.0/lib_fds_usage.html?cp=7_5_0_3_55_3 +- make power management/sleep work properly +- make a settimeofday implementation +- DONE increase preamble length? - will break other clients? so all devices must update +- DONE enable BLE DFU somehow - set appversion/hwversion - report appversion/hwversion in BLE - use new LCD driver from screen.cpp. Still need to hook it to a subclass of (poorly named) OLEDDisplay, and override display() to stream bytes out to the screen. -- get full BLE api working - we need to enable the external xtal for the sx1262 (on dio3) - figure out which regulator mode the sx1262 is operating in - turn on security for BLE, make pairing work -- make power management/sleep work properly -- make a settimeofday implementation -- make a file system implementation (preferably one that can see the files the bootloader also sees) - use https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.3.0/lib_fds_usage.html?cp=7_5_0_3_55_3 - make ble endpoints not require "start config", just have them start in config mode - measure power management and confirm battery life - use new PMU to provide battery voltage/% full to app (both bluetooth and screen) @@ -41,6 +36,8 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At ## Items to be 'feature complete' +- check datasheet about sx1262 temperature compensation +- stop polling for GPS characters, instead stay blocked on read in a thread - 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. diff --git a/platformio.ini b/platformio.ini index ad27e2944..b0fbd3752 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = tbeam ; Note: the github actions CI test build can't yet build NRF52 targets +default_envs = nrf52dk ; Note: the github actions CI test build can't yet build NRF52 targets [common] ; common is not currently used diff --git a/proto b/proto index bc3ecd97e..2cb162a30 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit bc3ecd97e381b724c1a28acce0d12c688de73ba3 +Subproject commit 2cb162a3036b8513c743f05bd4f06aacb0b0680d diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index 9a5704f66..25b3b9c46 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -1,5 +1,6 @@ #include "PacketHistory.h" #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) diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index e09c4f4a9..1418d4383 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -298,7 +298,7 @@ void RadioLibInterface::handleReceiveInterrupt() addReceiveMetadata(mp); mp->which_payload = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point - assert(payloadLen <= sizeof(mp->encrypted.bytes)); + assert(payloadLen <= (int32_t)sizeof(mp->encrypted.bytes)); memcpy(mp->encrypted.bytes, payload, payloadLen); mp->encrypted.size = payloadLen; diff --git a/src/mesh/mesh.pb.c b/src/mesh/mesh.pb.c index 0b2c5b8ce..321576556 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 d193d38ee..1c2ecff80 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -50,8 +50,15 @@ 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; + uint32_t my_node_num; bool has_gps; int32_t num_channels; char region[12]; @@ -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 @@ -424,7 +440,7 @@ X(a, STATIC, SINGULAR, FLOAT, snr, 7) #define NodeInfo_position_MSGTYPE Position #define MyNodeInfo_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, INT32, my_node_num, 1) \ +X(a, STATIC, SINGULAR, UINT32, my_node_num, 1) \ X(a, STATIC, SINGULAR, BOOL, has_gps, 2) \ X(a, STATIC, SINGULAR, INT32, num_channels, 3) \ X(a, STATIC, SINGULAR, STRING, region, 4) \ @@ -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 MyNodeInfo_size 80 +#define DeviceState_size 15016 #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" */ diff --git a/src/nrf52/PmuBQ25703A.cpp b/src/nrf52/PmuBQ25703A.cpp index 25dc8fb9c..c369ca2b1 100644 --- a/src/nrf52/PmuBQ25703A.cpp +++ b/src/nrf52/PmuBQ25703A.cpp @@ -1,3 +1,4 @@ +#ifdef ARDUINO_NRF52840_PPR #include "PmuBQ25703A.h" #include @@ -36,6 +37,8 @@ void PmuBQ25703A::init() delay(15); } +#endif + /* diff --git a/src/nrf52/main-nrf52.cpp b/src/nrf52/main-nrf52.cpp index 0fe2c8d96..a7bd02bcc 100644 --- a/src/nrf52/main-nrf52.cpp +++ b/src/nrf52/main-nrf52.cpp @@ -59,9 +59,11 @@ void setBluetoothEnable(bool on) } } +#ifdef ARDUINO_NRF52840_PPR #include "PmuBQ25703A.h" PmuBQ25703A pmu; +#endif void nrf52Setup() { From 9149912a2d773111ba581b314967ce24612f6c6c Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 21 May 2020 21:17:19 -0700 Subject: [PATCH 033/131] adafruit includes segger by default --- lib/segger_rtt/License.txt | 34 - lib/segger_rtt/README.txt | 20 - .../Syscalls/SEGGER_RTT_Syscalls_GCC.c | 121 - .../Syscalls/SEGGER_RTT_Syscalls_IAR.c | 115 - .../Syscalls/SEGGER_RTT_Syscalls_KEIL.c | 386 ---- .../Syscalls/SEGGER_RTT_Syscalls_SES.c | 247 -- .../examples/Main_RTT_InputEchoApp.c | 43 - lib/segger_rtt/examples/Main_RTT_MenuApp.c | 70 - lib/segger_rtt/examples/Main_RTT_PrintfTest.c | 118 - .../examples/Main_RTT_SpeedTestApp.c | 69 - lib/segger_rtt/include/SEGGER_RTT.h | 321 --- lib/segger_rtt/include/SEGGER_RTT_Conf.h | 384 ---- lib/segger_rtt/src/SEGGER_RTT.c | 2005 ----------------- lib/segger_rtt/src/SEGGER_RTT_ASM_ARMv7M.S | 235 -- lib/segger_rtt/src/SEGGER_RTT_printf.c | 500 ---- 15 files changed, 4668 deletions(-) delete mode 100644 lib/segger_rtt/License.txt delete mode 100644 lib/segger_rtt/README.txt delete mode 100644 lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_GCC.c delete mode 100644 lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_IAR.c delete mode 100644 lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_KEIL.c delete mode 100644 lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_SES.c delete mode 100644 lib/segger_rtt/examples/Main_RTT_InputEchoApp.c delete mode 100644 lib/segger_rtt/examples/Main_RTT_MenuApp.c delete mode 100644 lib/segger_rtt/examples/Main_RTT_PrintfTest.c delete mode 100644 lib/segger_rtt/examples/Main_RTT_SpeedTestApp.c delete mode 100644 lib/segger_rtt/include/SEGGER_RTT.h delete mode 100644 lib/segger_rtt/include/SEGGER_RTT_Conf.h delete mode 100644 lib/segger_rtt/src/SEGGER_RTT.c delete mode 100644 lib/segger_rtt/src/SEGGER_RTT_ASM_ARMv7M.S delete mode 100644 lib/segger_rtt/src/SEGGER_RTT_printf.c diff --git a/lib/segger_rtt/License.txt b/lib/segger_rtt/License.txt deleted file mode 100644 index bc83e48a1..000000000 --- a/lib/segger_rtt/License.txt +++ /dev/null @@ -1,34 +0,0 @@ -Important - Read carefully: - -SEGGER RTT - Real Time Transfer for embedded targets - -All rights reserved. - -SEGGER strongly recommends to not make any changes -to or modify the source code of this software in order to stay -compatible with the RTT protocol and J-Link. - -Redistribution and use in source and binary forms, with or -without modification, are permitted provided that the following -condition is met: - -o Redistributions of source code must retain the above copyright - notice, this condition and the following disclaimer. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND -CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT -OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. - - -(c) 2014 - 2016 SEGGER Microcontroller GmbH -www.segger.com diff --git a/lib/segger_rtt/README.txt b/lib/segger_rtt/README.txt deleted file mode 100644 index 4dd23eba2..000000000 --- a/lib/segger_rtt/README.txt +++ /dev/null @@ -1,20 +0,0 @@ -README.txt for the SEGGER RTT Implementation Pack. - -Included files: -=============== -Root Directory - - Examples - - Main_RTT_InputEchoApp.c - Sample application which echoes input on Channel 0. - - Main_RTT_MenuApp.c - Sample application to demonstrate RTT bi-directional functionality. - - Main_RTT_PrintfTest.c - Sample application to test RTT small printf implementation. - - Main_RTT_SpeedTestApp.c - Sample application for measuring RTT performance. embOS needed. - - RTT - - SEGGER_RTT.c - The RTT implementation. - - SEGGER_RTT.h - Header for RTT implementation. - - SEGGER_RTT_Conf.h - Pre-processor configuration for the RTT implementation. - - SEGGER_RTT_Printf.c - Simple implementation of printf to write formatted strings via RTT. - - Syscalls - - RTT_Syscalls_GCC.c - Low-level syscalls to retarget printf() to RTT with GCC / Newlib. - - RTT_Syscalls_IAR.c - Low-level syscalls to retarget printf() to RTT with IAR compiler. - - RTT_Syscalls_KEIL.c - Low-level syscalls to retarget printf() to RTT with KEIL/uVision compiler. - - RTT_Syscalls_SES.c - Low-level syscalls to retarget printf() to RTT with SEGGER Embedded Studio. diff --git a/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_GCC.c b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_GCC.c deleted file mode 100644 index 32dab72bf..000000000 --- a/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_GCC.c +++ /dev/null @@ -1,121 +0,0 @@ -/********************************************************************* -* SEGGER Microcontroller GmbH * -* The Embedded Experts * -********************************************************************** -* * -* (c) 1995 - 2019 SEGGER Microcontroller GmbH * -* * -* www.segger.com Support: support@segger.com * -* * -********************************************************************** -* * -* SEGGER RTT * Real Time Transfer for embedded targets * -* * -********************************************************************** -* * -* All rights reserved. * -* * -* SEGGER strongly recommends to not make any changes * -* to or modify the source code of this software in order to stay * -* compatible with the RTT protocol and J-Link. * -* * -* Redistribution and use in source and binary forms, with or * -* without modification, are permitted provided that the following * -* condition is met: * -* * -* o Redistributions of source code must retain the above copyright * -* notice, this condition and the following disclaimer. * -* * -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * -* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * -* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * -* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * -* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * -* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * -* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * -* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * -* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * -* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * -* DAMAGE. * -* * -********************************************************************** ----------------------------END-OF-HEADER------------------------------ -File : SEGGER_RTT_Syscalls_GCC.c -Purpose : Low-level functions for using printf() via RTT in GCC. - To use RTT for printf output, include this file in your - application. -Revision: $Rev: 17697 $ ----------------------------------------------------------------------- -*/ -#if (defined __GNUC__) && !(defined __SES_ARM) && !(defined __CROSSWORKS_ARM) - -#include "SEGGER_RTT.h" -#include // required for _write_r - -/********************************************************************* - * - * Types - * - ********************************************************************** - */ -// -// If necessary define the _reent struct -// to match the one passed by the used standard library. -// -struct _reent; - -/********************************************************************* - * - * Function prototypes - * - ********************************************************************** - */ -int _write(int file, char *ptr, int len); -// _ssize_t _write_r(struct _reent *r, int file, const void *ptr, int len); - -/********************************************************************* - * - * Global functions - * - ********************************************************************** - */ - -/********************************************************************* - * - * _write() - * - * Function description - * Low-level write function. - * libc subroutines will use this system routine for output to all files, - * including stdout. - * Write data via RTT. - */ -int _write(int file, char *ptr, int len) -{ - (void)file; /* Not used, avoid warning */ - SEGGER_RTT_Write(0, ptr, len); - return len; -} - -/********************************************************************* - * - * _write_r() - * - * Function description - * Low-level reentrant write function. - * libc subroutines will use this system routine for output to all files, - * including stdout. - * Write data via RTT. - */ -_ssize_t _write_r(struct _reent *r, int file, const void *ptr, int len) -{ - (void)file; /* Not used, avoid warning */ - (void)r; /* Not used, avoid warning */ - SEGGER_RTT_Write(0, ptr, len); - return len; -} - -#endif -/****** End Of File *************************************************/ diff --git a/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_IAR.c b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_IAR.c deleted file mode 100644 index 11a968941..000000000 --- a/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_IAR.c +++ /dev/null @@ -1,115 +0,0 @@ -/********************************************************************* -* SEGGER Microcontroller GmbH * -* The Embedded Experts * -********************************************************************** -* * -* (c) 1995 - 2019 SEGGER Microcontroller GmbH * -* * -* www.segger.com Support: support@segger.com * -* * -********************************************************************** -* * -* SEGGER RTT * Real Time Transfer for embedded targets * -* * -********************************************************************** -* * -* All rights reserved. * -* * -* SEGGER strongly recommends to not make any changes * -* to or modify the source code of this software in order to stay * -* compatible with the RTT protocol and J-Link. * -* * -* Redistribution and use in source and binary forms, with or * -* without modification, are permitted provided that the following * -* condition is met: * -* * -* o Redistributions of source code must retain the above copyright * -* notice, this condition and the following disclaimer. * -* * -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * -* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * -* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * -* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * -* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * -* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * -* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * -* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * -* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * -* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * -* DAMAGE. * -* * -********************************************************************** ----------------------------END-OF-HEADER------------------------------ -File : SEGGER_RTT_Syscalls_IAR.c -Purpose : Low-level functions for using printf() via RTT in IAR. - To use RTT for printf output, include this file in your - application and set the Library Configuration to Normal. -Revision: $Rev: 17697 $ ----------------------------------------------------------------------- -*/ -#ifdef __IAR_SYSTEMS_ICC__ - -// -// Since IAR EWARM V8 and EWRX V4, yfuns.h is considered as deprecated and LowLevelIOInterface.h -// shall be used instead. To not break any compatibility with older compiler versions, we have a -// version check in here. -// -#if ((defined __ICCARM__) && (__VER__ >= 8000000)) || ((defined __ICCRX__) && (__VER__ >= 400)) - #include -#else - #include -#endif - -#include "SEGGER_RTT.h" -#pragma module_name = "?__write" - -/********************************************************************* -* -* Function prototypes -* -********************************************************************** -*/ -size_t __write(int handle, const unsigned char * buffer, size_t size); - -/********************************************************************* -* -* Global functions -* -********************************************************************** -*/ -/********************************************************************* -* -* __write() -* -* Function description -* Low-level write function. -* Standard library subroutines will use this system routine -* for output to all files, including stdout. -* Write data via RTT. -*/ -size_t __write(int handle, const unsigned char * buffer, size_t size) { - (void) handle; /* Not used, avoid warning */ - SEGGER_RTT_Write(0, (const char*)buffer, size); - return size; -} - -/********************************************************************* -* -* __write_buffered() -* -* Function description -* Low-level write function. -* Standard library subroutines will use this system routine -* for output to all files, including stdout. -* Write data via RTT. -*/ -size_t __write_buffered(int handle, const unsigned char * buffer, size_t size) { - (void) handle; /* Not used, avoid warning */ - SEGGER_RTT_Write(0, (const char*)buffer, size); - return size; -} - -#endif -/****** End Of File *************************************************/ diff --git a/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_KEIL.c b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_KEIL.c deleted file mode 100644 index f84bc1a19..000000000 --- a/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_KEIL.c +++ /dev/null @@ -1,386 +0,0 @@ -/********************************************************************* -* SEGGER Microcontroller GmbH * -* The Embedded Experts * -********************************************************************** -* * -* (c) 1995 - 2019 SEGGER Microcontroller GmbH * -* * -* www.segger.com Support: support@segger.com * -* * -********************************************************************** -* * -* SEGGER RTT * Real Time Transfer for embedded targets * -* * -********************************************************************** -* * -* All rights reserved. * -* * -* SEGGER strongly recommends to not make any changes * -* to or modify the source code of this software in order to stay * -* compatible with the RTT protocol and J-Link. * -* * -* Redistribution and use in source and binary forms, with or * -* without modification, are permitted provided that the following * -* condition is met: * -* * -* o Redistributions of source code must retain the above copyright * -* notice, this condition and the following disclaimer. * -* * -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * -* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * -* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * -* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * -* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * -* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * -* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * -* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * -* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * -* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * -* DAMAGE. * -* * -********************************************************************** ----------------------------END-OF-HEADER------------------------------ -File : RTT_Syscalls_KEIL.c -Purpose : Retargeting module for KEIL MDK-CM3. - Low-level functions for using printf() via RTT -Revision: $Rev: 17697 $ ----------------------------------------------------------------------- -*/ -#ifdef __CC_ARM - -#include -#include -#include -#include -#include - -#include "SEGGER_RTT.h" -/********************************************************************* -* -* #pragmas -* -********************************************************************** -*/ -#pragma import(__use_no_semihosting) - -#ifdef _MICROLIB - #pragma import(__use_full_stdio) -#endif - -/********************************************************************* -* -* Defines non-configurable -* -********************************************************************** -*/ - -/* Standard IO device handles - arbitrary, but any real file system handles must be - less than 0x8000. */ -#define STDIN 0x8001 // Standard Input Stream -#define STDOUT 0x8002 // Standard Output Stream -#define STDERR 0x8003 // Standard Error Stream - -/********************************************************************* -* -* Public const -* -********************************************************************** -*/ -#if __ARMCC_VERSION < 5000000 -//const char __stdin_name[] = "STDIN"; -const char __stdout_name[] = "STDOUT"; -const char __stderr_name[] = "STDERR"; -#endif - -/********************************************************************* -* -* Public code -* -********************************************************************** -*/ - -/********************************************************************* -* -* _ttywrch -* -* Function description: -* Outputs a character to the console -* -* Parameters: -* c - character to output -* -*/ -void _ttywrch(int c) { - fputc(c, stdout); // stdout - fflush(stdout); -} - -/********************************************************************* -* -* _sys_open -* -* Function description: -* Opens the device/file in order to do read/write operations -* -* Parameters: -* sName - sName of the device/file to open -* OpenMode - This parameter is currently ignored -* -* Return value: -* != 0 - Handle to the object to open, otherwise -* == 0 -"device" is not handled by this module -* -*/ -FILEHANDLE _sys_open(const char * sName, int OpenMode) { - (void)OpenMode; - // Register standard Input Output devices. - if (strcmp(sName, __stdout_name) == 0) { - return (STDOUT); - } else if (strcmp(sName, __stderr_name) == 0) { - return (STDERR); - } else - return (0); // Not implemented -} - -/********************************************************************* -* -* _sys_close -* -* Function description: -* Closes the handle to the open device/file -* -* Parameters: -* hFile - Handle to a file opened via _sys_open -* -* Return value: -* 0 - device/file closed -* -*/ -int _sys_close(FILEHANDLE hFile) { - (void)hFile; - return 0; // Not implemented -} - -/********************************************************************* -* -* _sys_write -* -* Function description: -* Writes the data to an open handle. -* Currently this function only outputs data to the console -* -* Parameters: -* hFile - Handle to a file opened via _sys_open -* pBuffer - Pointer to the data that shall be written -* NumBytes - Number of bytes to write -* Mode - The Mode that shall be used -* -* Return value: -* Number of bytes *not* written to the file/device -* -*/ -int _sys_write(FILEHANDLE hFile, const unsigned char * pBuffer, unsigned NumBytes, int Mode) { - int r = 0; - - (void)Mode; - if (hFile == STDOUT) { - SEGGER_RTT_Write(0, (const char*)pBuffer, NumBytes); - return 0; - } - return r; -} - -/********************************************************************* -* -* _sys_read -* -* Function description: -* Reads data from an open handle. -* Currently this modules does nothing. -* -* Parameters: -* hFile - Handle to a file opened via _sys_open -* pBuffer - Pointer to buffer to store the read data -* NumBytes - Number of bytes to read -* Mode - The Mode that shall be used -* -* Return value: -* Number of bytes read from the file/device -* -*/ -int _sys_read(FILEHANDLE hFile, unsigned char * pBuffer, unsigned NumBytes, int Mode) { - (void)hFile; - (void)pBuffer; - (void)NumBytes; - (void)Mode; - return (0); // Not implemented -} - -/********************************************************************* -* -* _sys_istty -* -* Function description: -* This function shall return whether the opened file -* is a console device or not. -* -* Parameters: -* hFile - Handle to a file opened via _sys_open -* -* Return value: -* 1 - Device is a console -* 0 - Device is not a console -* -*/ -int _sys_istty(FILEHANDLE hFile) { - if (hFile > 0x8000) { - return (1); - } - return (0); // Not implemented -} - -/********************************************************************* -* -* _sys_seek -* -* Function description: -* Seeks via the file to a specific position -* -* Parameters: -* hFile - Handle to a file opened via _sys_open -* Pos - -* -* Return value: -* int - -* -*/ -int _sys_seek(FILEHANDLE hFile, long Pos) { - (void)hFile; - (void)Pos; - return (0); // Not implemented -} - -/********************************************************************* -* -* _sys_ensure -* -* Function description: -* -* -* Parameters: -* hFile - Handle to a file opened via _sys_open -* -* Return value: -* int - -* -*/ -int _sys_ensure(FILEHANDLE hFile) { - (void)hFile; - return (-1); // Not implemented -} - -/********************************************************************* -* -* _sys_flen -* -* Function description: -* Returns the length of the opened file handle -* -* Parameters: -* hFile - Handle to a file opened via _sys_open -* -* Return value: -* Length of the file -* -*/ -long _sys_flen(FILEHANDLE hFile) { - (void)hFile; - return (0); // Not implemented -} - -/********************************************************************* -* -* _sys_tmpnam -* -* Function description: -* This function converts the file number fileno for a temporary -* file to a unique filename, for example, tmp0001. -* -* Parameters: -* pBuffer - Pointer to a buffer to store the name -* FileNum - file number to convert -* MaxLen - Size of the buffer -* -* Return value: -* 1 - Error -* 0 - Success -* -*/ -int _sys_tmpnam(char * pBuffer, int FileNum, unsigned MaxLen) { - (void)pBuffer; - (void)FileNum; - (void)MaxLen; - return (1); // Not implemented -} - -/********************************************************************* -* -* _sys_command_string -* -* Function description: -* This function shall execute a system command. -* -* Parameters: -* cmd - Pointer to the command string -* len - Length of the string -* -* Return value: -* == NULL - Command was not successfully executed -* == sCmd - Command was passed successfully -* -*/ -char * _sys_command_string(char * cmd, int len) { - (void)len; - return cmd; // Not implemented -} - -/********************************************************************* -* -* _sys_exit -* -* Function description: -* This function is called when the application returns from main -* -* Parameters: -* ReturnCode - Return code from the main function -* -* -*/ -void _sys_exit(int ReturnCode) { - (void)ReturnCode; - while (1); // Not implemented -} - -#if __ARMCC_VERSION >= 5000000 -/********************************************************************* -* -* stdout_putchar -* -* Function description: -* Put a character to the stdout -* -* Parameters: -* ch - Character to output -* -* -*/ -int stdout_putchar(int ch) { - (void)ch; - return ch; // Not implemented -} -#endif - -#endif -/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_SES.c b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_SES.c deleted file mode 100644 index 3744f01fd..000000000 --- a/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_SES.c +++ /dev/null @@ -1,247 +0,0 @@ -/********************************************************************* -* SEGGER Microcontroller GmbH * -* The Embedded Experts * -********************************************************************** -* * -* (c) 1995 - 2019 SEGGER Microcontroller GmbH * -* * -* www.segger.com Support: support@segger.com * -* * -********************************************************************** -* * -* SEGGER RTT * Real Time Transfer for embedded targets * -* * -********************************************************************** -* * -* All rights reserved. * -* * -* SEGGER strongly recommends to not make any changes * -* to or modify the source code of this software in order to stay * -* compatible with the RTT protocol and J-Link. * -* * -* Redistribution and use in source and binary forms, with or * -* without modification, are permitted provided that the following * -* condition is met: * -* * -* o Redistributions of source code must retain the above copyright * -* notice, this condition and the following disclaimer. * -* * -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * -* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * -* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * -* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * -* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * -* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * -* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * -* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * -* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * -* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * -* DAMAGE. * -* * -********************************************************************** ----------------------------END-OF-HEADER------------------------------ -File : SEGGER_RTT_Syscalls_SES.c -Purpose : Reimplementation of printf, puts and __getchar using RTT - in SEGGER Embedded Studio. - To use RTT for printf output, include this file in your - application. -Revision: $Rev: 18539 $ ----------------------------------------------------------------------- -*/ -#if (defined __SES_ARM) || (defined __SES_RISCV) || (defined __CROSSWORKS_ARM) - -#include "SEGGER_RTT.h" -#include -#include -#include "limits.h" -#include "__libc.h" -#include "__vfprintf.h" - -/********************************************************************* -* -* Defines, configurable -* -********************************************************************** -*/ -// -// Select string formatting implementation. -// -// RTT printf formatting -// - Configurable stack usage. (SEGGER_RTT_PRINTF_BUFFER_SIZE in SEGGER_RTT_Conf.h) -// - No maximum string length. -// - Limited conversion specifiers and flags. (See SEGGER_RTT_printf.c) -// Standard library printf formatting -// - Configurable formatting capabilities. -// - Full conversion specifier and flag support. -// - Maximum string length has to be known or (slightly) slower character-wise output. -// -// #define PRINTF_USE_SEGGER_RTT_FORMATTING 0 // Use standard library formatting -// #define PRINTF_USE_SEGGER_RTT_FORMATTING 1 // Use RTT formatting -// -#ifndef PRINTF_USE_SEGGER_RTT_FORMATTING - #define PRINTF_USE_SEGGER_RTT_FORMATTING 0 -#endif -// -// If using standard library formatting, -// select maximum output string buffer size or character-wise output. -// -// #define PRINTF_BUFFER_SIZE 0 // Use character-wise output -// #define PRINTF_BUFFER_SIZE 128 // Default maximum string length -// -#ifndef PRINTF_BUFFER_SIZE - #define PRINTF_BUFFER_SIZE 128 -#endif - -#if PRINTF_USE_SEGGER_RTT_FORMATTING // Use SEGGER RTT formatting implementation -/********************************************************************* -* -* Function prototypes -* -********************************************************************** -*/ -int SEGGER_RTT_vprintf(unsigned BufferIndex, const char * sFormat, va_list * pParamList); - -/********************************************************************* -* -* Global functions, printf -* -********************************************************************** -*/ -/********************************************************************* -* -* printf() -* -* Function description -* print a formatted string using RTT and SEGGER RTT formatting. -*/ -int printf(const char *fmt,...) { - int n; - va_list args; - - va_start (args, fmt); - n = SEGGER_RTT_vprintf(0, fmt, &args); - va_end(args); - return n; -} - -#elif PRINTF_BUFFER_SIZE == 0 // Use standard library formatting with character-wise output - -/********************************************************************* -* -* Static functions -* -********************************************************************** -*/ -static int _putchar(int x, __printf_tag_ptr ctx) { - (void)ctx; - SEGGER_RTT_Write(0, (char *)&x, 1); - return x; -} - -/********************************************************************* -* -* Global functions, printf -* -********************************************************************** -*/ -/********************************************************************* -* -* printf() -* -* Function description -* print a formatted string character-wise, using RTT and standard -* library formatting. -*/ -int printf(const char *fmt, ...) { - int n; - va_list args; - __printf_t iod; - - va_start(args, fmt); - iod.string = 0; - iod.maxchars = INT_MAX; - iod.output_fn = _putchar; - SEGGER_RTT_LOCK(); - n = __vfprintf(&iod, fmt, args); - SEGGER_RTT_UNLOCK(); - va_end(args); - return n; -} - -#else // Use standard library formatting with static buffer - -/********************************************************************* -* -* Global functions, printf -* -********************************************************************** -*/ -/********************************************************************* -* -* printf() -* -* Function description -* print a formatted string using RTT and standard library formatting. -*/ -int printf(const char *fmt,...) { - int n; - char aBuffer[PRINTF_BUFFER_SIZE]; - va_list args; - - va_start (args, fmt); - n = vsnprintf(aBuffer, sizeof(aBuffer), fmt, args); - if (n > (int)sizeof(aBuffer)) { - SEGGER_RTT_Write(0, aBuffer, sizeof(aBuffer)); - } else if (n > 0) { - SEGGER_RTT_Write(0, aBuffer, n); - } - va_end(args); - return n; -} -#endif - -/********************************************************************* -* -* Global functions -* -********************************************************************** -*/ -/********************************************************************* -* -* puts() -* -* Function description -* print a string using RTT. -*/ -int puts(const char *s) { - return SEGGER_RTT_WriteString(0, s); -} - -/********************************************************************* -* -* __putchar() -* -* Function description -* Write one character via RTT. -*/ -int __putchar(int x, __printf_tag_ptr ctx) { - (void)ctx; - SEGGER_RTT_Write(0, (char *)&x, 1); - return x; -} - -/********************************************************************* -* -* __getchar() -* -* Function description -* Wait for and get a character via RTT. -*/ -int __getchar() { - return SEGGER_RTT_WaitKey(); -} - -#endif -/****** End Of File *************************************************/ diff --git a/lib/segger_rtt/examples/Main_RTT_InputEchoApp.c b/lib/segger_rtt/examples/Main_RTT_InputEchoApp.c deleted file mode 100644 index 156169283..000000000 --- a/lib/segger_rtt/examples/Main_RTT_InputEchoApp.c +++ /dev/null @@ -1,43 +0,0 @@ -/********************************************************************* -* SEGGER Microcontroller GmbH * -* Solutions for real time microcontroller applications * -********************************************************************** -* * -* (c) 1995 - 2018 SEGGER Microcontroller GmbH * -* * -* www.segger.com Support: support@segger.com * -* * -********************************************************************** - ---------- END-OF-HEADER -------------------------------------------- -File : Main_RTT_MenuApp.c -Purpose : Sample application to demonstrate RTT bi-directional functionality -*/ - -#define MAIN_C - -#include - -#include "SEGGER_RTT.h" - -volatile int _Cnt; -volatile int _Delay; - -static char r; - -/********************************************************************* -* -* main -*/ -void main(void) { - - SEGGER_RTT_WriteString(0, "SEGGER Real-Time-Terminal Sample\r\n"); - SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP); - do { - r = SEGGER_RTT_WaitKey(); - SEGGER_RTT_Write(0, &r, 1); - r++; - } while (1); -} - -/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/examples/Main_RTT_MenuApp.c b/lib/segger_rtt/examples/Main_RTT_MenuApp.c deleted file mode 100644 index c6a277ac1..000000000 --- a/lib/segger_rtt/examples/Main_RTT_MenuApp.c +++ /dev/null @@ -1,70 +0,0 @@ -/********************************************************************* -* SEGGER Microcontroller GmbH * -* Solutions for real time microcontroller applications * -********************************************************************** -* * -* (c) 1995 - 2018 SEGGER Microcontroller GmbH * -* * -* www.segger.com Support: support@segger.com * -* * -********************************************************************** ---------- END-OF-HEADER -------------------------------------------- -File : Main_RTT_MenuApp.c -Purpose : Sample application to demonstrate RTT bi-directional functionality -*/ - -#define MAIN_C - -#include - -#include "SEGGER_RTT.h" - -volatile int _Cnt; -volatile int _Delay; - -/********************************************************************* -* -* main -*/ -void main(void) { - int r; - int CancelOp; - - do { - _Cnt = 0; - - SEGGER_RTT_WriteString(0, "SEGGER Real-Time-Terminal Sample\r\n"); - SEGGER_RTT_WriteString(0, "Press <1> to continue in blocking mode (Application waits if necessary, no data lost)\r\n"); - SEGGER_RTT_WriteString(0, "Press <2> to continue in non-blocking mode (Application does not wait, data lost if fifo full)\r\n"); - do { - r = SEGGER_RTT_WaitKey(); - } while ((r != '1') && (r != '2')); - if (r == '1') { - SEGGER_RTT_WriteString(0, "\r\nSelected <1>. Configuring RTT and starting...\r\n"); - SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL); - } else { - SEGGER_RTT_WriteString(0, "\r\nSelected <2>. Configuring RTT and starting...\r\n"); - SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP); - } - CancelOp = 0; - do { - //for (_Delay = 0; _Delay < 10000; _Delay++); - SEGGER_RTT_printf(0, "Count: %d. Press to get back to menu.\r\n", _Cnt++); - r = SEGGER_RTT_HasKey(); - if (r) { - CancelOp = (SEGGER_RTT_GetKey() == ' ') ? 1 : 0; - } - // - // Check if user selected to cancel the current operation - // - if (CancelOp) { - SEGGER_RTT_WriteString(0, "Operation cancelled, going back to menu...\r\n"); - break; - } - } while (1); - SEGGER_RTT_GetKey(); - SEGGER_RTT_WriteString(0, "\r\n"); - } while (1); -} - -/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/examples/Main_RTT_PrintfTest.c b/lib/segger_rtt/examples/Main_RTT_PrintfTest.c deleted file mode 100644 index de81b15db..000000000 --- a/lib/segger_rtt/examples/Main_RTT_PrintfTest.c +++ /dev/null @@ -1,118 +0,0 @@ -/********************************************************************* -* SEGGER Microcontroller GmbH * -* Solutions for real time microcontroller applications * -********************************************************************** -* * -* (c) 1995 - 2018 SEGGER Microcontroller GmbH * -* * -* www.segger.com Support: support@segger.com * -* * -********************************************************************** - ---------- END-OF-HEADER -------------------------------------------- -File : Main_RTT_MenuApp.c -Purpose : Sample application to demonstrate RTT bi-directional functionality -*/ - -#define MAIN_C - -#include - -#include "SEGGER_RTT.h" - -volatile int _Cnt; - -/********************************************************************* -* -* main -*/ -void main(void) { - - SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL); - - SEGGER_RTT_WriteString(0, "SEGGER Real-Time-Terminal Sample\r\n\r\n"); - SEGGER_RTT_WriteString(0, "###### Testing SEGGER_printf() ######\r\n"); - - SEGGER_RTT_printf(0, "printf Test: %%c, 'S' : %c.\r\n", 'S'); - SEGGER_RTT_printf(0, "printf Test: %%5c, 'E' : %5c.\r\n", 'E'); - SEGGER_RTT_printf(0, "printf Test: %%-5c, 'G' : %-5c.\r\n", 'G'); - SEGGER_RTT_printf(0, "printf Test: %%5.3c, 'G' : %-5c.\r\n", 'G'); - SEGGER_RTT_printf(0, "printf Test: %%.3c, 'E' : %-5c.\r\n", 'E'); - SEGGER_RTT_printf(0, "printf Test: %%c, 'R' : %c.\r\n", 'R'); - - SEGGER_RTT_printf(0, "printf Test: %%s, \"RTT\" : %s.\r\n", "RTT"); - SEGGER_RTT_printf(0, "printf Test: %%s, \"RTT\\r\\nRocks.\" : %s.\r\n", "RTT\r\nRocks."); - - SEGGER_RTT_printf(0, "printf Test: %%u, 12345 : %u.\r\n", 12345); - SEGGER_RTT_printf(0, "printf Test: %%+u, 12345 : %+u.\r\n", 12345); - SEGGER_RTT_printf(0, "printf Test: %%.3u, 12345 : %.3u.\r\n", 12345); - SEGGER_RTT_printf(0, "printf Test: %%.6u, 12345 : %.6u.\r\n", 12345); - SEGGER_RTT_printf(0, "printf Test: %%6.3u, 12345 : %6.3u.\r\n", 12345); - SEGGER_RTT_printf(0, "printf Test: %%8.6u, 12345 : %8.6u.\r\n", 12345); - SEGGER_RTT_printf(0, "printf Test: %%08u, 12345 : %08u.\r\n", 12345); - SEGGER_RTT_printf(0, "printf Test: %%08.6u, 12345 : %08.6u.\r\n", 12345); - SEGGER_RTT_printf(0, "printf Test: %%0u, 12345 : %0u.\r\n", 12345); - SEGGER_RTT_printf(0, "printf Test: %%-.6u, 12345 : %-.6u.\r\n", 12345); - SEGGER_RTT_printf(0, "printf Test: %%-6.3u, 12345 : %-6.3u.\r\n", 12345); - SEGGER_RTT_printf(0, "printf Test: %%-8.6u, 12345 : %-8.6u.\r\n", 12345); - SEGGER_RTT_printf(0, "printf Test: %%-08u, 12345 : %-08u.\r\n", 12345); - SEGGER_RTT_printf(0, "printf Test: %%-08.6u, 12345 : %-08.6u.\r\n", 12345); - SEGGER_RTT_printf(0, "printf Test: %%-0u, 12345 : %-0u.\r\n", 12345); - - SEGGER_RTT_printf(0, "printf Test: %%u, -12345 : %u.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%+u, -12345 : %+u.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%.3u, -12345 : %.3u.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%.6u, -12345 : %.6u.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%6.3u, -12345 : %6.3u.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%8.6u, -12345 : %8.6u.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%08u, -12345 : %08u.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%08.6u, -12345 : %08.6u.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%0u, -12345 : %0u.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%-.6u, -12345 : %-.6u.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%-6.3u, -12345 : %-6.3u.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%-8.6u, -12345 : %-8.6u.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%-08u, -12345 : %-08u.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%-08.6u, -12345 : %-08.6u.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%-0u, -12345 : %-0u.\r\n", -12345); - - SEGGER_RTT_printf(0, "printf Test: %%d, -12345 : %d.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%+d, -12345 : %+d.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%.3d, -12345 : %.3d.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%.6d, -12345 : %.6d.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%6.3d, -12345 : %6.3d.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%8.6d, -12345 : %8.6d.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%08d, -12345 : %08d.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%08.6d, -12345 : %08.6d.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%0d, -12345 : %0d.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%-.6d, -12345 : %-.6d.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%-6.3d, -12345 : %-6.3d.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%-8.6d, -12345 : %-8.6d.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%-08d, -12345 : %-08d.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%-08.6d, -12345 : %-08.6d.\r\n", -12345); - SEGGER_RTT_printf(0, "printf Test: %%-0d, -12345 : %-0d.\r\n", -12345); - - SEGGER_RTT_printf(0, "printf Test: %%x, 0x1234ABC : %x.\r\n", 0x1234ABC); - SEGGER_RTT_printf(0, "printf Test: %%+x, 0x1234ABC : %+x.\r\n", 0x1234ABC); - SEGGER_RTT_printf(0, "printf Test: %%.3x, 0x1234ABC : %.3x.\r\n", 0x1234ABC); - SEGGER_RTT_printf(0, "printf Test: %%.6x, 0x1234ABC : %.6x.\r\n", 0x1234ABC); - SEGGER_RTT_printf(0, "printf Test: %%6.3x, 0x1234ABC : %6.3x.\r\n", 0x1234ABC); - SEGGER_RTT_printf(0, "printf Test: %%8.6x, 0x1234ABC : %8.6x.\r\n", 0x1234ABC); - SEGGER_RTT_printf(0, "printf Test: %%08x, 0x1234ABC : %08x.\r\n", 0x1234ABC); - SEGGER_RTT_printf(0, "printf Test: %%08.6x, 0x1234ABC : %08.6x.\r\n", 0x1234ABC); - SEGGER_RTT_printf(0, "printf Test: %%0x, 0x1234ABC : %0x.\r\n", 0x1234ABC); - SEGGER_RTT_printf(0, "printf Test: %%-.6x, 0x1234ABC : %-.6x.\r\n", 0x1234ABC); - SEGGER_RTT_printf(0, "printf Test: %%-6.3x, 0x1234ABC : %-6.3x.\r\n", 0x1234ABC); - SEGGER_RTT_printf(0, "printf Test: %%-8.6x, 0x1234ABC : %-8.6x.\r\n", 0x1234ABC); - SEGGER_RTT_printf(0, "printf Test: %%-08x, 0x1234ABC : %-08x.\r\n", 0x1234ABC); - SEGGER_RTT_printf(0, "printf Test: %%-08.6x, 0x1234ABC : %-08.6x.\r\n", 0x1234ABC); - SEGGER_RTT_printf(0, "printf Test: %%-0x, 0x1234ABC : %-0x.\r\n", 0x1234ABC); - - SEGGER_RTT_printf(0, "printf Test: %%p, &_Cnt : %p.\r\n", &_Cnt); - - SEGGER_RTT_WriteString(0, "###### SEGGER_printf() Tests done. ######\r\n"); - do { - _Cnt++; - } while (1); -} - -/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/examples/Main_RTT_SpeedTestApp.c b/lib/segger_rtt/examples/Main_RTT_SpeedTestApp.c deleted file mode 100644 index 304b16f4f..000000000 --- a/lib/segger_rtt/examples/Main_RTT_SpeedTestApp.c +++ /dev/null @@ -1,69 +0,0 @@ -/********************************************************************* -* SEGGER Microcontroller GmbH * -* Solutions for real time microcontroller applications * -********************************************************************** -* * -* (c) 1995 - 2018 SEGGER Microcontroller GmbH * -* * -* www.segger.com Support: support@segger.com * -* * -********************************************************************** - ---------- END-OF-HEADER -------------------------------------------- -File : Main_RTT_SpeedTestApp.c -Purpose : Sample program for measuring RTT performance. -*/ - -#include "RTOS.h" -#include "BSP.h" - -#include "SEGGER_RTT.h" -#include - -OS_STACKPTR int StackHP[128], StackLP[128]; /* Task stacks */ -OS_TASK TCBHP, TCBLP; /* Task-control-blocks */ - -static void HPTask(void) { - while (1) { - // - // Measure time needed for RTT output - // Perform dummy write with 0 characters, so we know the overhead of toggling LEDs and RTT in general - // -// Set BP here. Then start sampling on scope - BSP_ClrLED(0); - SEGGER_RTT_Write(0, 0, 0); - BSP_SetLED(0); - BSP_ClrLED(0); - SEGGER_RTT_Write(0, "01234567890123456789012345678901234567890123456789012345678901234567890123456789\r\n", 82); - BSP_SetLED(0); -// Set BP here. Then stop sampling on scope - OS_Delay(200); - } -} - -static void LPTask(void) { - while (1) { - BSP_ToggleLED(1); - OS_Delay (500); - } -} - -/********************************************************************* -* -* main -* -*********************************************************************/ - -int main(void) { - OS_IncDI(); /* Initially disable interrupts */ - OS_InitKern(); /* Initialize OS */ - OS_InitHW(); /* Initialize Hardware for OS */ - BSP_Init(); /* Initialize LED ports */ - BSP_SetLED(0); - /* You need to create at least one task before calling OS_Start() */ - OS_CREATETASK(&TCBHP, "HP Task", HPTask, 100, StackHP); - OS_CREATETASK(&TCBLP, "LP Task", LPTask, 50, StackLP); - OS_Start(); /* Start multitasking */ - return 0; -} - diff --git a/lib/segger_rtt/include/SEGGER_RTT.h b/lib/segger_rtt/include/SEGGER_RTT.h deleted file mode 100644 index 0945e347e..000000000 --- a/lib/segger_rtt/include/SEGGER_RTT.h +++ /dev/null @@ -1,321 +0,0 @@ -/********************************************************************* -* SEGGER Microcontroller GmbH * -* The Embedded Experts * -********************************************************************** -* * -* (c) 1995 - 2019 SEGGER Microcontroller GmbH * -* * -* www.segger.com Support: support@segger.com * -* * -********************************************************************** -* * -* SEGGER RTT * Real Time Transfer for embedded targets * -* * -********************************************************************** -* * -* All rights reserved. * -* * -* SEGGER strongly recommends to not make any changes * -* to or modify the source code of this software in order to stay * -* compatible with the RTT protocol and J-Link. * -* * -* Redistribution and use in source and binary forms, with or * -* without modification, are permitted provided that the following * -* condition is met: * -* * -* o Redistributions of source code must retain the above copyright * -* notice, this condition and the following disclaimer. * -* * -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * -* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * -* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * -* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * -* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * -* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * -* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * -* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * -* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * -* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * -* DAMAGE. * -* * -********************************************************************** ----------------------------END-OF-HEADER------------------------------ -File : SEGGER_RTT.h -Purpose : Implementation of SEGGER real-time transfer which allows - real-time communication on targets which support debugger - memory accesses while the CPU is running. -Revision: $Rev: 17697 $ ----------------------------------------------------------------------- -*/ - -#ifndef SEGGER_RTT_H -#define SEGGER_RTT_H - -#include "SEGGER_RTT_Conf.h" - - - -/********************************************************************* -* -* Defines, defaults -* -********************************************************************** -*/ -#ifndef RTT_USE_ASM - #if (defined __SES_ARM) // SEGGER Embedded Studio - #define _CC_HAS_RTT_ASM_SUPPORT 1 - #elif (defined __CROSSWORKS_ARM) // Rowley Crossworks - #define _CC_HAS_RTT_ASM_SUPPORT 1 - #elif (defined __GNUC__) // GCC - #define _CC_HAS_RTT_ASM_SUPPORT 1 - #elif (defined __clang__) // Clang compiler - #define _CC_HAS_RTT_ASM_SUPPORT 1 - #elif (defined __IASMARM__) // IAR assembler - #define _CC_HAS_RTT_ASM_SUPPORT 1 - #elif (defined __ICCARM__) // IAR compiler - #define _CC_HAS_RTT_ASM_SUPPORT 1 - #else - #define _CC_HAS_RTT_ASM_SUPPORT 0 - #endif - #if (defined __ARM_ARCH_7M__) // Cortex-M3/4 - #define _CORE_HAS_RTT_ASM_SUPPORT 1 - #elif (defined __ARM_ARCH_7EM__) // Cortex-M7 - #define _CORE_HAS_RTT_ASM_SUPPORT 1 - #elif (defined __ARM_ARCH_8M_MAIN__) // Cortex-M33 - #define _CORE_HAS_RTT_ASM_SUPPORT 1 - #elif (defined __ARM7M__) // IAR Cortex-M3/4 - #if (__CORE__ == __ARM7M__) - #define _CORE_HAS_RTT_ASM_SUPPORT 1 - #else - #define _CORE_HAS_RTT_ASM_SUPPORT 0 - #endif - #elif (defined __ARM7EM__) // IAR Cortex-M7 - #if (__CORE__ == __ARM7EM__) - #define _CORE_HAS_RTT_ASM_SUPPORT 1 - #else - #define _CORE_HAS_RTT_ASM_SUPPORT 0 - #endif - #else - #define _CORE_HAS_RTT_ASM_SUPPORT 0 - #endif - // - // If IDE and core support the ASM version, enable ASM version by default - // - #if (_CC_HAS_RTT_ASM_SUPPORT && _CORE_HAS_RTT_ASM_SUPPORT) - #define RTT_USE_ASM (1) - #else - #define RTT_USE_ASM (0) - #endif -#endif - -#ifndef SEGGER_RTT_ASM // defined when SEGGER_RTT.h is included from assembly file -#include -#include - -/********************************************************************* -* -* Defines, fixed -* -********************************************************************** -*/ - -/********************************************************************* -* -* Types -* -********************************************************************** -*/ - -// -// Description for a circular buffer (also called "ring buffer") -// which is used as up-buffer (T->H) -// -typedef struct { - const char* sName; // Optional name. Standard names so far are: "Terminal", "SysView", "J-Scope_t4i4" - char* pBuffer; // Pointer to start of buffer - unsigned SizeOfBuffer; // Buffer size in bytes. Note that one byte is lost, as this implementation does not fill up the buffer in order to avoid the problem of being unable to distinguish between full and empty. - unsigned WrOff; // Position of next item to be written by either target. - volatile unsigned RdOff; // Position of next item to be read by host. Must be volatile since it may be modified by host. - unsigned Flags; // Contains configuration flags -} SEGGER_RTT_BUFFER_UP; - -// -// Description for a circular buffer (also called "ring buffer") -// which is used as down-buffer (H->T) -// -typedef struct { - const char* sName; // Optional name. Standard names so far are: "Terminal", "SysView", "J-Scope_t4i4" - char* pBuffer; // Pointer to start of buffer - unsigned SizeOfBuffer; // Buffer size in bytes. Note that one byte is lost, as this implementation does not fill up the buffer in order to avoid the problem of being unable to distinguish between full and empty. - volatile unsigned WrOff; // Position of next item to be written by host. Must be volatile since it may be modified by host. - unsigned RdOff; // Position of next item to be read by target (down-buffer). - unsigned Flags; // Contains configuration flags -} SEGGER_RTT_BUFFER_DOWN; - -// -// RTT control block which describes the number of buffers available -// as well as the configuration for each buffer -// -// -typedef struct { - char acID[16]; // Initialized to "SEGGER RTT" - int MaxNumUpBuffers; // Initialized to SEGGER_RTT_MAX_NUM_UP_BUFFERS (type. 2) - int MaxNumDownBuffers; // Initialized to SEGGER_RTT_MAX_NUM_DOWN_BUFFERS (type. 2) - SEGGER_RTT_BUFFER_UP aUp[SEGGER_RTT_MAX_NUM_UP_BUFFERS]; // Up buffers, transferring information up from target via debug probe to host - SEGGER_RTT_BUFFER_DOWN aDown[SEGGER_RTT_MAX_NUM_DOWN_BUFFERS]; // Down buffers, transferring information down from host via debug probe to target -} SEGGER_RTT_CB; - -/********************************************************************* -* -* Global data -* -********************************************************************** -*/ -extern SEGGER_RTT_CB _SEGGER_RTT; - -/********************************************************************* -* -* RTT API functions -* -********************************************************************** -*/ -#ifdef __cplusplus - extern "C" { -#endif -int SEGGER_RTT_AllocDownBuffer (const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags); -int SEGGER_RTT_AllocUpBuffer (const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags); -int SEGGER_RTT_ConfigUpBuffer (unsigned BufferIndex, const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags); -int SEGGER_RTT_ConfigDownBuffer (unsigned BufferIndex, const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags); -int SEGGER_RTT_GetKey (void); -unsigned SEGGER_RTT_HasData (unsigned BufferIndex); -int SEGGER_RTT_HasKey (void); -unsigned SEGGER_RTT_HasDataUp (unsigned BufferIndex); -void SEGGER_RTT_Init (void); -unsigned SEGGER_RTT_Read (unsigned BufferIndex, void* pBuffer, unsigned BufferSize); -unsigned SEGGER_RTT_ReadNoLock (unsigned BufferIndex, void* pData, unsigned BufferSize); -int SEGGER_RTT_SetNameDownBuffer (unsigned BufferIndex, const char* sName); -int SEGGER_RTT_SetNameUpBuffer (unsigned BufferIndex, const char* sName); -int SEGGER_RTT_SetFlagsDownBuffer (unsigned BufferIndex, unsigned Flags); -int SEGGER_RTT_SetFlagsUpBuffer (unsigned BufferIndex, unsigned Flags); -int SEGGER_RTT_WaitKey (void); -unsigned SEGGER_RTT_Write (unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); -unsigned SEGGER_RTT_WriteNoLock (unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); -unsigned SEGGER_RTT_WriteSkipNoLock (unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); -unsigned SEGGER_RTT_ASM_WriteSkipNoLock (unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); -unsigned SEGGER_RTT_WriteString (unsigned BufferIndex, const char* s); -void SEGGER_RTT_WriteWithOverwriteNoLock(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); -unsigned SEGGER_RTT_PutChar (unsigned BufferIndex, char c); -unsigned SEGGER_RTT_PutCharSkip (unsigned BufferIndex, char c); -unsigned SEGGER_RTT_PutCharSkipNoLock (unsigned BufferIndex, char c); -unsigned SEGGER_RTT_GetAvailWriteSpace (unsigned BufferIndex); -unsigned SEGGER_RTT_GetBytesInBuffer (unsigned BufferIndex); -// -// Function macro for performance optimization -// -#define SEGGER_RTT_HASDATA(n) (_SEGGER_RTT.aDown[n].WrOff - _SEGGER_RTT.aDown[n].RdOff) - -#if RTT_USE_ASM - #define SEGGER_RTT_WriteSkipNoLock SEGGER_RTT_ASM_WriteSkipNoLock -#endif - -/********************************************************************* -* -* RTT transfer functions to send RTT data via other channels. -* -********************************************************************** -*/ -unsigned SEGGER_RTT_ReadUpBuffer (unsigned BufferIndex, void* pBuffer, unsigned BufferSize); -unsigned SEGGER_RTT_ReadUpBufferNoLock (unsigned BufferIndex, void* pData, unsigned BufferSize); -unsigned SEGGER_RTT_WriteDownBuffer (unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); -unsigned SEGGER_RTT_WriteDownBufferNoLock (unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); - -#define SEGGER_RTT_HASDATA_UP(n) (_SEGGER_RTT.aUp[n].WrOff - _SEGGER_RTT.aUp[n].RdOff) - -/********************************************************************* -* -* RTT "Terminal" API functions -* -********************************************************************** -*/ -int SEGGER_RTT_SetTerminal (unsigned char TerminalId); -int SEGGER_RTT_TerminalOut (unsigned char TerminalId, const char* s); - -/********************************************************************* -* -* RTT printf functions (require SEGGER_RTT_printf.c) -* -********************************************************************** -*/ -int SEGGER_RTT_printf(unsigned BufferIndex, const char * sFormat, ...); -int SEGGER_RTT_vprintf(unsigned BufferIndex, const char * sFormat, va_list * pParamList); - -#ifdef __cplusplus - } -#endif - -#endif // ifndef(SEGGER_RTT_ASM) - -/********************************************************************* -* -* Defines -* -********************************************************************** -*/ - -// -// Operating modes. Define behavior if buffer is full (not enough space for entire message) -// -#define SEGGER_RTT_MODE_NO_BLOCK_SKIP (0) // Skip. Do not block, output nothing. (Default) -#define SEGGER_RTT_MODE_NO_BLOCK_TRIM (1) // Trim: Do not block, output as much as fits. -#define SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL (2) // Block: Wait until there is space in the buffer. -#define SEGGER_RTT_MODE_MASK (3) - -// -// Control sequences, based on ANSI. -// Can be used to control color, and clear the screen -// -#define RTT_CTRL_RESET "\x1B[0m" // Reset to default colors -#define RTT_CTRL_CLEAR "\x1B[2J" // Clear screen, reposition cursor to top left - -#define RTT_CTRL_TEXT_BLACK "\x1B[2;30m" -#define RTT_CTRL_TEXT_RED "\x1B[2;31m" -#define RTT_CTRL_TEXT_GREEN "\x1B[2;32m" -#define RTT_CTRL_TEXT_YELLOW "\x1B[2;33m" -#define RTT_CTRL_TEXT_BLUE "\x1B[2;34m" -#define RTT_CTRL_TEXT_MAGENTA "\x1B[2;35m" -#define RTT_CTRL_TEXT_CYAN "\x1B[2;36m" -#define RTT_CTRL_TEXT_WHITE "\x1B[2;37m" - -#define RTT_CTRL_TEXT_BRIGHT_BLACK "\x1B[1;30m" -#define RTT_CTRL_TEXT_BRIGHT_RED "\x1B[1;31m" -#define RTT_CTRL_TEXT_BRIGHT_GREEN "\x1B[1;32m" -#define RTT_CTRL_TEXT_BRIGHT_YELLOW "\x1B[1;33m" -#define RTT_CTRL_TEXT_BRIGHT_BLUE "\x1B[1;34m" -#define RTT_CTRL_TEXT_BRIGHT_MAGENTA "\x1B[1;35m" -#define RTT_CTRL_TEXT_BRIGHT_CYAN "\x1B[1;36m" -#define RTT_CTRL_TEXT_BRIGHT_WHITE "\x1B[1;37m" - -#define RTT_CTRL_BG_BLACK "\x1B[24;40m" -#define RTT_CTRL_BG_RED "\x1B[24;41m" -#define RTT_CTRL_BG_GREEN "\x1B[24;42m" -#define RTT_CTRL_BG_YELLOW "\x1B[24;43m" -#define RTT_CTRL_BG_BLUE "\x1B[24;44m" -#define RTT_CTRL_BG_MAGENTA "\x1B[24;45m" -#define RTT_CTRL_BG_CYAN "\x1B[24;46m" -#define RTT_CTRL_BG_WHITE "\x1B[24;47m" - -#define RTT_CTRL_BG_BRIGHT_BLACK "\x1B[4;40m" -#define RTT_CTRL_BG_BRIGHT_RED "\x1B[4;41m" -#define RTT_CTRL_BG_BRIGHT_GREEN "\x1B[4;42m" -#define RTT_CTRL_BG_BRIGHT_YELLOW "\x1B[4;43m" -#define RTT_CTRL_BG_BRIGHT_BLUE "\x1B[4;44m" -#define RTT_CTRL_BG_BRIGHT_MAGENTA "\x1B[4;45m" -#define RTT_CTRL_BG_BRIGHT_CYAN "\x1B[4;46m" -#define RTT_CTRL_BG_BRIGHT_WHITE "\x1B[4;47m" - - -#endif - -/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/include/SEGGER_RTT_Conf.h b/lib/segger_rtt/include/SEGGER_RTT_Conf.h deleted file mode 100644 index a4d8dd2e0..000000000 --- a/lib/segger_rtt/include/SEGGER_RTT_Conf.h +++ /dev/null @@ -1,384 +0,0 @@ -/********************************************************************* -* SEGGER Microcontroller GmbH * -* The Embedded Experts * -********************************************************************** -* * -* (c) 1995 - 2019 SEGGER Microcontroller GmbH * -* * -* www.segger.com Support: support@segger.com * -* * -********************************************************************** -* * -* SEGGER RTT * Real Time Transfer for embedded targets * -* * -********************************************************************** -* * -* All rights reserved. * -* * -* SEGGER strongly recommends to not make any changes * -* to or modify the source code of this software in order to stay * -* compatible with the RTT protocol and J-Link. * -* * -* Redistribution and use in source and binary forms, with or * -* without modification, are permitted provided that the following * -* condition is met: * -* * -* o Redistributions of source code must retain the above copyright * -* notice, this condition and the following disclaimer. * -* * -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * -* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * -* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * -* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * -* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * -* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * -* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * -* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * -* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * -* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * -* DAMAGE. * -* * -********************************************************************** ----------------------------END-OF-HEADER------------------------------ -File : SEGGER_RTT_Conf.h -Purpose : Implementation of SEGGER real-time transfer (RTT) which - allows real-time communication on targets which support - debugger memory accesses while the CPU is running. -Revision: $Rev: 18601 $ - -*/ - -#ifndef SEGGER_RTT_CONF_H -#define SEGGER_RTT_CONF_H - -#ifdef __IAR_SYSTEMS_ICC__ - #include -#endif - -/********************************************************************* -* -* Defines, configurable -* -********************************************************************** -*/ -#ifndef SEGGER_RTT_MAX_NUM_UP_BUFFERS - #define SEGGER_RTT_MAX_NUM_UP_BUFFERS (3) // Max. number of up-buffers (T->H) available on this target (Default: 3) -#endif - -#ifndef SEGGER_RTT_MAX_NUM_DOWN_BUFFERS - #define SEGGER_RTT_MAX_NUM_DOWN_BUFFERS (3) // Max. number of down-buffers (H->T) available on this target (Default: 3) -#endif - -#ifndef BUFFER_SIZE_UP - #define BUFFER_SIZE_UP (1024) // Size of the buffer for terminal output of target, up to host (Default: 1k) -#endif - -#ifndef BUFFER_SIZE_DOWN - #define BUFFER_SIZE_DOWN (16) // Size of the buffer for terminal input to target from host (Usually keyboard input) (Default: 16) -#endif - -#ifndef SEGGER_RTT_PRINTF_BUFFER_SIZE - #define SEGGER_RTT_PRINTF_BUFFER_SIZE (64u) // Size of buffer for RTT printf to bulk-send chars via RTT (Default: 64) -#endif - -#ifndef SEGGER_RTT_MODE_DEFAULT - #define SEGGER_RTT_MODE_DEFAULT SEGGER_RTT_MODE_NO_BLOCK_SKIP // Mode for pre-initialized terminal channel (buffer 0) -#endif - -/********************************************************************* -* -* RTT memcpy configuration -* -* memcpy() is good for large amounts of data, -* but the overhead is big for small amounts, which are usually stored via RTT. -* With SEGGER_RTT_MEMCPY_USE_BYTELOOP a simple byte loop can be used instead. -* -* SEGGER_RTT_MEMCPY() can be used to replace standard memcpy() in RTT functions. -* This is may be required with memory access restrictions, -* such as on Cortex-A devices with MMU. -*/ -#ifndef SEGGER_RTT_MEMCPY_USE_BYTELOOP - #define SEGGER_RTT_MEMCPY_USE_BYTELOOP 0 // 0: Use memcpy/SEGGER_RTT_MEMCPY, 1: Use a simple byte-loop -#endif -// -// Example definition of SEGGER_RTT_MEMCPY to external memcpy with GCC toolchains and Cortex-A targets -// -//#if ((defined __SES_ARM) || (defined __CROSSWORKS_ARM) || (defined __GNUC__)) && (defined (__ARM_ARCH_7A__)) -// #define SEGGER_RTT_MEMCPY(pDest, pSrc, NumBytes) SEGGER_memcpy((pDest), (pSrc), (NumBytes)) -//#endif - -// -// Target is not allowed to perform other RTT operations while string still has not been stored completely. -// Otherwise we would probably end up with a mixed string in the buffer. -// If using RTT from within interrupts, multiple tasks or multi processors, define the SEGGER_RTT_LOCK() and SEGGER_RTT_UNLOCK() function here. -// -// SEGGER_RTT_MAX_INTERRUPT_PRIORITY can be used in the sample lock routines on Cortex-M3/4. -// Make sure to mask all interrupts which can send RTT data, i.e. generate SystemView events, or cause task switches. -// When high-priority interrupts must not be masked while sending RTT data, SEGGER_RTT_MAX_INTERRUPT_PRIORITY needs to be adjusted accordingly. -// (Higher priority = lower priority number) -// Default value for embOS: 128u -// Default configuration in FreeRTOS: configMAX_SYSCALL_INTERRUPT_PRIORITY: ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) -// In case of doubt mask all interrupts: 1 << (8 - BASEPRI_PRIO_BITS) i.e. 1 << 5 when 3 bits are implemented in NVIC -// or define SEGGER_RTT_LOCK() to completely disable interrupts. -// -#ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY - #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) // Interrupt priority to lock on SEGGER_RTT_LOCK on Cortex-M3/4 (Default: 0x20) -#endif - -/********************************************************************* -* -* RTT lock configuration for SEGGER Embedded Studio, -* Rowley CrossStudio and GCC -*/ -#if ((defined(__SES_ARM) || defined(__SES_RISCV) || defined(__CROSSWORKS_ARM) || defined(__GNUC__) || defined(__clang__)) && !defined (__CC_ARM) && !defined(WIN32)) - #if (defined(__ARM_ARCH_6M__) || defined(__ARM_ARCH_8M_BASE__)) - #define SEGGER_RTT_LOCK() { \ - unsigned int LockState; \ - __asm volatile ("mrs %0, primask \n\t" \ - "movs r1, $1 \n\t" \ - "msr primask, r1 \n\t" \ - : "=r" (LockState) \ - : \ - : "r1" \ - ); - - #define SEGGER_RTT_UNLOCK() __asm volatile ("msr primask, %0 \n\t" \ - : \ - : "r" (LockState) \ - : \ - ); \ - } - #elif (defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_8M_MAIN__)) - #ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY - #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) - #endif - #define SEGGER_RTT_LOCK() { \ - unsigned int LockState; \ - __asm volatile ("mrs %0, basepri \n\t" \ - "mov r1, %1 \n\t" \ - "msr basepri, r1 \n\t" \ - : "=r" (LockState) \ - : "i"(SEGGER_RTT_MAX_INTERRUPT_PRIORITY) \ - : "r1" \ - ); - - #define SEGGER_RTT_UNLOCK() __asm volatile ("msr basepri, %0 \n\t" \ - : \ - : "r" (LockState) \ - : \ - ); \ - } - - #elif defined(__ARM_ARCH_7A__) - #define SEGGER_RTT_LOCK() { \ - unsigned int LockState; \ - __asm volatile ("mrs r1, CPSR \n\t" \ - "mov %0, r1 \n\t" \ - "orr r1, r1, #0xC0 \n\t" \ - "msr CPSR_c, r1 \n\t" \ - : "=r" (LockState) \ - : \ - : "r1" \ - ); - - #define SEGGER_RTT_UNLOCK() __asm volatile ("mov r0, %0 \n\t" \ - "mrs r1, CPSR \n\t" \ - "bic r1, r1, #0xC0 \n\t" \ - "and r0, r0, #0xC0 \n\t" \ - "orr r1, r1, r0 \n\t" \ - "msr CPSR_c, r1 \n\t" \ - : \ - : "r" (LockState) \ - : "r0", "r1" \ - ); \ - } - #elif defined(__riscv) || defined(__riscv_xlen) - #define SEGGER_RTT_LOCK() { \ - unsigned int LockState; \ - __asm volatile ("csrr %0, mstatus \n\t" \ - "csrci mstatus, 8 \n\t" \ - "andi %0, %0, 8 \n\t" \ - : "=r" (LockState) \ - : \ - : \ - ); - - #define SEGGER_RTT_UNLOCK() __asm volatile ("csrr a1, mstatus \n\t" \ - "or %0, %0, a1 \n\t" \ - "csrs mstatus, %0 \n\t" \ - : \ - : "r" (LockState) \ - : "a1" \ - ); \ - } - #else - #define SEGGER_RTT_LOCK() - #define SEGGER_RTT_UNLOCK() - #endif -#endif - -/********************************************************************* -* -* RTT lock configuration for IAR EWARM -*/ -#ifdef __ICCARM__ - #if (defined (__ARM6M__) && (__CORE__ == __ARM6M__)) || \ - (defined (__ARM8M_BASELINE__) && (__CORE__ == __ARM8M_BASELINE__)) - #define SEGGER_RTT_LOCK() { \ - unsigned int LockState; \ - LockState = __get_PRIMASK(); \ - __set_PRIMASK(1); - - #define SEGGER_RTT_UNLOCK() __set_PRIMASK(LockState); \ - } - #elif (defined (__ARM7EM__) && (__CORE__ == __ARM7EM__)) || \ - (defined (__ARM7M__) && (__CORE__ == __ARM7M__)) || \ - (defined (__ARM8M_MAINLINE__) && (__CORE__ == __ARM8M_MAINLINE__)) || \ - (defined (__ARM8M_MAINLINE__) && (__CORE__ == __ARM8M_MAINLINE__)) - #ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY - #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) - #endif - #define SEGGER_RTT_LOCK() { \ - unsigned int LockState; \ - LockState = __get_BASEPRI(); \ - __set_BASEPRI(SEGGER_RTT_MAX_INTERRUPT_PRIORITY); - - #define SEGGER_RTT_UNLOCK() __set_BASEPRI(LockState); \ - } - #endif -#endif - -/********************************************************************* -* -* RTT lock configuration for IAR RX -*/ -#ifdef __ICCRX__ - #define SEGGER_RTT_LOCK() { \ - unsigned long LockState; \ - LockState = __get_interrupt_state(); \ - __disable_interrupt(); - - #define SEGGER_RTT_UNLOCK() __set_interrupt_state(LockState); \ - } -#endif - -/********************************************************************* -* -* RTT lock configuration for IAR RL78 -*/ -#ifdef __ICCRL78__ - #define SEGGER_RTT_LOCK() { \ - __istate_t LockState; \ - LockState = __get_interrupt_state(); \ - __disable_interrupt(); - - #define SEGGER_RTT_UNLOCK() __set_interrupt_state(LockState); \ - } -#endif - -/********************************************************************* -* -* RTT lock configuration for KEIL ARM -*/ -#ifdef __CC_ARM - #if (defined __TARGET_ARCH_6S_M) - #define SEGGER_RTT_LOCK() { \ - unsigned int LockState; \ - register unsigned char PRIMASK __asm( "primask"); \ - LockState = PRIMASK; \ - PRIMASK = 1u; \ - __schedule_barrier(); - - #define SEGGER_RTT_UNLOCK() PRIMASK = LockState; \ - __schedule_barrier(); \ - } - #elif (defined(__TARGET_ARCH_7_M) || defined(__TARGET_ARCH_7E_M)) - #ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY - #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) - #endif - #define SEGGER_RTT_LOCK() { \ - unsigned int LockState; \ - register unsigned char BASEPRI __asm( "basepri"); \ - LockState = BASEPRI; \ - BASEPRI = SEGGER_RTT_MAX_INTERRUPT_PRIORITY; \ - __schedule_barrier(); - - #define SEGGER_RTT_UNLOCK() BASEPRI = LockState; \ - __schedule_barrier(); \ - } - #endif -#endif - -/********************************************************************* -* -* RTT lock configuration for TI ARM -*/ -#ifdef __TI_ARM__ - #if defined (__TI_ARM_V6M0__) - #define SEGGER_RTT_LOCK() { \ - unsigned int LockState; \ - LockState = __get_PRIMASK(); \ - __set_PRIMASK(1); - - #define SEGGER_RTT_UNLOCK() __set_PRIMASK(LockState); \ - } - #elif (defined (__TI_ARM_V7M3__) || defined (__TI_ARM_V7M4__)) - #ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY - #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) - #endif - #define SEGGER_RTT_LOCK() { \ - unsigned int LockState; \ - LockState = _set_interrupt_priority(SEGGER_RTT_MAX_INTERRUPT_PRIORITY); - - #define SEGGER_RTT_UNLOCK() _set_interrupt_priority(LockState); \ - } - #endif -#endif - -/********************************************************************* -* -* RTT lock configuration for CCRX -*/ -#ifdef __RX - #define SEGGER_RTT_LOCK() { \ - unsigned long LockState; \ - LockState = get_psw() & 0x010000; \ - clrpsw_i(); - - #define SEGGER_RTT_UNLOCK() set_psw(get_psw() | LockState); \ - } -#endif - -/********************************************************************* -* -* RTT lock configuration for embOS Simulation on Windows -* (Can also be used for generic RTT locking with embOS) -*/ -#if defined(WIN32) || defined(SEGGER_RTT_LOCK_EMBOS) - -void OS_SIM_EnterCriticalSection(void); -void OS_SIM_LeaveCriticalSection(void); - -#define SEGGER_RTT_LOCK() { \ - OS_SIM_EnterCriticalSection(); - -#define SEGGER_RTT_UNLOCK() OS_SIM_LeaveCriticalSection(); \ - } -#endif - -/********************************************************************* -* -* RTT lock configuration fallback -*/ -#ifndef SEGGER_RTT_LOCK - #define SEGGER_RTT_LOCK() // Lock RTT (nestable) (i.e. disable interrupts) -#endif - -#ifndef SEGGER_RTT_UNLOCK - #define SEGGER_RTT_UNLOCK() // Unlock RTT (nestable) (i.e. enable previous interrupt lock state) -#endif - -#endif -/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/src/SEGGER_RTT.c b/lib/segger_rtt/src/SEGGER_RTT.c deleted file mode 100644 index 74791e0ac..000000000 --- a/lib/segger_rtt/src/SEGGER_RTT.c +++ /dev/null @@ -1,2005 +0,0 @@ -/********************************************************************* -* SEGGER Microcontroller GmbH * -* The Embedded Experts * -********************************************************************** -* * -* (c) 1995 - 2019 SEGGER Microcontroller GmbH * -* * -* www.segger.com Support: support@segger.com * -* * -********************************************************************** -* * -* SEGGER RTT * Real Time Transfer for embedded targets * -* * -********************************************************************** -* * -* All rights reserved. * -* * -* SEGGER strongly recommends to not make any changes * -* to or modify the source code of this software in order to stay * -* compatible with the RTT protocol and J-Link. * -* * -* Redistribution and use in source and binary forms, with or * -* without modification, are permitted provided that the following * -* condition is met: * -* * -* o Redistributions of source code must retain the above copyright * -* notice, this condition and the following disclaimer. * -* * -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * -* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * -* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * -* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * -* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * -* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * -* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * -* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * -* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * -* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * -* DAMAGE. * -* * -********************************************************************** ----------------------------END-OF-HEADER------------------------------ -File : SEGGER_RTT.c -Purpose : Implementation of SEGGER real-time transfer (RTT) which - allows real-time communication on targets which support - debugger memory accesses while the CPU is running. -Revision: $Rev: 17697 $ - -Additional information: - Type "int" is assumed to be 32-bits in size - H->T Host to target communication - T->H Target to host communication - - RTT channel 0 is always present and reserved for Terminal usage. - Name is fixed to "Terminal" - - Effective buffer size: SizeOfBuffer - 1 - - WrOff == RdOff: Buffer is empty - WrOff == (RdOff - 1): Buffer is full - WrOff > RdOff: Free space includes wrap-around - WrOff < RdOff: Used space includes wrap-around - (WrOff == (SizeOfBuffer - 1)) && (RdOff == 0): - Buffer full and wrap-around after next byte - - ----------------------------------------------------------------------- -*/ - -#include "SEGGER_RTT.h" - -#include // for memcpy - -/********************************************************************* -* -* Configuration, default values -* -********************************************************************** -*/ - -#ifndef BUFFER_SIZE_UP - #define BUFFER_SIZE_UP 1024 // Size of the buffer for terminal output of target, up to host -#endif - -#ifndef BUFFER_SIZE_DOWN - #define BUFFER_SIZE_DOWN 16 // Size of the buffer for terminal input to target from host (Usually keyboard input) -#endif - -#ifndef SEGGER_RTT_MAX_NUM_UP_BUFFERS - #define SEGGER_RTT_MAX_NUM_UP_BUFFERS 2 // Number of up-buffers (T->H) available on this target -#endif - -#ifndef SEGGER_RTT_MAX_NUM_DOWN_BUFFERS - #define SEGGER_RTT_MAX_NUM_DOWN_BUFFERS 2 // Number of down-buffers (H->T) available on this target -#endif - -#ifndef SEGGER_RTT_BUFFER_SECTION - #if defined(SEGGER_RTT_SECTION) - #define SEGGER_RTT_BUFFER_SECTION SEGGER_RTT_SECTION - #endif -#endif - -#ifndef SEGGER_RTT_ALIGNMENT - #define SEGGER_RTT_ALIGNMENT 0 -#endif - -#ifndef SEGGER_RTT_BUFFER_ALIGNMENT - #define SEGGER_RTT_BUFFER_ALIGNMENT 0 -#endif - -#ifndef SEGGER_RTT_MODE_DEFAULT - #define SEGGER_RTT_MODE_DEFAULT SEGGER_RTT_MODE_NO_BLOCK_SKIP -#endif - -#ifndef SEGGER_RTT_LOCK - #define SEGGER_RTT_LOCK() -#endif - -#ifndef SEGGER_RTT_UNLOCK - #define SEGGER_RTT_UNLOCK() -#endif - -#ifndef STRLEN - #define STRLEN(a) strlen((a)) -#endif - -#ifndef STRCPY - #define STRCPY(pDest, pSrc, NumBytes) strcpy((pDest), (pSrc)) -#endif - -#ifndef SEGGER_RTT_MEMCPY_USE_BYTELOOP - #define SEGGER_RTT_MEMCPY_USE_BYTELOOP 0 -#endif - -#ifndef SEGGER_RTT_MEMCPY - #ifdef MEMCPY - #define SEGGER_RTT_MEMCPY(pDest, pSrc, NumBytes) MEMCPY((pDest), (pSrc), (NumBytes)) - #else - #define SEGGER_RTT_MEMCPY(pDest, pSrc, NumBytes) memcpy((pDest), (pSrc), (NumBytes)) - #endif -#endif - -#ifndef MIN - #define MIN(a, b) (((a) < (b)) ? (a) : (b)) -#endif - -#ifndef MAX - #define MAX(a, b) (((a) > (b)) ? (a) : (b)) -#endif -// -// For some environments, NULL may not be defined until certain headers are included -// -#ifndef NULL - #define NULL 0 -#endif - -/********************************************************************* -* -* Defines, fixed -* -********************************************************************** -*/ -#if (defined __ICCARM__) || (defined __ICCRX__) - #define RTT_PRAGMA(P) _Pragma(#P) -#endif - -#if SEGGER_RTT_ALIGNMENT || SEGGER_RTT_BUFFER_ALIGNMENT - #if (defined __GNUC__) - #define SEGGER_RTT_ALIGN(Var, Alignment) Var __attribute__ ((aligned (Alignment))) - #elif (defined __ICCARM__) || (defined __ICCRX__) - #define PRAGMA(A) _Pragma(#A) -#define SEGGER_RTT_ALIGN(Var, Alignment) RTT_PRAGMA(data_alignment=Alignment) \ - Var - #elif (defined __CC_ARM) - #define SEGGER_RTT_ALIGN(Var, Alignment) Var __attribute__ ((aligned (Alignment))) - #else - #error "Alignment not supported for this compiler." - #endif -#else - #define SEGGER_RTT_ALIGN(Var, Alignment) Var -#endif - -#if defined(SEGGER_RTT_SECTION) || defined (SEGGER_RTT_BUFFER_SECTION) - #if (defined __GNUC__) - #define SEGGER_RTT_PUT_SECTION(Var, Section) __attribute__ ((section (Section))) Var - #elif (defined __ICCARM__) || (defined __ICCRX__) -#define SEGGER_RTT_PUT_SECTION(Var, Section) RTT_PRAGMA(location=Section) \ - Var - #elif (defined __CC_ARM) - #define SEGGER_RTT_PUT_SECTION(Var, Section) __attribute__ ((section (Section), zero_init)) Var - #else - #error "Section placement not supported for this compiler." - #endif -#else - #define SEGGER_RTT_PUT_SECTION(Var, Section) Var -#endif - - -#if SEGGER_RTT_ALIGNMENT - #define SEGGER_RTT_CB_ALIGN(Var) SEGGER_RTT_ALIGN(Var, SEGGER_RTT_ALIGNMENT) -#else - #define SEGGER_RTT_CB_ALIGN(Var) Var -#endif - -#if SEGGER_RTT_BUFFER_ALIGNMENT - #define SEGGER_RTT_BUFFER_ALIGN(Var) SEGGER_RTT_ALIGN(Var, SEGGER_RTT_BUFFER_ALIGNMENT) -#else - #define SEGGER_RTT_BUFFER_ALIGN(Var) Var -#endif - - -#if defined(SEGGER_RTT_SECTION) - #define SEGGER_RTT_PUT_CB_SECTION(Var) SEGGER_RTT_PUT_SECTION(Var, SEGGER_RTT_SECTION) -#else - #define SEGGER_RTT_PUT_CB_SECTION(Var) Var -#endif - -#if defined(SEGGER_RTT_BUFFER_SECTION) - #define SEGGER_RTT_PUT_BUFFER_SECTION(Var) SEGGER_RTT_PUT_SECTION(Var, SEGGER_RTT_BUFFER_SECTION) -#else - #define SEGGER_RTT_PUT_BUFFER_SECTION(Var) Var -#endif - -/********************************************************************* -* -* Static const data -* -********************************************************************** -*/ - -static unsigned char _aTerminalId[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - -/********************************************************************* -* -* Static data -* -********************************************************************** -*/ -// -// RTT Control Block and allocate buffers for channel 0 -// -SEGGER_RTT_PUT_CB_SECTION(SEGGER_RTT_CB_ALIGN(SEGGER_RTT_CB _SEGGER_RTT)); - -SEGGER_RTT_PUT_BUFFER_SECTION(SEGGER_RTT_BUFFER_ALIGN(static char _acUpBuffer [BUFFER_SIZE_UP])); -SEGGER_RTT_PUT_BUFFER_SECTION(SEGGER_RTT_BUFFER_ALIGN(static char _acDownBuffer[BUFFER_SIZE_DOWN])); - -static unsigned char _ActiveTerminal; - -/********************************************************************* -* -* Static functions -* -********************************************************************** -*/ - -/********************************************************************* -* -* _DoInit() -* -* Function description -* Initializes the control block an buffers. -* May only be called via INIT() to avoid overriding settings. -* -*/ -#define INIT() do { \ - if (_SEGGER_RTT.acID[0] == '\0') { _DoInit(); } \ - } while (0) -static void _DoInit(void) { - SEGGER_RTT_CB* p; - // - // Initialize control block - // - p = &_SEGGER_RTT; - p->MaxNumUpBuffers = SEGGER_RTT_MAX_NUM_UP_BUFFERS; - p->MaxNumDownBuffers = SEGGER_RTT_MAX_NUM_DOWN_BUFFERS; - // - // Initialize up buffer 0 - // - p->aUp[0].sName = "Terminal"; - p->aUp[0].pBuffer = _acUpBuffer; - p->aUp[0].SizeOfBuffer = sizeof(_acUpBuffer); - p->aUp[0].RdOff = 0u; - p->aUp[0].WrOff = 0u; - p->aUp[0].Flags = SEGGER_RTT_MODE_DEFAULT; - // - // Initialize down buffer 0 - // - p->aDown[0].sName = "Terminal"; - p->aDown[0].pBuffer = _acDownBuffer; - p->aDown[0].SizeOfBuffer = sizeof(_acDownBuffer); - p->aDown[0].RdOff = 0u; - p->aDown[0].WrOff = 0u; - p->aDown[0].Flags = SEGGER_RTT_MODE_DEFAULT; - // - // Finish initialization of the control block. - // Copy Id string in three steps to make sure "SEGGER RTT" is not found - // in initializer memory (usually flash) by J-Link - // - STRCPY(&p->acID[7], "RTT", 9); - STRCPY(&p->acID[0], "SEGGER", 7); - p->acID[6] = ' '; -} - -/********************************************************************* -* -* _WriteBlocking() -* -* Function description -* Stores a specified number of characters in SEGGER RTT ring buffer -* and updates the associated write pointer which is periodically -* read by the host. -* The caller is responsible for managing the write chunk sizes as -* _WriteBlocking() will block until all data has been posted successfully. -* -* Parameters -* pRing Ring buffer to post to. -* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. -* NumBytes Number of bytes to be stored in the SEGGER RTT control block. -* -* Return value -* >= 0 - Number of bytes written into buffer. -*/ -static unsigned _WriteBlocking(SEGGER_RTT_BUFFER_UP* pRing, const char* pBuffer, unsigned NumBytes) { - unsigned NumBytesToWrite; - unsigned NumBytesWritten; - unsigned RdOff; - unsigned WrOff; -#if SEGGER_RTT_MEMCPY_USE_BYTELOOP - char* pDst; -#endif - // - // Write data to buffer and handle wrap-around if necessary - // - NumBytesWritten = 0u; - WrOff = pRing->WrOff; - do { - RdOff = pRing->RdOff; // May be changed by host (debug probe) in the meantime - if (RdOff > WrOff) { - NumBytesToWrite = RdOff - WrOff - 1u; - } else { - NumBytesToWrite = pRing->SizeOfBuffer - (WrOff - RdOff + 1u); - } - NumBytesToWrite = MIN(NumBytesToWrite, (pRing->SizeOfBuffer - WrOff)); // Number of bytes that can be written until buffer wrap-around - NumBytesToWrite = MIN(NumBytesToWrite, NumBytes); -#if SEGGER_RTT_MEMCPY_USE_BYTELOOP - pDst = pRing->pBuffer + WrOff; - NumBytesWritten += NumBytesToWrite; - NumBytes -= NumBytesToWrite; - WrOff += NumBytesToWrite; - while (NumBytesToWrite--) { - *pDst++ = *pBuffer++; - }; -#else - SEGGER_RTT_MEMCPY(pRing->pBuffer + WrOff, pBuffer, NumBytesToWrite); - NumBytesWritten += NumBytesToWrite; - pBuffer += NumBytesToWrite; - NumBytes -= NumBytesToWrite; - WrOff += NumBytesToWrite; -#endif - if (WrOff == pRing->SizeOfBuffer) { - WrOff = 0u; - } - pRing->WrOff = WrOff; - } while (NumBytes); - // - return NumBytesWritten; -} - -/********************************************************************* -* -* _WriteNoCheck() -* -* Function description -* Stores a specified number of characters in SEGGER RTT ring buffer -* and updates the associated write pointer which is periodically -* read by the host. -* It is callers responsibility to make sure data actually fits in buffer. -* -* Parameters -* pRing Ring buffer to post to. -* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. -* NumBytes Number of bytes to be stored in the SEGGER RTT control block. -* -* Notes -* (1) If there might not be enough space in the "Up"-buffer, call _WriteBlocking -*/ -static void _WriteNoCheck(SEGGER_RTT_BUFFER_UP* pRing, const char* pData, unsigned NumBytes) { - unsigned NumBytesAtOnce; - unsigned WrOff; - unsigned Rem; -#if SEGGER_RTT_MEMCPY_USE_BYTELOOP - char* pDst; -#endif - - WrOff = pRing->WrOff; - Rem = pRing->SizeOfBuffer - WrOff; - if (Rem > NumBytes) { - // - // All data fits before wrap around - // -#if SEGGER_RTT_MEMCPY_USE_BYTELOOP - pDst = pRing->pBuffer + WrOff; - WrOff += NumBytes; - while (NumBytes--) { - *pDst++ = *pData++; - }; - pRing->WrOff = WrOff; -#else - SEGGER_RTT_MEMCPY(pRing->pBuffer + WrOff, pData, NumBytes); - pRing->WrOff = WrOff + NumBytes; -#endif - } else { - // - // We reach the end of the buffer, so need to wrap around - // -#if SEGGER_RTT_MEMCPY_USE_BYTELOOP - pDst = pRing->pBuffer + WrOff; - NumBytesAtOnce = Rem; - while (NumBytesAtOnce--) { - *pDst++ = *pData++; - }; - pDst = pRing->pBuffer; - NumBytesAtOnce = NumBytes - Rem; - while (NumBytesAtOnce--) { - *pDst++ = *pData++; - }; - pRing->WrOff = NumBytes - Rem; -#else - NumBytesAtOnce = Rem; - SEGGER_RTT_MEMCPY(pRing->pBuffer + WrOff, pData, NumBytesAtOnce); - NumBytesAtOnce = NumBytes - Rem; - SEGGER_RTT_MEMCPY(pRing->pBuffer, pData + Rem, NumBytesAtOnce); - pRing->WrOff = NumBytesAtOnce; -#endif - } -} - -/********************************************************************* -* -* _PostTerminalSwitch() -* -* Function description -* Switch terminal to the given terminal ID. It is the caller's -* responsibility to ensure the terminal ID is correct and there is -* enough space in the buffer for this to complete successfully. -* -* Parameters -* pRing Ring buffer to post to. -* TerminalId Terminal ID to switch to. -*/ -static void _PostTerminalSwitch(SEGGER_RTT_BUFFER_UP* pRing, unsigned char TerminalId) { - unsigned char ac[2]; - - ac[0] = 0xFFu; - ac[1] = _aTerminalId[TerminalId]; // Caller made already sure that TerminalId does not exceed our terminal limit - _WriteBlocking(pRing, (const char*)ac, 2u); -} - -/********************************************************************* -* -* _GetAvailWriteSpace() -* -* Function description -* Returns the number of bytes that can be written to the ring -* buffer without blocking. -* -* Parameters -* pRing Ring buffer to check. -* -* Return value -* Number of bytes that are free in the buffer. -*/ -static unsigned _GetAvailWriteSpace(SEGGER_RTT_BUFFER_UP* pRing) { - unsigned RdOff; - unsigned WrOff; - unsigned r; - // - // Avoid warnings regarding volatile access order. It's not a problem - // in this case, but dampen compiler enthusiasm. - // - RdOff = pRing->RdOff; - WrOff = pRing->WrOff; - if (RdOff <= WrOff) { - r = pRing->SizeOfBuffer - 1u - WrOff + RdOff; - } else { - r = RdOff - WrOff - 1u; - } - return r; -} - -/********************************************************************* -* -* Public code -* -********************************************************************** -*/ -/********************************************************************* -* -* SEGGER_RTT_ReadUpBufferNoLock() -* -* Function description -* Reads characters from SEGGER real-time-terminal control block -* which have been previously stored by the application. -* Do not lock against interrupts and multiple access. -* Used to do the same operation that J-Link does, to transfer -* RTT data via other channels, such as TCP/IP or UART. -* -* Parameters -* BufferIndex Index of Up-buffer to be used. -* pBuffer Pointer to buffer provided by target application, to copy characters from RTT-up-buffer to. -* BufferSize Size of the target application buffer. -* -* Return value -* Number of bytes that have been read. -* -* Additional information -* This function must not be called when J-Link might also do RTT. -*/ -unsigned SEGGER_RTT_ReadUpBufferNoLock(unsigned BufferIndex, void* pData, unsigned BufferSize) { - unsigned NumBytesRem; - unsigned NumBytesRead; - unsigned RdOff; - unsigned WrOff; - unsigned char* pBuffer; - SEGGER_RTT_BUFFER_UP* pRing; -#if SEGGER_RTT_MEMCPY_USE_BYTELOOP - const char* pSrc; -#endif - // - INIT(); - pRing = &_SEGGER_RTT.aUp[BufferIndex]; - pBuffer = (unsigned char*)pData; - RdOff = pRing->RdOff; - WrOff = pRing->WrOff; - NumBytesRead = 0u; - // - // Read from current read position to wrap-around of buffer, first - // - if (RdOff > WrOff) { - NumBytesRem = pRing->SizeOfBuffer - RdOff; - NumBytesRem = MIN(NumBytesRem, BufferSize); -#if SEGGER_RTT_MEMCPY_USE_BYTELOOP - pSrc = pRing->pBuffer + RdOff; - NumBytesRead += NumBytesRem; - BufferSize -= NumBytesRem; - RdOff += NumBytesRem; - while (NumBytesRem--) { - *pBuffer++ = *pSrc++; - }; -#else - SEGGER_RTT_MEMCPY(pBuffer, pRing->pBuffer + RdOff, NumBytesRem); - NumBytesRead += NumBytesRem; - pBuffer += NumBytesRem; - BufferSize -= NumBytesRem; - RdOff += NumBytesRem; -#endif - // - // Handle wrap-around of buffer - // - if (RdOff == pRing->SizeOfBuffer) { - RdOff = 0u; - } - } - // - // Read remaining items of buffer - // - NumBytesRem = WrOff - RdOff; - NumBytesRem = MIN(NumBytesRem, BufferSize); - if (NumBytesRem > 0u) { -#if SEGGER_RTT_MEMCPY_USE_BYTELOOP - pSrc = pRing->pBuffer + RdOff; - NumBytesRead += NumBytesRem; - BufferSize -= NumBytesRem; - RdOff += NumBytesRem; - while (NumBytesRem--) { - *pBuffer++ = *pSrc++; - }; -#else - SEGGER_RTT_MEMCPY(pBuffer, pRing->pBuffer + RdOff, NumBytesRem); - NumBytesRead += NumBytesRem; - pBuffer += NumBytesRem; - BufferSize -= NumBytesRem; - RdOff += NumBytesRem; -#endif - } - // - // Update read offset of buffer - // - if (NumBytesRead) { - pRing->RdOff = RdOff; - } - // - return NumBytesRead; -} - -/********************************************************************* -* -* SEGGER_RTT_ReadNoLock() -* -* Function description -* Reads characters from SEGGER real-time-terminal control block -* which have been previously stored by the host. -* Do not lock against interrupts and multiple access. -* -* Parameters -* BufferIndex Index of Down-buffer to be used (e.g. 0 for "Terminal"). -* pBuffer Pointer to buffer provided by target application, to copy characters from RTT-down-buffer to. -* BufferSize Size of the target application buffer. -* -* Return value -* Number of bytes that have been read. -*/ -unsigned SEGGER_RTT_ReadNoLock(unsigned BufferIndex, void* pData, unsigned BufferSize) { - unsigned NumBytesRem; - unsigned NumBytesRead; - unsigned RdOff; - unsigned WrOff; - unsigned char* pBuffer; - SEGGER_RTT_BUFFER_DOWN* pRing; -#if SEGGER_RTT_MEMCPY_USE_BYTELOOP - const char* pSrc; -#endif - // - INIT(); - pRing = &_SEGGER_RTT.aDown[BufferIndex]; - pBuffer = (unsigned char*)pData; - RdOff = pRing->RdOff; - WrOff = pRing->WrOff; - NumBytesRead = 0u; - // - // Read from current read position to wrap-around of buffer, first - // - if (RdOff > WrOff) { - NumBytesRem = pRing->SizeOfBuffer - RdOff; - NumBytesRem = MIN(NumBytesRem, BufferSize); -#if SEGGER_RTT_MEMCPY_USE_BYTELOOP - pSrc = pRing->pBuffer + RdOff; - NumBytesRead += NumBytesRem; - BufferSize -= NumBytesRem; - RdOff += NumBytesRem; - while (NumBytesRem--) { - *pBuffer++ = *pSrc++; - }; -#else - SEGGER_RTT_MEMCPY(pBuffer, pRing->pBuffer + RdOff, NumBytesRem); - NumBytesRead += NumBytesRem; - pBuffer += NumBytesRem; - BufferSize -= NumBytesRem; - RdOff += NumBytesRem; -#endif - // - // Handle wrap-around of buffer - // - if (RdOff == pRing->SizeOfBuffer) { - RdOff = 0u; - } - } - // - // Read remaining items of buffer - // - NumBytesRem = WrOff - RdOff; - NumBytesRem = MIN(NumBytesRem, BufferSize); - if (NumBytesRem > 0u) { -#if SEGGER_RTT_MEMCPY_USE_BYTELOOP - pSrc = pRing->pBuffer + RdOff; - NumBytesRead += NumBytesRem; - BufferSize -= NumBytesRem; - RdOff += NumBytesRem; - while (NumBytesRem--) { - *pBuffer++ = *pSrc++; - }; -#else - SEGGER_RTT_MEMCPY(pBuffer, pRing->pBuffer + RdOff, NumBytesRem); - NumBytesRead += NumBytesRem; - pBuffer += NumBytesRem; - BufferSize -= NumBytesRem; - RdOff += NumBytesRem; -#endif - } - if (NumBytesRead) { - pRing->RdOff = RdOff; - } - // - return NumBytesRead; -} - -/********************************************************************* -* -* SEGGER_RTT_ReadUpBuffer -* -* Function description -* Reads characters from SEGGER real-time-terminal control block -* which have been previously stored by the application. -* Used to do the same operation that J-Link does, to transfer -* RTT data via other channels, such as TCP/IP or UART. -* -* Parameters -* BufferIndex Index of Up-buffer to be used. -* pBuffer Pointer to buffer provided by target application, to copy characters from RTT-up-buffer to. -* BufferSize Size of the target application buffer. -* -* Return value -* Number of bytes that have been read. -* -* Additional information -* This function must not be called when J-Link might also do RTT. -* This function locks against all other RTT operations. I.e. during -* the read operation, writing is also locked. -* If only one consumer reads from the up buffer, -* call sEGGER_RTT_ReadUpBufferNoLock() instead. -*/ -unsigned SEGGER_RTT_ReadUpBuffer(unsigned BufferIndex, void* pBuffer, unsigned BufferSize) { - unsigned NumBytesRead; - // - SEGGER_RTT_LOCK(); - // - // Call the non-locking read function - // - NumBytesRead = SEGGER_RTT_ReadUpBufferNoLock(BufferIndex, pBuffer, BufferSize); - // - // Finish up. - // - SEGGER_RTT_UNLOCK(); - // - return NumBytesRead; -} - -/********************************************************************* -* -* SEGGER_RTT_Read -* -* Function description -* Reads characters from SEGGER real-time-terminal control block -* which have been previously stored by the host. -* -* Parameters -* BufferIndex Index of Down-buffer to be used (e.g. 0 for "Terminal"). -* pBuffer Pointer to buffer provided by target application, to copy characters from RTT-down-buffer to. -* BufferSize Size of the target application buffer. -* -* Return value -* Number of bytes that have been read. -*/ -unsigned SEGGER_RTT_Read(unsigned BufferIndex, void* pBuffer, unsigned BufferSize) { - unsigned NumBytesRead; - // - SEGGER_RTT_LOCK(); - // - // Call the non-locking read function - // - NumBytesRead = SEGGER_RTT_ReadNoLock(BufferIndex, pBuffer, BufferSize); - // - // Finish up. - // - SEGGER_RTT_UNLOCK(); - // - return NumBytesRead; -} - -/********************************************************************* -* -* SEGGER_RTT_WriteWithOverwriteNoLock -* -* Function description -* Stores a specified number of characters in SEGGER RTT -* control block. -* SEGGER_RTT_WriteWithOverwriteNoLock does not lock the application -* and overwrites data if the data does not fit into the buffer. -* -* Parameters -* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). -* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. -* NumBytes Number of bytes to be stored in the SEGGER RTT control block. -* -* Notes -* (1) If there is not enough space in the "Up"-buffer, data is overwritten. -* (2) For performance reasons this function does not call Init() -* and may only be called after RTT has been initialized. -* Either by calling SEGGER_RTT_Init() or calling another RTT API function first. -* (3) Do not use SEGGER_RTT_WriteWithOverwriteNoLock if a J-Link -* connection reads RTT data. -*/ -void SEGGER_RTT_WriteWithOverwriteNoLock(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes) { - const char* pData; - SEGGER_RTT_BUFFER_UP* pRing; - unsigned Avail; -#if SEGGER_RTT_MEMCPY_USE_BYTELOOP - char* pDst; -#endif - - pData = (const char *)pBuffer; - // - // Get "to-host" ring buffer and copy some elements into local variables. - // - pRing = &_SEGGER_RTT.aUp[BufferIndex]; - // - // Check if we will overwrite data and need to adjust the RdOff. - // - if (pRing->WrOff == pRing->RdOff) { - Avail = pRing->SizeOfBuffer - 1u; - } else if ( pRing->WrOff < pRing->RdOff) { - Avail = pRing->RdOff - pRing->WrOff - 1u; - } else { - Avail = pRing->RdOff - pRing->WrOff - 1u + pRing->SizeOfBuffer; - } - if (NumBytes > Avail) { - pRing->RdOff += (NumBytes - Avail); - while (pRing->RdOff >= pRing->SizeOfBuffer) { - pRing->RdOff -= pRing->SizeOfBuffer; - } - } - // - // Write all data, no need to check the RdOff, but possibly handle multiple wrap-arounds - // - Avail = pRing->SizeOfBuffer - pRing->WrOff; - do { - if (Avail > NumBytes) { - // - // Last round - // -#if SEGGER_RTT_MEMCPY_USE_BYTELOOP - pDst = pRing->pBuffer + pRing->WrOff; - Avail = NumBytes; - while (NumBytes--) { - *pDst++ = *pData++; - }; - pRing->WrOff += Avail; -#else - SEGGER_RTT_MEMCPY(pRing->pBuffer + pRing->WrOff, pData, NumBytes); - pRing->WrOff += NumBytes; -#endif - break; - } else { - // - // Wrap-around necessary, write until wrap-around and reset WrOff - // -#if SEGGER_RTT_MEMCPY_USE_BYTELOOP - pDst = pRing->pBuffer + pRing->WrOff; - NumBytes -= Avail; - while (Avail--) { - *pDst++ = *pData++; - }; - pRing->WrOff = 0; -#else - SEGGER_RTT_MEMCPY(pRing->pBuffer + pRing->WrOff, pData, Avail); - pData += Avail; - pRing->WrOff = 0; - NumBytes -= Avail; -#endif - Avail = (pRing->SizeOfBuffer - 1); - } - } while (NumBytes); -} - -/********************************************************************* -* -* SEGGER_RTT_WriteSkipNoLock -* -* Function description -* Stores a specified number of characters in SEGGER RTT -* control block which is then read by the host. -* SEGGER_RTT_WriteSkipNoLock does not lock the application and -* skips all data, if the data does not fit into the buffer. -* -* Parameters -* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). -* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. -* NumBytes Number of bytes to be stored in the SEGGER RTT control block. -* MUST be > 0!!! -* This is done for performance reasons, so no initial check has do be done. -* -* Return value -* 1: Data has been copied -* 0: No space, data has not been copied -* -* Notes -* (1) If there is not enough space in the "Up"-buffer, all data is dropped. -* (2) For performance reasons this function does not call Init() -* and may only be called after RTT has been initialized. -* Either by calling SEGGER_RTT_Init() or calling another RTT API function first. -*/ -#if (RTT_USE_ASM == 0) -unsigned SEGGER_RTT_WriteSkipNoLock(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes) { - const char* pData; - SEGGER_RTT_BUFFER_UP* pRing; - unsigned Avail; - unsigned RdOff; - unsigned WrOff; - unsigned Rem; - // - // Cases: - // 1) RdOff <= WrOff => Space until wrap-around is sufficient - // 2) RdOff <= WrOff => Space after wrap-around needed (copy in 2 chunks) - // 3) RdOff < WrOff => No space in buf - // 4) RdOff > WrOff => Space is sufficient - // 5) RdOff > WrOff => No space in buf - // - // 1) is the most common case for large buffers and assuming that J-Link reads the data fast enough - // - pData = (const char *)pBuffer; - pRing = &_SEGGER_RTT.aUp[BufferIndex]; - RdOff = pRing->RdOff; - WrOff = pRing->WrOff; - if (RdOff <= WrOff) { // Case 1), 2) or 3) - Avail = pRing->SizeOfBuffer - WrOff - 1u; // Space until wrap-around (assume 1 byte not usable for case that RdOff == 0) - if (Avail >= NumBytes) { // Case 1)? -CopyStraight: - memcpy(pRing->pBuffer + WrOff, pData, NumBytes); - pRing->WrOff = WrOff + NumBytes; - return 1; - } - Avail += RdOff; // Space incl. wrap-around - if (Avail >= NumBytes) { // Case 2? => If not, we have case 3) (does not fit) - Rem = pRing->SizeOfBuffer - WrOff; // Space until end of buffer - memcpy(pRing->pBuffer + WrOff, pData, Rem); // Copy 1st chunk - NumBytes -= Rem; - // - // Special case: First check that assumed RdOff == 0 calculated that last element before wrap-around could not be used - // But 2nd check (considering space until wrap-around and until RdOff) revealed that RdOff is not 0, so we can use the last element - // In this case, we may use a copy straight until buffer end anyway without needing to copy 2 chunks - // Therefore, check if 2nd memcpy is necessary at all - // - if (NumBytes) { - memcpy(pRing->pBuffer, pData + Rem, NumBytes); - } - pRing->WrOff = NumBytes; - return 1; - } - } else { // Potential case 4) - Avail = RdOff - WrOff - 1u; - if (Avail >= NumBytes) { // Case 4)? => If not, we have case 5) (does not fit) - goto CopyStraight; - } - } - return 0; // No space in buffer -} -#endif - -/********************************************************************* -* -* SEGGER_RTT_WriteDownBufferNoLock -* -* Function description -* Stores a specified number of characters in SEGGER RTT -* control block inside a buffer. -* SEGGER_RTT_WriteDownBufferNoLock does not lock the application. -* Used to do the same operation that J-Link does, to transfer -* RTT data from other channels, such as TCP/IP or UART. -* -* Parameters -* BufferIndex Index of "Down"-buffer to be used. -* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. -* NumBytes Number of bytes to be stored in the SEGGER RTT control block. -* -* Return value -* Number of bytes which have been stored in the "Down"-buffer. -* -* Notes -* (1) Data is stored according to buffer flags. -* (2) For performance reasons this function does not call Init() -* and may only be called after RTT has been initialized. -* Either by calling SEGGER_RTT_Init() or calling another RTT API function first. -* -* Additional information -* This function must not be called when J-Link might also do RTT. -*/ -unsigned SEGGER_RTT_WriteDownBufferNoLock(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes) { - unsigned Status; - unsigned Avail; - const char* pData; - SEGGER_RTT_BUFFER_UP* pRing; - - pData = (const char *)pBuffer; - // - // Get "to-target" ring buffer. - // It is save to cast that to a "to-host" buffer. Up and Down buffer differ in volatility of offsets that might be modified by J-Link. - // - pRing = (SEGGER_RTT_BUFFER_UP*)&_SEGGER_RTT.aDown[BufferIndex]; - // - // How we output depends upon the mode... - // - switch (pRing->Flags) { - case SEGGER_RTT_MODE_NO_BLOCK_SKIP: - // - // If we are in skip mode and there is no space for the whole - // of this output, don't bother. - // - Avail = _GetAvailWriteSpace(pRing); - if (Avail < NumBytes) { - Status = 0u; - } else { - Status = NumBytes; - _WriteNoCheck(pRing, pData, NumBytes); - } - break; - case SEGGER_RTT_MODE_NO_BLOCK_TRIM: - // - // If we are in trim mode, trim to what we can output without blocking. - // - Avail = _GetAvailWriteSpace(pRing); - Status = Avail < NumBytes ? Avail : NumBytes; - _WriteNoCheck(pRing, pData, Status); - break; - case SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL: - // - // If we are in blocking mode, output everything. - // - Status = _WriteBlocking(pRing, pData, NumBytes); - break; - default: - Status = 0u; - break; - } - // - // Finish up. - // - return Status; -} - -/********************************************************************* -* -* SEGGER_RTT_WriteNoLock -* -* Function description -* Stores a specified number of characters in SEGGER RTT -* control block which is then read by the host. -* SEGGER_RTT_WriteNoLock does not lock the application. -* -* Parameters -* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). -* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. -* NumBytes Number of bytes to be stored in the SEGGER RTT control block. -* -* Return value -* Number of bytes which have been stored in the "Up"-buffer. -* -* Notes -* (1) Data is stored according to buffer flags. -* (2) For performance reasons this function does not call Init() -* and may only be called after RTT has been initialized. -* Either by calling SEGGER_RTT_Init() or calling another RTT API function first. -*/ -unsigned SEGGER_RTT_WriteNoLock(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes) { - unsigned Status; - unsigned Avail; - const char* pData; - SEGGER_RTT_BUFFER_UP* pRing; - - pData = (const char *)pBuffer; - // - // Get "to-host" ring buffer. - // - pRing = &_SEGGER_RTT.aUp[BufferIndex]; - // - // How we output depends upon the mode... - // - switch (pRing->Flags) { - case SEGGER_RTT_MODE_NO_BLOCK_SKIP: - // - // If we are in skip mode and there is no space for the whole - // of this output, don't bother. - // - Avail = _GetAvailWriteSpace(pRing); - if (Avail < NumBytes) { - Status = 0u; - } else { - Status = NumBytes; - _WriteNoCheck(pRing, pData, NumBytes); - } - break; - case SEGGER_RTT_MODE_NO_BLOCK_TRIM: - // - // If we are in trim mode, trim to what we can output without blocking. - // - Avail = _GetAvailWriteSpace(pRing); - Status = Avail < NumBytes ? Avail : NumBytes; - _WriteNoCheck(pRing, pData, Status); - break; - case SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL: - // - // If we are in blocking mode, output everything. - // - Status = _WriteBlocking(pRing, pData, NumBytes); - break; - default: - Status = 0u; - break; - } - // - // Finish up. - // - return Status; -} - -/********************************************************************* -* -* SEGGER_RTT_WriteDownBuffer -* -* Function description -* Stores a specified number of characters in SEGGER RTT control block in a buffer. -* -* Parameters -* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). -* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. -* NumBytes Number of bytes to be stored in the SEGGER RTT control block. -* -* Return value -* Number of bytes which have been stored in the "Down"-buffer. -* -* Notes -* (1) Data is stored according to buffer flags. -* -* Additional information -* This function must not be called when J-Link might also do RTT. -* This function locks against all other RTT operations. I.e. during -* the write operation, writing from the application is also locked. -* If only one consumer writes to the down buffer, -* call SEGGER_RTT_WriteDownBufferNoLock() instead. -*/ -unsigned SEGGER_RTT_WriteDownBuffer(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes) { - unsigned Status; - // - INIT(); - SEGGER_RTT_LOCK(); - // - // Call the non-locking write function - // - Status = SEGGER_RTT_WriteDownBufferNoLock(BufferIndex, pBuffer, NumBytes); - // - // Finish up. - // - SEGGER_RTT_UNLOCK(); - // - return Status; -} - -/********************************************************************* -* -* SEGGER_RTT_Write -* -* Function description -* Stores a specified number of characters in SEGGER RTT -* control block which is then read by the host. -* -* Parameters -* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). -* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. -* NumBytes Number of bytes to be stored in the SEGGER RTT control block. -* -* Return value -* Number of bytes which have been stored in the "Up"-buffer. -* -* Notes -* (1) Data is stored according to buffer flags. -*/ -unsigned SEGGER_RTT_Write(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes) { - unsigned Status; - // - INIT(); - SEGGER_RTT_LOCK(); - // - // Call the non-locking write function - // - Status = SEGGER_RTT_WriteNoLock(BufferIndex, pBuffer, NumBytes); - // - // Finish up. - // - SEGGER_RTT_UNLOCK(); - // - return Status; -} - -/********************************************************************* -* -* SEGGER_RTT_WriteString -* -* Function description -* Stores string in SEGGER RTT control block. -* This data is read by the host. -* -* Parameters -* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). -* s Pointer to string. -* -* Return value -* Number of bytes which have been stored in the "Up"-buffer. -* -* Notes -* (1) Data is stored according to buffer flags. -* (2) String passed to this function has to be \0 terminated -* (3) \0 termination character is *not* stored in RTT buffer -*/ -unsigned SEGGER_RTT_WriteString(unsigned BufferIndex, const char* s) { - unsigned Len; - - Len = STRLEN(s); - return SEGGER_RTT_Write(BufferIndex, s, Len); -} - -/********************************************************************* -* -* SEGGER_RTT_PutCharSkipNoLock -* -* Function description -* Stores a single character/byte in SEGGER RTT buffer. -* SEGGER_RTT_PutCharSkipNoLock does not lock the application and -* skips the byte, if it does not fit into the buffer. -* -* Parameters -* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). -* c Byte to be stored. -* -* Return value -* Number of bytes which have been stored in the "Up"-buffer. -* -* Notes -* (1) If there is not enough space in the "Up"-buffer, the character is dropped. -* (2) For performance reasons this function does not call Init() -* and may only be called after RTT has been initialized. -* Either by calling SEGGER_RTT_Init() or calling another RTT API function first. -*/ - -unsigned SEGGER_RTT_PutCharSkipNoLock(unsigned BufferIndex, char c) { - SEGGER_RTT_BUFFER_UP* pRing; - unsigned WrOff; - unsigned Status; - // - // Get "to-host" ring buffer. - // - pRing = &_SEGGER_RTT.aUp[BufferIndex]; - // - // Get write position and handle wrap-around if necessary - // - WrOff = pRing->WrOff + 1; - if (WrOff == pRing->SizeOfBuffer) { - WrOff = 0; - } - // - // Output byte if free space is available - // - if (WrOff != pRing->RdOff) { - pRing->pBuffer[pRing->WrOff] = c; - pRing->WrOff = WrOff; - Status = 1; - } else { - Status = 0; - } - // - return Status; -} - -/********************************************************************* -* -* SEGGER_RTT_PutCharSkip -* -* Function description -* Stores a single character/byte in SEGGER RTT buffer. -* -* Parameters -* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). -* c Byte to be stored. -* -* Return value -* Number of bytes which have been stored in the "Up"-buffer. -* -* Notes -* (1) If there is not enough space in the "Up"-buffer, the character is dropped. -*/ - -unsigned SEGGER_RTT_PutCharSkip(unsigned BufferIndex, char c) { - SEGGER_RTT_BUFFER_UP* pRing; - unsigned WrOff; - unsigned Status; - // - // Prepare - // - INIT(); - SEGGER_RTT_LOCK(); - // - // Get "to-host" ring buffer. - // - pRing = &_SEGGER_RTT.aUp[BufferIndex]; - // - // Get write position and handle wrap-around if necessary - // - WrOff = pRing->WrOff + 1; - if (WrOff == pRing->SizeOfBuffer) { - WrOff = 0; - } - // - // Output byte if free space is available - // - if (WrOff != pRing->RdOff) { - pRing->pBuffer[pRing->WrOff] = c; - pRing->WrOff = WrOff; - Status = 1; - } else { - Status = 0; - } - // - // Finish up. - // - SEGGER_RTT_UNLOCK(); - // - return Status; -} - - /********************************************************************* -* -* SEGGER_RTT_PutChar -* -* Function description -* Stores a single character/byte in SEGGER RTT buffer. -* -* Parameters -* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). -* c Byte to be stored. -* -* Return value -* Number of bytes which have been stored in the "Up"-buffer. -* -* Notes -* (1) Data is stored according to buffer flags. -*/ - -unsigned SEGGER_RTT_PutChar(unsigned BufferIndex, char c) { - SEGGER_RTT_BUFFER_UP* pRing; - unsigned WrOff; - unsigned Status; - // - // Prepare - // - INIT(); - SEGGER_RTT_LOCK(); - // - // Get "to-host" ring buffer. - // - pRing = &_SEGGER_RTT.aUp[BufferIndex]; - // - // Get write position and handle wrap-around if necessary - // - WrOff = pRing->WrOff + 1; - if (WrOff == pRing->SizeOfBuffer) { - WrOff = 0; - } - // - // Wait for free space if mode is set to blocking - // - if (pRing->Flags == SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL) { - while (WrOff == pRing->RdOff) { - ; - } - } - // - // Output byte if free space is available - // - if (WrOff != pRing->RdOff) { - pRing->pBuffer[pRing->WrOff] = c; - pRing->WrOff = WrOff; - Status = 1; - } else { - Status = 0; - } - // - // Finish up. - // - SEGGER_RTT_UNLOCK(); - // - return Status; -} - -/********************************************************************* -* -* SEGGER_RTT_GetKey -* -* Function description -* Reads one character from the SEGGER RTT buffer. -* Host has previously stored data there. -* -* Return value -* < 0 - No character available (buffer empty). -* >= 0 - Character which has been read. (Possible values: 0 - 255) -* -* Notes -* (1) This function is only specified for accesses to RTT buffer 0. -*/ -int SEGGER_RTT_GetKey(void) { - char c; - int r; - - r = (int)SEGGER_RTT_Read(0u, &c, 1u); - if (r == 1) { - r = (int)(unsigned char)c; - } else { - r = -1; - } - return r; -} - -/********************************************************************* -* -* SEGGER_RTT_WaitKey -* -* Function description -* Waits until at least one character is avaible in the SEGGER RTT buffer. -* Once a character is available, it is read and this function returns. -* -* Return value -* >=0 - Character which has been read. -* -* Notes -* (1) This function is only specified for accesses to RTT buffer 0 -* (2) This function is blocking if no character is present in RTT buffer -*/ -int SEGGER_RTT_WaitKey(void) { - int r; - - do { - r = SEGGER_RTT_GetKey(); - } while (r < 0); - return r; -} - -/********************************************************************* -* -* SEGGER_RTT_HasKey -* -* Function description -* Checks if at least one character for reading is available in the SEGGER RTT buffer. -* -* Return value -* == 0 - No characters are available to read. -* == 1 - At least one character is available. -* -* Notes -* (1) This function is only specified for accesses to RTT buffer 0 -*/ -int SEGGER_RTT_HasKey(void) { - unsigned RdOff; - int r; - - INIT(); - RdOff = _SEGGER_RTT.aDown[0].RdOff; - if (RdOff != _SEGGER_RTT.aDown[0].WrOff) { - r = 1; - } else { - r = 0; - } - return r; -} - -/********************************************************************* -* -* SEGGER_RTT_HasData -* -* Function description -* Check if there is data from the host in the given buffer. -* -* Return value: -* ==0: No data -* !=0: Data in buffer -* -*/ -unsigned SEGGER_RTT_HasData(unsigned BufferIndex) { - SEGGER_RTT_BUFFER_DOWN* pRing; - unsigned v; - - pRing = &_SEGGER_RTT.aDown[BufferIndex]; - v = pRing->WrOff; - return v - pRing->RdOff; -} - -/********************************************************************* -* -* SEGGER_RTT_HasDataUp -* -* Function description -* Check if there is data remaining to be sent in the given buffer. -* -* Return value: -* ==0: No data -* !=0: Data in buffer -* -*/ -unsigned SEGGER_RTT_HasDataUp(unsigned BufferIndex) { - SEGGER_RTT_BUFFER_UP* pRing; - unsigned v; - - pRing = &_SEGGER_RTT.aUp[BufferIndex]; - v = pRing->RdOff; - return pRing->WrOff - v; -} - -/********************************************************************* -* -* SEGGER_RTT_AllocDownBuffer -* -* Function description -* Run-time configuration of the next down-buffer (H->T). -* The next buffer, which is not used yet is configured. -* This includes: Buffer address, size, name, flags, ... -* -* Parameters -* sName Pointer to a constant name string. -* pBuffer Pointer to a buffer to be used. -* BufferSize Size of the buffer. -* Flags Operating modes. Define behavior if buffer is full (not enough space for entire message). -* -* Return value -* >= 0 - O.K. Buffer Index -* < 0 - Error -*/ -int SEGGER_RTT_AllocDownBuffer(const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags) { - int BufferIndex; - - INIT(); - SEGGER_RTT_LOCK(); - BufferIndex = 0; - do { - if (_SEGGER_RTT.aDown[BufferIndex].pBuffer == NULL) { - break; - } - BufferIndex++; - } while (BufferIndex < _SEGGER_RTT.MaxNumDownBuffers); - if (BufferIndex < _SEGGER_RTT.MaxNumDownBuffers) { - _SEGGER_RTT.aDown[BufferIndex].sName = sName; - _SEGGER_RTT.aDown[BufferIndex].pBuffer = (char*)pBuffer; - _SEGGER_RTT.aDown[BufferIndex].SizeOfBuffer = BufferSize; - _SEGGER_RTT.aDown[BufferIndex].RdOff = 0u; - _SEGGER_RTT.aDown[BufferIndex].WrOff = 0u; - _SEGGER_RTT.aDown[BufferIndex].Flags = Flags; - } else { - BufferIndex = -1; - } - SEGGER_RTT_UNLOCK(); - return BufferIndex; -} - -/********************************************************************* -* -* SEGGER_RTT_AllocUpBuffer -* -* Function description -* Run-time configuration of the next up-buffer (T->H). -* The next buffer, which is not used yet is configured. -* This includes: Buffer address, size, name, flags, ... -* -* Parameters -* sName Pointer to a constant name string. -* pBuffer Pointer to a buffer to be used. -* BufferSize Size of the buffer. -* Flags Operating modes. Define behavior if buffer is full (not enough space for entire message). -* -* Return value -* >= 0 - O.K. Buffer Index -* < 0 - Error -*/ -int SEGGER_RTT_AllocUpBuffer(const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags) { - int BufferIndex; - - INIT(); - SEGGER_RTT_LOCK(); - BufferIndex = 0; - do { - if (_SEGGER_RTT.aUp[BufferIndex].pBuffer == NULL) { - break; - } - BufferIndex++; - } while (BufferIndex < _SEGGER_RTT.MaxNumUpBuffers); - if (BufferIndex < _SEGGER_RTT.MaxNumUpBuffers) { - _SEGGER_RTT.aUp[BufferIndex].sName = sName; - _SEGGER_RTT.aUp[BufferIndex].pBuffer = (char*)pBuffer; - _SEGGER_RTT.aUp[BufferIndex].SizeOfBuffer = BufferSize; - _SEGGER_RTT.aUp[BufferIndex].RdOff = 0u; - _SEGGER_RTT.aUp[BufferIndex].WrOff = 0u; - _SEGGER_RTT.aUp[BufferIndex].Flags = Flags; - } else { - BufferIndex = -1; - } - SEGGER_RTT_UNLOCK(); - return BufferIndex; -} - -/********************************************************************* -* -* SEGGER_RTT_ConfigUpBuffer -* -* Function description -* Run-time configuration of a specific up-buffer (T->H). -* Buffer to be configured is specified by index. -* This includes: Buffer address, size, name, flags, ... -* -* Parameters -* BufferIndex Index of the buffer to configure. -* sName Pointer to a constant name string. -* pBuffer Pointer to a buffer to be used. -* BufferSize Size of the buffer. -* Flags Operating modes. Define behavior if buffer is full (not enough space for entire message). -* -* Return value -* >= 0 - O.K. -* < 0 - Error -* -* Additional information -* Buffer 0 is configured on compile-time. -* May only be called once per buffer. -* Buffer name and flags can be reconfigured using the appropriate functions. -*/ -int SEGGER_RTT_ConfigUpBuffer(unsigned BufferIndex, const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags) { - int r; - - INIT(); - if (BufferIndex < (unsigned)_SEGGER_RTT.MaxNumUpBuffers) { - SEGGER_RTT_LOCK(); - if (BufferIndex > 0u) { - _SEGGER_RTT.aUp[BufferIndex].sName = sName; - _SEGGER_RTT.aUp[BufferIndex].pBuffer = (char*)pBuffer; - _SEGGER_RTT.aUp[BufferIndex].SizeOfBuffer = BufferSize; - _SEGGER_RTT.aUp[BufferIndex].RdOff = 0u; - _SEGGER_RTT.aUp[BufferIndex].WrOff = 0u; - } - _SEGGER_RTT.aUp[BufferIndex].Flags = Flags; - SEGGER_RTT_UNLOCK(); - r = 0; - } else { - r = -1; - } - return r; -} - -/********************************************************************* -* -* SEGGER_RTT_ConfigDownBuffer -* -* Function description -* Run-time configuration of a specific down-buffer (H->T). -* Buffer to be configured is specified by index. -* This includes: Buffer address, size, name, flags, ... -* -* Parameters -* BufferIndex Index of the buffer to configure. -* sName Pointer to a constant name string. -* pBuffer Pointer to a buffer to be used. -* BufferSize Size of the buffer. -* Flags Operating modes. Define behavior if buffer is full (not enough space for entire message). -* -* Return value -* >= 0 O.K. -* < 0 Error -* -* Additional information -* Buffer 0 is configured on compile-time. -* May only be called once per buffer. -* Buffer name and flags can be reconfigured using the appropriate functions. -*/ -int SEGGER_RTT_ConfigDownBuffer(unsigned BufferIndex, const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags) { - int r; - - INIT(); - if (BufferIndex < (unsigned)_SEGGER_RTT.MaxNumDownBuffers) { - SEGGER_RTT_LOCK(); - if (BufferIndex > 0u) { - _SEGGER_RTT.aDown[BufferIndex].sName = sName; - _SEGGER_RTT.aDown[BufferIndex].pBuffer = (char*)pBuffer; - _SEGGER_RTT.aDown[BufferIndex].SizeOfBuffer = BufferSize; - _SEGGER_RTT.aDown[BufferIndex].RdOff = 0u; - _SEGGER_RTT.aDown[BufferIndex].WrOff = 0u; - } - _SEGGER_RTT.aDown[BufferIndex].Flags = Flags; - SEGGER_RTT_UNLOCK(); - r = 0; - } else { - r = -1; - } - return r; -} - -/********************************************************************* -* -* SEGGER_RTT_SetNameUpBuffer -* -* Function description -* Run-time configuration of a specific up-buffer name (T->H). -* Buffer to be configured is specified by index. -* -* Parameters -* BufferIndex Index of the buffer to renamed. -* sName Pointer to a constant name string. -* -* Return value -* >= 0 O.K. -* < 0 Error -*/ -int SEGGER_RTT_SetNameUpBuffer(unsigned BufferIndex, const char* sName) { - int r; - - INIT(); - if (BufferIndex < (unsigned)_SEGGER_RTT.MaxNumUpBuffers) { - SEGGER_RTT_LOCK(); - _SEGGER_RTT.aUp[BufferIndex].sName = sName; - SEGGER_RTT_UNLOCK(); - r = 0; - } else { - r = -1; - } - return r; -} - -/********************************************************************* -* -* SEGGER_RTT_SetNameDownBuffer -* -* Function description -* Run-time configuration of a specific Down-buffer name (T->H). -* Buffer to be configured is specified by index. -* -* Parameters -* BufferIndex Index of the buffer to renamed. -* sName Pointer to a constant name string. -* -* Return value -* >= 0 O.K. -* < 0 Error -*/ -int SEGGER_RTT_SetNameDownBuffer(unsigned BufferIndex, const char* sName) { - int r; - - INIT(); - if (BufferIndex < (unsigned)_SEGGER_RTT.MaxNumDownBuffers) { - SEGGER_RTT_LOCK(); - _SEGGER_RTT.aDown[BufferIndex].sName = sName; - SEGGER_RTT_UNLOCK(); - r = 0; - } else { - r = -1; - } - return r; -} - -/********************************************************************* -* -* SEGGER_RTT_SetFlagsUpBuffer -* -* Function description -* Run-time configuration of specific up-buffer flags (T->H). -* Buffer to be configured is specified by index. -* -* Parameters -* BufferIndex Index of the buffer. -* Flags Flags to set for the buffer. -* -* Return value -* >= 0 O.K. -* < 0 Error -*/ -int SEGGER_RTT_SetFlagsUpBuffer(unsigned BufferIndex, unsigned Flags) { - int r; - - INIT(); - if (BufferIndex < (unsigned)_SEGGER_RTT.MaxNumUpBuffers) { - SEGGER_RTT_LOCK(); - _SEGGER_RTT.aUp[BufferIndex].Flags = Flags; - SEGGER_RTT_UNLOCK(); - r = 0; - } else { - r = -1; - } - return r; -} - -/********************************************************************* -* -* SEGGER_RTT_SetFlagsDownBuffer -* -* Function description -* Run-time configuration of specific Down-buffer flags (T->H). -* Buffer to be configured is specified by index. -* -* Parameters -* BufferIndex Index of the buffer to renamed. -* Flags Flags to set for the buffer. -* -* Return value -* >= 0 O.K. -* < 0 Error -*/ -int SEGGER_RTT_SetFlagsDownBuffer(unsigned BufferIndex, unsigned Flags) { - int r; - - INIT(); - if (BufferIndex < (unsigned)_SEGGER_RTT.MaxNumDownBuffers) { - SEGGER_RTT_LOCK(); - _SEGGER_RTT.aDown[BufferIndex].Flags = Flags; - SEGGER_RTT_UNLOCK(); - r = 0; - } else { - r = -1; - } - return r; -} - -/********************************************************************* -* -* SEGGER_RTT_Init -* -* Function description -* Initializes the RTT Control Block. -* Should be used in RAM targets, at start of the application. -* -*/ -void SEGGER_RTT_Init (void) { - _DoInit(); -} - -/********************************************************************* -* -* SEGGER_RTT_SetTerminal -* -* Function description -* Sets the terminal to be used for output on channel 0. -* -* Parameters -* TerminalId Index of the terminal. -* -* Return value -* >= 0 O.K. -* < 0 Error (e.g. if RTT is configured for non-blocking mode and there was no space in the buffer to set the new terminal Id) -*/ -int SEGGER_RTT_SetTerminal (unsigned char TerminalId) { - unsigned char ac[2]; - SEGGER_RTT_BUFFER_UP* pRing; - unsigned Avail; - int r; - // - INIT(); - // - r = 0; - ac[0] = 0xFFu; - if (TerminalId < sizeof(_aTerminalId)) { // We only support a certain number of channels - ac[1] = _aTerminalId[TerminalId]; - pRing = &_SEGGER_RTT.aUp[0]; // Buffer 0 is always reserved for terminal I/O, so we can use index 0 here, fixed - SEGGER_RTT_LOCK(); // Lock to make sure that no other task is writing into buffer, while we are and number of free bytes in buffer does not change downwards after checking and before writing - if ((pRing->Flags & SEGGER_RTT_MODE_MASK) == SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL) { - _ActiveTerminal = TerminalId; - _WriteBlocking(pRing, (const char*)ac, 2u); - } else { // Skipping mode or trim mode? => We cannot trim this command so handling is the same for both modes - Avail = _GetAvailWriteSpace(pRing); - if (Avail >= 2) { - _ActiveTerminal = TerminalId; // Only change active terminal in case of success - _WriteNoCheck(pRing, (const char*)ac, 2u); - } else { - r = -1; - } - } - SEGGER_RTT_UNLOCK(); - } else { - r = -1; - } - return r; -} - -/********************************************************************* -* -* SEGGER_RTT_TerminalOut -* -* Function description -* Writes a string to the given terminal -* without changing the terminal for channel 0. -* -* Parameters -* TerminalId Index of the terminal. -* s String to be printed on the terminal. -* -* Return value -* >= 0 - Number of bytes written. -* < 0 - Error. -* -*/ -int SEGGER_RTT_TerminalOut (unsigned char TerminalId, const char* s) { - int Status; - unsigned FragLen; - unsigned Avail; - SEGGER_RTT_BUFFER_UP* pRing; - // - INIT(); - // - // Validate terminal ID. - // - if (TerminalId < (char)sizeof(_aTerminalId)) { // We only support a certain number of channels - // - // Get "to-host" ring buffer. - // - pRing = &_SEGGER_RTT.aUp[0]; - // - // Need to be able to change terminal, write data, change back. - // Compute the fixed and variable sizes. - // - FragLen = STRLEN(s); - // - // How we output depends upon the mode... - // - SEGGER_RTT_LOCK(); - Avail = _GetAvailWriteSpace(pRing); - switch (pRing->Flags & SEGGER_RTT_MODE_MASK) { - case SEGGER_RTT_MODE_NO_BLOCK_SKIP: - // - // If we are in skip mode and there is no space for the whole - // of this output, don't bother switching terminals at all. - // - if (Avail < (FragLen + 4u)) { - Status = 0; - } else { - _PostTerminalSwitch(pRing, TerminalId); - Status = (int)_WriteBlocking(pRing, s, FragLen); - _PostTerminalSwitch(pRing, _ActiveTerminal); - } - break; - case SEGGER_RTT_MODE_NO_BLOCK_TRIM: - // - // If we are in trim mode and there is not enough space for everything, - // trim the output but always include the terminal switch. If no room - // for terminal switch, skip that totally. - // - if (Avail < 4u) { - Status = -1; - } else { - _PostTerminalSwitch(pRing, TerminalId); - Status = (int)_WriteBlocking(pRing, s, (FragLen < (Avail - 4u)) ? FragLen : (Avail - 4u)); - _PostTerminalSwitch(pRing, _ActiveTerminal); - } - break; - case SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL: - // - // If we are in blocking mode, output everything. - // - _PostTerminalSwitch(pRing, TerminalId); - Status = (int)_WriteBlocking(pRing, s, FragLen); - _PostTerminalSwitch(pRing, _ActiveTerminal); - break; - default: - Status = -1; - break; - } - // - // Finish up. - // - SEGGER_RTT_UNLOCK(); - } else { - Status = -1; - } - return Status; -} - -/********************************************************************* -* -* SEGGER_RTT_GetAvailWriteSpace -* -* Function description -* Returns the number of bytes available in the ring buffer. -* -* Parameters -* BufferIndex Index of the up buffer. -* -* Return value -* Number of bytes that are free in the selected up buffer. -*/ -unsigned SEGGER_RTT_GetAvailWriteSpace (unsigned BufferIndex){ - return _GetAvailWriteSpace(&_SEGGER_RTT.aUp[BufferIndex]); -} - - -/********************************************************************* -* -* SEGGER_RTT_GetBytesInBuffer() -* -* Function description -* Returns the number of bytes currently used in the up buffer. -* -* Parameters -* BufferIndex Index of the up buffer. -* -* Return value -* Number of bytes that are used in the buffer. -*/ -unsigned SEGGER_RTT_GetBytesInBuffer(unsigned BufferIndex) { - unsigned RdOff; - unsigned WrOff; - unsigned r; - // - // Avoid warnings regarding volatile access order. It's not a problem - // in this case, but dampen compiler enthusiasm. - // - RdOff = _SEGGER_RTT.aUp[BufferIndex].RdOff; - WrOff = _SEGGER_RTT.aUp[BufferIndex].WrOff; - if (RdOff <= WrOff) { - r = WrOff - RdOff; - } else { - r = _SEGGER_RTT.aUp[BufferIndex].SizeOfBuffer - (WrOff - RdOff); - } - return r; -} - -/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/src/SEGGER_RTT_ASM_ARMv7M.S b/lib/segger_rtt/src/SEGGER_RTT_ASM_ARMv7M.S deleted file mode 100644 index 0c0eae1c2..000000000 --- a/lib/segger_rtt/src/SEGGER_RTT_ASM_ARMv7M.S +++ /dev/null @@ -1,235 +0,0 @@ -/********************************************************************* -* (c) SEGGER Microcontroller GmbH * -* The Embedded Experts * -* www.segger.com * -********************************************************************** - --------------------------- END-OF-HEADER ----------------------------- - -File : SEGGER_RTT_ASM_ARMv7M.S -Purpose : Assembler implementation of RTT functions for ARMv7M - -Additional information: - This module is written to be assembler-independent and works with - GCC and clang (Embedded Studio) and IAR. -*/ - -#define SEGGER_RTT_ASM // Used to control processed input from header file -#include "SEGGER_RTT.h" - -/********************************************************************* -* -* Defines, fixed -* -********************************************************************** -*/ -#define _CCIAR 0 -#define _CCCLANG 1 - -#if (defined __SES_ARM) || (defined __GNUC__) || (defined __clang__) - #define _CC_TYPE _CCCLANG - #define _PUB_SYM .global - #define _EXT_SYM .extern - #define _END .end - #define _WEAK .weak - #define _THUMB_FUNC .thumb_func - #define _THUMB_CODE .code 16 - #define _WORD .word - #define _SECTION(Sect, Type, AlignExp) .section Sect ##, "ax" - #define _ALIGN(Exp) .align Exp - #define _PLACE_LITS .ltorg - #define _DATA_SECT_START - #define _C_STARTUP _start - #define _STACK_END __stack_end__ - #define _RAMFUNC - // - // .text => Link to flash - // .fast => Link to RAM - // OtherSect => Usually link to RAM - // Alignment is 2^x - // -#elif defined (__IASMARM__) - #define _CC_TYPE _CCIAR - #define _PUB_SYM PUBLIC - #define _EXT_SYM EXTERN - #define _END END - #define _WEAK _WEAK - #define _THUMB_FUNC - #define _THUMB_CODE THUMB - #define _WORD DCD - #define _SECTION(Sect, Type, AlignExp) SECTION Sect ## : ## Type ## :REORDER:NOROOT ## (AlignExp) - #define _ALIGN(Exp) alignrom Exp - #define _PLACE_LITS - #define _DATA_SECT_START DATA - #define _C_STARTUP __iar_program_start - #define _STACK_END sfe(CSTACK) - #define _RAMFUNC SECTION_TYPE SHT_PROGBITS, SHF_WRITE | SHF_EXECINSTR - // - // .text => Link to flash - // .textrw => Link to RAM - // OtherSect => Usually link to RAM - // NOROOT => Allows linker to throw away the function, if not referenced - // Alignment is 2^x - // -#endif - -#if (_CC_TYPE == _CCIAR) - NAME SEGGER_RTT_ASM_ARMv7M -#else - .syntax unified -#endif - -#if defined (RTT_USE_ASM) && (RTT_USE_ASM == 1) - #define SHT_PROGBITS 0x1 - -/********************************************************************* -* -* Public / external symbols -* -********************************************************************** -*/ - - _EXT_SYM __aeabi_memcpy - _EXT_SYM __aeabi_memcpy4 - _EXT_SYM _SEGGER_RTT - - _PUB_SYM SEGGER_RTT_ASM_WriteSkipNoLock - -/********************************************************************* -* -* SEGGER_RTT_WriteSkipNoLock -* -* Function description -* Stores a specified number of characters in SEGGER RTT -* control block which is then read by the host. -* SEGGER_RTT_WriteSkipNoLock does not lock the application and -* skips all data, if the data does not fit into the buffer. -* -* Parameters -* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). -* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. -* NumBytes Number of bytes to be stored in the SEGGER RTT control block. -* MUST be > 0!!! -* This is done for performance reasons, so no initial check has do be done. -* -* Return value -* 1: Data has been copied -* 0: No space, data has not been copied -* -* Notes -* (1) If there is not enough space in the "Up"-buffer, all data is dropped. -* (2) For performance reasons this function does not call Init() -* and may only be called after RTT has been initialized. -* Either by calling SEGGER_RTT_Init() or calling another RTT API function first. -*/ - _SECTION(.text, CODE, 2) - _ALIGN(2) - _THUMB_FUNC -SEGGER_RTT_ASM_WriteSkipNoLock: // unsigned SEGGER_RTT_WriteSkipNoLock(unsigned BufferIndex, const void* pData, unsigned NumBytes) { - // - // Cases: - // 1) RdOff <= WrOff => Space until wrap-around is sufficient - // 2) RdOff <= WrOff => Space after wrap-around needed (copy in 2 chunks) - // 3) RdOff < WrOff => No space in buf - // 4) RdOff > WrOff => Space is sufficient - // 5) RdOff > WrOff => No space in buf - // - // 1) is the most common case for large buffers and assuming that J-Link reads the data fast enough - // - // Register usage: - // R0 Temporary needed as RdOff, register later on - // R1 pData - // R2 - // R3 register. Hold free for subroutine calls - // R4 - // R5 pRing->pBuffer - // R6 pRing (Points to active struct SEGGER_RTT_BUFFER_DOWN) - // R7 WrOff - // - PUSH {R4-R7} - ADD R3,R0,R0, LSL #+1 - LDR.W R0,=_SEGGER_RTT // pRing = &_SEGGER_RTT.aUp[BufferIndex]; - ADD R0,R0,R3, LSL #+3 - ADD R6,R0,#+24 - LDR R0,[R6, #+16] // RdOff = pRing->RdOff; - LDR R7,[R6, #+12] // WrOff = pRing->WrOff; - LDR R5,[R6, #+4] // pRing->pBuffer - CMP R7,R0 - BCC.N _CheckCase4 // if (RdOff <= WrOff) { => Case 1), 2) or 3) - // - // Handling for case 1, later on identical to case 4 - // - LDR R3,[R6, #+8] // Avail = pRing->SizeOfBuffer - WrOff - 1u; => Space until wrap-around (assume 1 byte not usable for case that RdOff == 0) - SUBS R4,R3,R7 // (Used in case we jump into case 2 afterwards) - SUBS R3,R4,#+1 // - CMP R3,R2 - BCC.N _CheckCase2 // if (Avail >= NumBytes) { => Case 1)? -_Case4: - ADDS R5,R7,R5 // pBuffer += WrOff - ADDS R0,R2,R7 // v = WrOff + NumBytes - // - // 2x unrolling for the copy loop that is used most of the time - // This is a special optimization for small SystemView packets and makes them even faster - // - _ALIGN(2) -_LoopCopyStraight: // memcpy(pRing->pBuffer + WrOff, pData, NumBytes); - LDRB R3,[R1], #+1 - STRB R3,[R5], #+1 // *pDest++ = *pSrc++ - SUBS R2,R2,#+1 - BEQ _CSDone - LDRB R3,[R1], #+1 - STRB R3,[R5], #+1 // *pDest++ = *pSrc++ - SUBS R2,R2,#+1 - BNE _LoopCopyStraight -_CSDone: - STR R0,[R6, #+12] // pRing->WrOff = WrOff + NumBytes; - MOVS R0,#+1 - POP {R4-R7} - BX LR // Return 1 -_CheckCase2: - ADDS R0,R0,R3 // Avail += RdOff; => Space incl. wrap-around - CMP R0,R2 - BCC.N _Case3 // if (Avail >= NumBytes) { => Case 2? => If not, we have case 3) (does not fit) - // - // Handling for case 2 - // - ADDS R0,R7,R5 // v = pRing->pBuffer + WrOff => Do not change pRing->pBuffer here because 2nd chunk needs org. value - SUBS R2,R2,R4 // NumBytes -= Rem; (Rem = pRing->SizeOfBuffer - WrOff; => Space until end of buffer) -_LoopCopyBeforeWrapAround: // memcpy(pRing->pBuffer + WrOff, pData, Rem); => Copy 1st chunk - LDRB R3,[R1], #+1 - STRB R3,[R0], #+1 // *pDest++ = *pSrc++ - SUBS R4,R4,#+1 - BNE _LoopCopyBeforeWrapAround - // - // Special case: First check that assumed RdOff == 0 calculated that last element before wrap-around could not be used - // But 2nd check (considering space until wrap-around and until RdOff) revealed that RdOff is not 0, so we can use the last element - // In this case, we may use a copy straight until buffer end anyway without needing to copy 2 chunks - // Therefore, check if 2nd memcpy is necessary at all - // - ADDS R4,R2,#+0 // Save (needed as counter in loop but must be written to after the loop). Also use this inst to update the flags to skip 2nd loop if possible - BEQ.N _No2ChunkNeeded // if (NumBytes) { -_LoopCopyAfterWrapAround: // memcpy(pRing->pBuffer, pData + Rem, NumBytes); - LDRB R3,[R1], #+1 // pData already points to the next src byte due to copy loop increment before this loop - STRB R3,[R5], #+1 // *pDest++ = *pSrc++ - SUBS R2,R2,#+1 - BNE _LoopCopyAfterWrapAround -_No2ChunkNeeded: - STR R4,[R6, #+12] // pRing->WrOff = NumBytes; => Must be written after copying data because J-Link may read control block asynchronously while writing into buffer - MOVS R0,#+1 - POP {R4-R7} - BX LR // Return 1 -_CheckCase4: - SUBS R0,R0,R7 - SUBS R0,R0,#+1 // Avail = RdOff - WrOff - 1u; - CMP R0,R2 - BCS.N _Case4 // if (Avail >= NumBytes) { => Case 4) == 1) ? => If not, we have case 5) == 3) (does not fit) -_Case3: - MOVS R0,#+0 - POP {R4-R7} - BX LR // Return 0 - _PLACE_LITS - -#endif // defined (RTT_USE_ASM) && (RTT_USE_ASM == 1) - _END - -/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/src/SEGGER_RTT_printf.c b/lib/segger_rtt/src/SEGGER_RTT_printf.c deleted file mode 100644 index 6a83cd91b..000000000 --- a/lib/segger_rtt/src/SEGGER_RTT_printf.c +++ /dev/null @@ -1,500 +0,0 @@ -/********************************************************************* -* SEGGER Microcontroller GmbH * -* The Embedded Experts * -********************************************************************** -* * -* (c) 1995 - 2019 SEGGER Microcontroller GmbH * -* * -* www.segger.com Support: support@segger.com * -* * -********************************************************************** -* * -* SEGGER RTT * Real Time Transfer for embedded targets * -* * -********************************************************************** -* * -* All rights reserved. * -* * -* SEGGER strongly recommends to not make any changes * -* to or modify the source code of this software in order to stay * -* compatible with the RTT protocol and J-Link. * -* * -* Redistribution and use in source and binary forms, with or * -* without modification, are permitted provided that the following * -* condition is met: * -* * -* o Redistributions of source code must retain the above copyright * -* notice, this condition and the following disclaimer. * -* * -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * -* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * -* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * -* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * -* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * -* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * -* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * -* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * -* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * -* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * -* DAMAGE. * -* * -********************************************************************** ----------------------------END-OF-HEADER------------------------------ -File : SEGGER_RTT_printf.c -Purpose : Replacement for printf to write formatted data via RTT -Revision: $Rev: 17697 $ ----------------------------------------------------------------------- -*/ -#include "SEGGER_RTT.h" -#include "SEGGER_RTT_Conf.h" - -/********************************************************************* -* -* Defines, configurable -* -********************************************************************** -*/ - -#ifndef SEGGER_RTT_PRINTF_BUFFER_SIZE - #define SEGGER_RTT_PRINTF_BUFFER_SIZE (64) -#endif - -#include -#include - - -#define FORMAT_FLAG_LEFT_JUSTIFY (1u << 0) -#define FORMAT_FLAG_PAD_ZERO (1u << 1) -#define FORMAT_FLAG_PRINT_SIGN (1u << 2) -#define FORMAT_FLAG_ALTERNATE (1u << 3) - -/********************************************************************* -* -* Types -* -********************************************************************** -*/ - -typedef struct { - char* pBuffer; - unsigned BufferSize; - unsigned Cnt; - - int ReturnValue; - - unsigned RTTBufferIndex; -} SEGGER_RTT_PRINTF_DESC; - -/********************************************************************* -* -* Function prototypes -* -********************************************************************** -*/ - -/********************************************************************* -* -* Static code -* -********************************************************************** -*/ -/********************************************************************* -* -* _StoreChar -*/ -static void _StoreChar(SEGGER_RTT_PRINTF_DESC * p, char c) { - unsigned Cnt; - - Cnt = p->Cnt; - if ((Cnt + 1u) <= p->BufferSize) { - *(p->pBuffer + Cnt) = c; - p->Cnt = Cnt + 1u; - p->ReturnValue++; - } - // - // Write part of string, when the buffer is full - // - if (p->Cnt == p->BufferSize) { - if (SEGGER_RTT_Write(p->RTTBufferIndex, p->pBuffer, p->Cnt) != p->Cnt) { - p->ReturnValue = -1; - } else { - p->Cnt = 0u; - } - } -} - -/********************************************************************* -* -* _PrintUnsigned -*/ -static void _PrintUnsigned(SEGGER_RTT_PRINTF_DESC * pBufferDesc, unsigned v, unsigned Base, unsigned NumDigits, unsigned FieldWidth, unsigned FormatFlags) { - static const char _aV2C[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - unsigned Div; - unsigned Digit; - unsigned Number; - unsigned Width; - char c; - - Number = v; - Digit = 1u; - // - // Get actual field width - // - Width = 1u; - while (Number >= Base) { - Number = (Number / Base); - Width++; - } - if (NumDigits > Width) { - Width = NumDigits; - } - // - // Print leading chars if necessary - // - if ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == 0u) { - if (FieldWidth != 0u) { - if (((FormatFlags & FORMAT_FLAG_PAD_ZERO) == FORMAT_FLAG_PAD_ZERO) && (NumDigits == 0u)) { - c = '0'; - } else { - c = ' '; - } - while ((FieldWidth != 0u) && (Width < FieldWidth)) { - FieldWidth--; - _StoreChar(pBufferDesc, c); - if (pBufferDesc->ReturnValue < 0) { - break; - } - } - } - } - if (pBufferDesc->ReturnValue >= 0) { - // - // Compute Digit. - // Loop until Digit has the value of the highest digit required. - // Example: If the output is 345 (Base 10), loop 2 times until Digit is 100. - // - while (1) { - if (NumDigits > 1u) { // User specified a min number of digits to print? => Make sure we loop at least that often, before checking anything else (> 1 check avoids problems with NumDigits being signed / unsigned) - NumDigits--; - } else { - Div = v / Digit; - if (Div < Base) { // Is our divider big enough to extract the highest digit from value? => Done - break; - } - } - Digit *= Base; - } - // - // Output digits - // - do { - Div = v / Digit; - v -= Div * Digit; - _StoreChar(pBufferDesc, _aV2C[Div]); - if (pBufferDesc->ReturnValue < 0) { - break; - } - Digit /= Base; - } while (Digit); - // - // Print trailing spaces if necessary - // - if ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == FORMAT_FLAG_LEFT_JUSTIFY) { - if (FieldWidth != 0u) { - while ((FieldWidth != 0u) && (Width < FieldWidth)) { - FieldWidth--; - _StoreChar(pBufferDesc, ' '); - if (pBufferDesc->ReturnValue < 0) { - break; - } - } - } - } - } -} - -/********************************************************************* -* -* _PrintInt -*/ -static void _PrintInt(SEGGER_RTT_PRINTF_DESC * pBufferDesc, int v, unsigned Base, unsigned NumDigits, unsigned FieldWidth, unsigned FormatFlags) { - unsigned Width; - int Number; - - Number = (v < 0) ? -v : v; - - // - // Get actual field width - // - Width = 1u; - while (Number >= (int)Base) { - Number = (Number / (int)Base); - Width++; - } - if (NumDigits > Width) { - Width = NumDigits; - } - if ((FieldWidth > 0u) && ((v < 0) || ((FormatFlags & FORMAT_FLAG_PRINT_SIGN) == FORMAT_FLAG_PRINT_SIGN))) { - FieldWidth--; - } - - // - // Print leading spaces if necessary - // - if ((((FormatFlags & FORMAT_FLAG_PAD_ZERO) == 0u) || (NumDigits != 0u)) && ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == 0u)) { - if (FieldWidth != 0u) { - while ((FieldWidth != 0u) && (Width < FieldWidth)) { - FieldWidth--; - _StoreChar(pBufferDesc, ' '); - if (pBufferDesc->ReturnValue < 0) { - break; - } - } - } - } - // - // Print sign if necessary - // - if (pBufferDesc->ReturnValue >= 0) { - if (v < 0) { - v = -v; - _StoreChar(pBufferDesc, '-'); - } else if ((FormatFlags & FORMAT_FLAG_PRINT_SIGN) == FORMAT_FLAG_PRINT_SIGN) { - _StoreChar(pBufferDesc, '+'); - } else { - - } - if (pBufferDesc->ReturnValue >= 0) { - // - // Print leading zeros if necessary - // - if (((FormatFlags & FORMAT_FLAG_PAD_ZERO) == FORMAT_FLAG_PAD_ZERO) && ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == 0u) && (NumDigits == 0u)) { - if (FieldWidth != 0u) { - while ((FieldWidth != 0u) && (Width < FieldWidth)) { - FieldWidth--; - _StoreChar(pBufferDesc, '0'); - if (pBufferDesc->ReturnValue < 0) { - break; - } - } - } - } - if (pBufferDesc->ReturnValue >= 0) { - // - // Print number without sign - // - _PrintUnsigned(pBufferDesc, (unsigned)v, Base, NumDigits, FieldWidth, FormatFlags); - } - } - } -} - -/********************************************************************* -* -* Public code -* -********************************************************************** -*/ -/********************************************************************* -* -* SEGGER_RTT_vprintf -* -* Function description -* Stores a formatted string in SEGGER RTT control block. -* This data is read by the host. -* -* Parameters -* BufferIndex Index of "Up"-buffer to be used. (e.g. 0 for "Terminal") -* sFormat Pointer to format string -* pParamList Pointer to the list of arguments for the format string -* -* Return values -* >= 0: Number of bytes which have been stored in the "Up"-buffer. -* < 0: Error -*/ -int SEGGER_RTT_vprintf(unsigned BufferIndex, const char * sFormat, va_list * pParamList) { - char c; - SEGGER_RTT_PRINTF_DESC BufferDesc; - int v; - unsigned NumDigits; - unsigned FormatFlags; - unsigned FieldWidth; - char acBuffer[SEGGER_RTT_PRINTF_BUFFER_SIZE]; - - BufferDesc.pBuffer = acBuffer; - BufferDesc.BufferSize = SEGGER_RTT_PRINTF_BUFFER_SIZE; - BufferDesc.Cnt = 0u; - BufferDesc.RTTBufferIndex = BufferIndex; - BufferDesc.ReturnValue = 0; - - do { - c = *sFormat; - sFormat++; - if (c == 0u) { - break; - } - if (c == '%') { - // - // Filter out flags - // - FormatFlags = 0u; - v = 1; - do { - c = *sFormat; - switch (c) { - case '-': FormatFlags |= FORMAT_FLAG_LEFT_JUSTIFY; sFormat++; break; - case '0': FormatFlags |= FORMAT_FLAG_PAD_ZERO; sFormat++; break; - case '+': FormatFlags |= FORMAT_FLAG_PRINT_SIGN; sFormat++; break; - case '#': FormatFlags |= FORMAT_FLAG_ALTERNATE; sFormat++; break; - default: v = 0; break; - } - } while (v); - // - // filter out field with - // - FieldWidth = 0u; - do { - c = *sFormat; - if ((c < '0') || (c > '9')) { - break; - } - sFormat++; - FieldWidth = (FieldWidth * 10u) + ((unsigned)c - '0'); - } while (1); - - // - // Filter out precision (number of digits to display) - // - NumDigits = 0u; - c = *sFormat; - if (c == '.') { - sFormat++; - do { - c = *sFormat; - if ((c < '0') || (c > '9')) { - break; - } - sFormat++; - NumDigits = NumDigits * 10u + ((unsigned)c - '0'); - } while (1); - } - // - // Filter out length modifier - // - c = *sFormat; - do { - if ((c == 'l') || (c == 'h')) { - sFormat++; - c = *sFormat; - } else { - break; - } - } while (1); - // - // Handle specifiers - // - switch (c) { - case 'c': { - char c0; - v = va_arg(*pParamList, int); - c0 = (char)v; - _StoreChar(&BufferDesc, c0); - break; - } - case 'd': - v = va_arg(*pParamList, int); - _PrintInt(&BufferDesc, v, 10u, NumDigits, FieldWidth, FormatFlags); - break; - case 'u': - v = va_arg(*pParamList, int); - _PrintUnsigned(&BufferDesc, (unsigned)v, 10u, NumDigits, FieldWidth, FormatFlags); - break; - case 'x': - case 'X': - v = va_arg(*pParamList, int); - _PrintUnsigned(&BufferDesc, (unsigned)v, 16u, NumDigits, FieldWidth, FormatFlags); - break; - case 's': - { - const char * s = va_arg(*pParamList, const char *); - do { - c = *s; - s++; - if (c == '\0') { - break; - } - _StoreChar(&BufferDesc, c); - } while (BufferDesc.ReturnValue >= 0); - } - break; - case 'p': - v = va_arg(*pParamList, int); - _PrintUnsigned(&BufferDesc, (unsigned)v, 16u, 8u, 8u, 0u); - break; - case '%': - _StoreChar(&BufferDesc, '%'); - break; - default: - break; - } - sFormat++; - } else { - _StoreChar(&BufferDesc, c); - } - } while (BufferDesc.ReturnValue >= 0); - - if (BufferDesc.ReturnValue > 0) { - // - // Write remaining data, if any - // - if (BufferDesc.Cnt != 0u) { - SEGGER_RTT_Write(BufferIndex, acBuffer, BufferDesc.Cnt); - } - BufferDesc.ReturnValue += (int)BufferDesc.Cnt; - } - return BufferDesc.ReturnValue; -} - -/********************************************************************* -* -* SEGGER_RTT_printf -* -* Function description -* Stores a formatted string in SEGGER RTT control block. -* This data is read by the host. -* -* Parameters -* BufferIndex Index of "Up"-buffer to be used. (e.g. 0 for "Terminal") -* sFormat Pointer to format string, followed by the arguments for conversion -* -* Return values -* >= 0: Number of bytes which have been stored in the "Up"-buffer. -* < 0: Error -* -* Notes -* (1) Conversion specifications have following syntax: -* %[flags][FieldWidth][.Precision]ConversionSpecifier -* (2) Supported flags: -* -: Left justify within the field width -* +: Always print sign extension for signed conversions -* 0: Pad with 0 instead of spaces. Ignored when using '-'-flag or precision -* Supported conversion specifiers: -* c: Print the argument as one char -* d: Print the argument as a signed integer -* u: Print the argument as an unsigned integer -* x: Print the argument as an hexadecimal integer -* s: Print the string pointed to by the argument -* p: Print the argument as an 8-digit hexadecimal integer. (Argument shall be a pointer to void.) -*/ -int SEGGER_RTT_printf(unsigned BufferIndex, const char * sFormat, ...) { - int r; - va_list ParamList; - - va_start(ParamList, sFormat); - r = SEGGER_RTT_vprintf(BufferIndex, sFormat, &ParamList); - va_end(ParamList); - return r; -} -/*************************** End of file ****************************/ From a6475ce848119c485a767bb7990cfbdf203ee0ae Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 21 May 2020 21:17:38 -0700 Subject: [PATCH 034/131] experiment with CFG_DEBUG (it fails) --- platformio.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index b0fbd3752..115f530e6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -130,7 +130,8 @@ framework = arduino debug_tool = jlink build_type = debug ; I'm debugging with ICE a lot now build_flags = - ${env.build_flags} -Wno-unused-variable -Isrc/nrf52 + ${env.build_flags} -Wno-unused-variable -Isrc/nrf52 +;-DCFG_DEBUG=3 src_filter = ${env.src_filter} - lib_ignore = From 925e46da8ca645587febe7c1e15f49eeb82380cd Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 21 May 2020 21:17:53 -0700 Subject: [PATCH 035/131] make serial console work on nrf52 --- docs/software/nrf52-TODO.md | 4 +++- src/configuration.h | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 01fbefeb1..67c489fce 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -17,13 +17,13 @@ Minimum items needed to make sure hardware is good. Needed to be fully functional at least at the same level of the ESP32 boards. At this point users would probably want them. +- DONE get serial API working - get full BLE api working - make a file system implementation (preferably one that can see the files the bootloader also sees) - use https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.3.0/lib_fds_usage.html?cp=7_5_0_3_55_3 - make power management/sleep work properly - make a settimeofday implementation - DONE increase preamble length? - will break other clients? so all devices must update - DONE enable BLE DFU somehow -- set appversion/hwversion - report appversion/hwversion in BLE - use new LCD driver from screen.cpp. Still need to hook it to a subclass of (poorly named) OLEDDisplay, and override display() to stream bytes out to the screen. - we need to enable the external xtal for the sx1262 (on dio3) @@ -84,6 +84,8 @@ Nice ideas worth considering someday... - Currently using Nordic PCA10059 Dongle hardware - https://community.platformio.org/t/same-bootloader-same-softdevice-different-board-different-pins/11411/9 +- To make Segger JLink more reliable, turn off its fake filesystem. "JLinkExe MSDDisable" per https://learn.adafruit.com/circuitpython-on-the-nrf52/nrf52840-bootloader + ## Done - DONE add "DFU trigger library" to application load diff --git a/src/configuration.h b/src/configuration.h index 04f9d26a1..bc64d3187 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -252,6 +252,10 @@ along with this program. If not, see . #define SERIAL_BAUD 921600 // Serial debug baud rate +#include "SerialConsole.h" + +#define DEBUG_PORT console // Serial debug port + #ifdef NO_ESP32 #define USE_SEGGER #endif @@ -259,10 +263,6 @@ along with this program. If not, see . #include "SEGGER_RTT.h" #define DEBUG_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__) #else -#include "SerialConsole.h" - -#define DEBUG_PORT console // Serial debug port - #ifdef DEBUG_PORT #define DEBUG_MSG(...) DEBUG_PORT.printf(__VA_ARGS__) #else From 9f2646ba030cf1506ff9b1ba55981f5c447497b0 Mon Sep 17 00:00:00 2001 From: Nicolas Derive Date: Fri, 22 May 2020 12:53:54 +0200 Subject: [PATCH 036/131] writeflash is actually write_flash in esptool.py --- README.md | 2 +- bin/device-update.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5fa2c871d..fbf6ab42d 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ Hard resetting via RTS pin... 7. To update run `device-update.sh firmware-_board_-_country_.bin` - Example: `./device-update.sh firmware-HELTEC-US-0.0.3.bin`. -Note: If you have previously installed meshtastic, you don't need to run this full script instead just run "esptool.py --baud 921600 write*flash 0x10000 firmware-\_board*-_country_.bin". This will be faster, also all of your current preferences will be preserved. +Note: If you have previously installed meshtastic, you don't need to run this full script instead just run `esptool.py --baud 921600 write_flash 0x10000 firmware-_board_-_country_-_version_.bin`. This will be faster, also all of your current preferences will be preserved. You should see something like this: diff --git a/bin/device-update.sh b/bin/device-update.sh index b8a8dcf9b..ecfee105a 100755 --- a/bin/device-update.sh +++ b/bin/device-update.sh @@ -5,4 +5,4 @@ set -e FILENAME=$1 echo "Trying to update $FILENAME" -esptool.py --baud 921600 writeflash 0x10000 $FILENAME +esptool.py --baud 921600 write_flash 0x10000 $FILENAME From ae92567383b35982b8513f8929930d55c5960e40 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 22 May 2020 11:09:10 -0700 Subject: [PATCH 037/131] notes --- docs/software/nrf52-TODO.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 67c489fce..cb13227f6 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -4,8 +4,8 @@ Minimum items needed to make sure hardware is good. +- install a hardfault handler for null ptrs (if one isn't already installed) - test new bootloader on 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. Make sure SPI is atomic. @@ -50,7 +50,6 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At - use the new buttons in the UX - currently using soft device SD140, is that ideal? - turn on the watchdog timer, require servicing from key application threads -- install a hardfault handler for null ptrs (if one isn't already installed) - nrf52setup should call randomSeed(tbd) ## Things to do 'someday' From 608f8349d95aed49042503024f87010ac215a24b Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 22 May 2020 19:05:29 -0700 Subject: [PATCH 038/131] todo updates --- docs/software/nrf52-TODO.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 7b38f3a1c..048c99035 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -37,6 +37,7 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At ## Items to be 'feature complete' +- change packet numbers to be 32 bits - check datasheet about sx1262 temperature compensation - stop polling for GPS characters, instead stay blocked on read in a thread - use SX126x::startReceiveDutyCycleAuto to save power by sleeping and briefly waking to check for preamble bits. Change xmit rules to have more preamble bits. From cc47e29faca80bf60ce1ed2ad7cfe65b96177665 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 22 May 2020 19:06:08 -0700 Subject: [PATCH 039/131] released 0.6.4 already --- bin/version.sh | 2 +- proto | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/version.sh b/bin/version.sh index fc9ebf0f0..2887e511d 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=0.6.3 \ No newline at end of file +export VERSION=0.6.4 \ No newline at end of file diff --git a/proto b/proto index bc3ecd97e..2cb162a30 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit bc3ecd97e381b724c1a28acce0d12c688de73ba3 +Subproject commit 2cb162a3036b8513c743f05bd4f06aacb0b0680d From c9cb293bf25bca0fb9f3d2588dc28c05d2af045c Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 23 May 2020 09:24:22 -0700 Subject: [PATCH 040/131] cleanup virtual inheritence for Router/Reliable/Flooding/DSR --- src/mesh/DSRRouter.cpp | 14 ++++++--- src/mesh/FloodingRouter.cpp | 60 ++++++++++++++++++------------------- src/mesh/FloodingRouter.h | 14 +++++---- src/mesh/ReliableRouter.cpp | 30 +++++++++---------- src/mesh/ReliableRouter.h | 8 ++--- src/mesh/Router.cpp | 8 ++++- src/mesh/Router.h | 31 +++++++++++++++---- 7 files changed, 96 insertions(+), 69 deletions(-) diff --git a/src/mesh/DSRRouter.cpp b/src/mesh/DSRRouter.cpp index 00c2a80d3..60630b20d 100644 --- a/src/mesh/DSRRouter.cpp +++ b/src/mesh/DSRRouter.cpp @@ -48,7 +48,8 @@ void DSRRouter::sniffReceived(const MeshPacket *p) if (weAreInRoute(p->decoded.request)) { DEBUG_MSG("Ignoring a route request that contains us\n"); } else { - updateRoutes(p->decoded.request, false); // Update our routing tables based on the route that came in so far on this request + updateRoutes(p->decoded.request, + false); // Update our routing tables based on the route that came in so far on this request if (p->decoded.dest == getNodeNum()) { // They were looking for us, send back a route reply (the sender address will be first in the list) @@ -67,12 +68,17 @@ void DSRRouter::sniffReceived(const MeshPacket *p) } } + // Handle route reply packets + if (p->decoded.which_payload == SubPacket_reply_tag) { + updateRoutes(p->decoded.reply, true); + } + // Handle regular packets if (p->to == getNodeNum()) { // Destined for us (at least for this hop) - // We need to route this packet - if (p->decoded.dest != p->to) { - // FIXME + // We need to route this packet to some other node + if (p->decoded.dest && p->decoded.dest != p->to) { + // FIXME if we have a route out, resend the packet to the next hop, otherwise return a nak with no-route available } } diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index d3cc5cbde..0b7aea64a 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -17,39 +17,37 @@ ErrorCode FloodingRouter::send(MeshPacket *p) return Router::send(p); } -/** - * 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 - */ -void FloodingRouter::handleReceived(MeshPacket *p) +bool FloodingRouter::shouldFilterReceived(const MeshPacket *p) { if (wasSeenRecently(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 - - 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); + return true; } + + return Router::shouldFilterReceived(p); +} + +void FloodingRouter::sniffReceived(const MeshPacket *p) +{ + // 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 + + 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::sniffReceived(p); } diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index 48a8f0bc7..699508d23 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -46,11 +46,15 @@ class FloodingRouter : public Router, protected PacketHistory 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. + * Should this incoming filter be dropped? * - * Note: this method will free the provided packet + * Called immedately on receiption, before any further processing. + * @return true to abandon the packet */ - virtual void handleReceived(MeshPacket *p); + virtual bool shouldFilterReceived(const MeshPacket *p); + + /** + * Look for broadcasts we need to rebroadcast + */ + virtual void sniffReceived(const MeshPacket *p); }; diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 0500f2799..c8e45a604 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -36,7 +36,7 @@ ErrorCode ReliableRouter::send(MeshPacket *p) * * Otherwise, let superclass handle it. */ -void ReliableRouter::handleReceived(MeshPacket *p) +void ReliableRouter::sniffReceived(const MeshPacket *p) { NodeNum ourNode = getNodeNum(); @@ -55,28 +55,26 @@ void ReliableRouter::handleReceived(MeshPacket *p) sendAckNak(true, p->from, p->id); } - if (perhapsDecode(p)) { - // If the payload is valid, look for ack/nak + // 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; + 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, false)) { - 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); - } + // we are careful to only read/update wasSeenRecently _after_ confirming this is an ack (to not mess + // up broadcasts) + if ((ackId || nakId) && !wasSeenRecently(p, false)) { + 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); + FloodingRouter::sniffReceived(p); } /** diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h index 7030793ae..f8a238341 100644 --- a/src/mesh/ReliableRouter.h +++ b/src/mesh/ReliableRouter.h @@ -88,13 +88,9 @@ class ReliableRouter : public FloodingRouter 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 + * Look for acks/naks or someone retransmitting us */ - virtual void handleReceived(MeshPacket *p); + virtual void sniffReceived(const MeshPacket *p); private: /** diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 0ef7b8333..0b63247e9 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -40,7 +40,7 @@ void Router::loop() { MeshPacket *mp; while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) { - handleReceived(mp); + perhapsHandleReceived(mp); } } @@ -191,6 +191,12 @@ void Router::handleReceived(MeshPacket *p) notifyPacketReceived.notifyObservers(p); } } +} + +void Router::perhapsHandleReceived(MeshPacket *p) +{ + if (!shouldFilterReceived(p)) + handleReceived(p); packetPool.release(p); } \ No newline at end of file diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 8c811667e..45f0b762c 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -67,14 +67,12 @@ class Router virtual ErrorCode send(MeshPacket *p); /** - * 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. + * Should this incoming filter be dropped? * - * Note: this packet will never be called for messages sent/generated by this node. - * Note: this method will free the provided packet. + * Called immedately on receiption, before any further processing. + * @return true to abandon the packet */ - virtual void handleReceived(MeshPacket *p); + virtual bool shouldFilterReceived(const MeshPacket *p) { return false; } /** * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to @@ -88,6 +86,27 @@ class Router * @return true for success, false for corrupt packet. */ bool perhapsDecode(MeshPacket *p); + + private: + /** + * 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 packet will never be called for messages sent/generated by this node. + * Note: this method will free the provided packet. + */ + void perhapsHandleReceived(MeshPacket *p); + + /** + * Called from perhapsHandleReceived() - allows subclass message delivery behavior. + * 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 packet will never be called for messages sent/generated by this node. + * Note: this method will free the provided packet. + */ + void handleReceived(MeshPacket *p); }; extern Router &router; From 16812c3ee4e5acdfd0250c8cc8e0f0ef5a7d2c9a Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 23 May 2020 10:01:36 -0700 Subject: [PATCH 041/131] add ignore_incoming to user preferences, for automated testing of DSR topologies --- proto | 2 +- src/mesh/Router.cpp | 10 +++++++++- src/mesh/mesh-pb-constants.cpp | 9 +++++++++ src/mesh/mesh-pb-constants.h | 10 ++++++++++ src/mesh/mesh.pb.h | 16 ++++++++++------ 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/proto b/proto index 2cb162a30..e95239a2e 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 2cb162a3036b8513c743f05bd4f06aacb0b0680d +Subproject commit e95239a2ec9ac41ba77fac47ae531640bb0f8078 diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 0b63247e9..fba0aee4b 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -195,7 +195,15 @@ void Router::handleReceived(MeshPacket *p) void Router::perhapsHandleReceived(MeshPacket *p) { - if (!shouldFilterReceived(p)) + assert(radioConfig.has_preferences); + bool inIgnore = is_in_repeated(radioConfig.preferences.ignore_incoming, p->from); + + if (inIgnore) + DEBUG_MSG("Ignoring incoming message, 0x%x is in our ignore list\n", p->from); + + // Note: we avoid calling shouldFilterReceived if we are supposed to ignore certain nodes - because some overrides might + // cache/learn of the existence of nodes (i.e. FloodRouter) that they should not + if (!inIgnore && !shouldFilterReceived(p)) handleReceived(p); packetPool.release(p); diff --git a/src/mesh/mesh-pb-constants.cpp b/src/mesh/mesh-pb-constants.cpp index 7e3800112..894afe479 100644 --- a/src/mesh/mesh-pb-constants.cpp +++ b/src/mesh/mesh-pb-constants.cpp @@ -64,3 +64,12 @@ bool writecb(pb_ostream_t *stream, const uint8_t *buf, size_t count) return false; #endif } + +bool is_in_helper(uint32_t n, const uint32_t *array, pb_size_t count) +{ + for (int i = 0; i < count; i++) + if (array[i] == n) + return true; + + return false; +} \ No newline at end of file diff --git a/src/mesh/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h index 661c1388a..baa9f5b55 100644 --- a/src/mesh/mesh-pb-constants.h +++ b/src/mesh/mesh-pb-constants.h @@ -25,3 +25,13 @@ bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count); /// Write to an arduino file bool writecb(pb_ostream_t *stream, const uint8_t *buf, size_t count); + +/** is_in_repeated is a macro/function that returns true if a specified word appears in a repeated protobuf array. + * It relies on the following naming conventions from nanopb: + * + * pb_size_t ignore_incoming_count; + * uint32_t ignore_incoming[3]; + */ +bool is_in_helper(uint32_t n, const uint32_t *array, pb_size_t count); + +#define is_in_repeated(name, n) is_in_helper(n, name, name##_count) \ No newline at end of file diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index 1c2ecff80..3a3241613 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -91,6 +91,8 @@ typedef struct _RadioConfig_UserPreferences { uint32_t min_wake_secs; bool keep_all_packets; bool promiscuous_mode; + pb_size_t ignore_incoming_count; + uint32_t ignore_incoming[3]; } RadioConfig_UserPreferences; typedef struct _RouteDiscovery { @@ -220,7 +222,7 @@ typedef struct _ToRadio { #define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0, 0} #define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""} #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 RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 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, 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} @@ -236,7 +238,7 @@ typedef struct _ToRadio { #define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0, 0} #define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""} #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 RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 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, 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} @@ -284,6 +286,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 RadioConfig_UserPreferences_ignore_incoming_tag 102 #define RouteDiscovery_route_tag 2 #define User_id_tag 1 #define User_long_name_tag 2 @@ -424,7 +427,8 @@ X(a, STATIC, SINGULAR, UINT32, sds_secs, 9) \ X(a, STATIC, SINGULAR, UINT32, ls_secs, 10) \ X(a, STATIC, SINGULAR, UINT32, min_wake_secs, 11) \ X(a, STATIC, SINGULAR, BOOL, keep_all_packets, 100) \ -X(a, STATIC, SINGULAR, BOOL, promiscuous_mode, 101) +X(a, STATIC, SINGULAR, BOOL, promiscuous_mode, 101) \ +X(a, STATIC, REPEATED, UINT32, ignore_incoming, 102) #define RadioConfig_UserPreferences_CALLBACK NULL #define RadioConfig_UserPreferences_DEFAULT NULL @@ -553,11 +557,11 @@ extern const pb_msgdesc_t ManufacturingData_msg; #define SubPacket_size 273 #define MeshPacket_size 312 #define ChannelSettings_size 60 -#define RadioConfig_size 136 -#define RadioConfig_UserPreferences_size 72 +#define RadioConfig_size 157 +#define RadioConfig_UserPreferences_size 93 #define NodeInfo_size 132 #define MyNodeInfo_size 80 -#define DeviceState_size 15016 +#define DeviceState_size 15037 #define DebugString_size 258 #define FromRadio_size 321 #define ToRadio_size 315 From e89fe2f7d9cbd1845a9b7ecc8c2cd72d3733509f Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 23 May 2020 12:50:33 -0700 Subject: [PATCH 042/131] DSR WIP --- src/mesh/DSRRouter.cpp | 11 +++++++++++ src/mesh/DSRRouter.h | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/src/mesh/DSRRouter.cpp b/src/mesh/DSRRouter.cpp index 60630b20d..99ef1a656 100644 --- a/src/mesh/DSRRouter.cpp +++ b/src/mesh/DSRRouter.cpp @@ -73,6 +73,15 @@ void DSRRouter::sniffReceived(const MeshPacket *p) updateRoutes(p->decoded.reply, true); } + // Learn 0 hop routes by just hearing any adjacent nodes + // But treat broadcasts carefully, because when flood broadcasts go out they keep the same original "from". So we want to + // ignore rebroadcasts. + if (p->to != NODENUM_BROADCAST || p->hop_limit != HOP_RELIABLE) { + setRoute(p->from, p->from, 0); // We are adjacent with zero hops + } + + // FIXME - handle any naks we receive (either because they are passing by us or someone naked a message we sent) + // Handle regular packets if (p->to == getNodeNum()) { // Destined for us (at least for this hop) @@ -80,6 +89,8 @@ void DSRRouter::sniffReceived(const MeshPacket *p) if (p->decoded.dest && p->decoded.dest != p->to) { // FIXME if we have a route out, resend the packet to the next hop, otherwise return a nak with no-route available } + + // FIXME - handle naks from our adjacent nodes - convert them to route error packets } return ReliableRouter::sniffReceived(p); diff --git a/src/mesh/DSRRouter.h b/src/mesh/DSRRouter.h index 5ecbdc8e5..62ec9c672 100644 --- a/src/mesh/DSRRouter.h +++ b/src/mesh/DSRRouter.h @@ -36,4 +36,11 @@ class DSRRouter : public ReliableRouter /** Not in our route cache, rebroadcast on their behalf (after adding ourselves to the request route) */ void resendRouteRequest(const MeshPacket *p); + + /** + * Record that forwarder can reach dest for us, but they will need numHops to get there. + * If our routing tables already have something that can reach that node in fewer hops we will keep the existing route + * instead. + */ + void setRoute(NodeNum dest, NodeNum forwarder, uint8_t numHops); }; \ No newline at end of file From fb3b62f8f0b85ea57e922770bdf410b8ba4a70c9 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 23 May 2020 15:48:23 -0700 Subject: [PATCH 043/131] CSR WIP --- platformio.ini | 2 +- proto | 2 +- src/mesh/DSRRouter.cpp | 49 ++++++++++++++++++++++++++++--------- src/mesh/DSRRouter.h | 17 ++++++++++++- src/mesh/ReliableRouter.cpp | 25 +++++++++++++------ src/mesh/ReliableRouter.h | 6 +++++ src/mesh/Router.cpp | 4 +++ src/mesh/mesh.pb.c | 1 + src/mesh/mesh.pb.h | 48 ++++++++++++++++++++++++------------ 9 files changed, 117 insertions(+), 37 deletions(-) diff --git a/platformio.ini b/platformio.ini index 28d8b63e8..761bf298d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = nrf52dk ; Note: the github actions CI test build can't yet build NRF52 targets +default_envs = tbeam ; Note: the github actions CI test build can't yet build NRF52 targets [common] ; common is not currently used diff --git a/proto b/proto index e95239a2e..f713e0c03 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit e95239a2ec9ac41ba77fac47ae531640bb0f8078 +Subproject commit f713e0c039140a1f2189274c9c28f219943e88de diff --git a/src/mesh/DSRRouter.cpp b/src/mesh/DSRRouter.cpp index 99ef1a656..d59fc36e2 100644 --- a/src/mesh/DSRRouter.cpp +++ b/src/mesh/DSRRouter.cpp @@ -42,24 +42,24 @@ void DSRRouter::sniffReceived(const MeshPacket *p) // FIXME, update nodedb // Handle route discovery packets (will be a broadcast message) - if (p->decoded.which_payload == SubPacket_request_tag) { + if (p->decoded.which_payload == SubPacket_route_request_tag) { // FIXME - always start request with the senders nodenum - if (weAreInRoute(p->decoded.request)) { + if (weAreInRoute(p->decoded.route_request)) { DEBUG_MSG("Ignoring a route request that contains us\n"); } else { - updateRoutes(p->decoded.request, + updateRoutes(p->decoded.route_request, false); // Update our routing tables based on the route that came in so far on this request if (p->decoded.dest == getNodeNum()) { // They were looking for us, send back a route reply (the sender address will be first in the list) - sendRouteReply(p->decoded.request); + sendRouteReply(p->decoded.route_request); } else { // They were looking for someone else, forward it along (as a zero hop broadcast) NodeNum nextHop = getNextHop(p->decoded.dest); if (nextHop) { // in our route cache, reply to the requester (the sender address will be first in the list) - sendRouteReply(p->decoded.request, nextHop); + sendRouteReply(p->decoded.route_request, nextHop); } else { // Not in our route cache, rebroadcast on their behalf (after adding ourselves to the request route) resendRouteRequest(p); @@ -69,28 +69,55 @@ void DSRRouter::sniffReceived(const MeshPacket *p) } // Handle route reply packets - if (p->decoded.which_payload == SubPacket_reply_tag) { - updateRoutes(p->decoded.reply, true); + if (p->decoded.which_payload == SubPacket_route_reply_tag) { + updateRoutes(p->decoded.route_reply, true); + } + + // Handle route error packets + if (p->decoded.which_payload == SubPacket_route_error_tag) { + // FIXME } // Learn 0 hop routes by just hearing any adjacent nodes // But treat broadcasts carefully, because when flood broadcasts go out they keep the same original "from". So we want to // ignore rebroadcasts. if (p->to != NODENUM_BROADCAST || p->hop_limit != HOP_RELIABLE) { - setRoute(p->from, p->from, 0); // We are adjacent with zero hops + addRoute(p->from, p->from, 0); // We are adjacent with zero hops } - // FIXME - handle any naks we receive (either because they are passing by us or someone naked a message we sent) + // We simply ignore ACKs - because ReliableRouter will delete the pending packet for us // Handle regular packets if (p->to == getNodeNum()) { // Destined for us (at least for this hop) // We need to route this packet to some other node if (p->decoded.dest && p->decoded.dest != p->to) { - // FIXME if we have a route out, resend the packet to the next hop, otherwise return a nak with no-route available + // FIXME if we have a route out, resend the packet to the next hop, otherwise return RouteError no-route available + + NodeNum nextHop = getNextHop(p->decoded.dest); + if (nextHop) { + sendNextHop(nextHop, p); // start a reliable single hop send + } else { + // We don't have a route out + assert(p->decoded.source); // I think this is guaranteed by now + + sendRouteError(p, RouteError_NO_ROUTE); + } + + // FIXME, stop local processing of this packet } - // FIXME - handle naks from our adjacent nodes - convert them to route error packets + // handle naks - convert them to route error packets + // All naks are generated locally, because we failed resending the packet too many times + PacketId nakId = p->decoded.which_ack == SubPacket_fail_id_tag ? p->decoded.ack.fail_id : 0; + if (nakId) { + auto pending = findPendingPacket(p->to, nakId); + if (pending && pending->packet->decoded.source) { // if source not set, this was not a multihop packet, just ignore + removeRoute(pending->packet->decoded.dest, p->to); // We no longer have a route to the specified node + + sendRouteError(p, RouteError_GOT_NAK); + } + } } return ReliableRouter::sniffReceived(p); diff --git a/src/mesh/DSRRouter.h b/src/mesh/DSRRouter.h index 62ec9c672..a6004260a 100644 --- a/src/mesh/DSRRouter.h +++ b/src/mesh/DSRRouter.h @@ -42,5 +42,20 @@ class DSRRouter : public ReliableRouter * If our routing tables already have something that can reach that node in fewer hops we will keep the existing route * instead. */ - void setRoute(NodeNum dest, NodeNum forwarder, uint8_t numHops); + void addRoute(NodeNum dest, NodeNum forwarder, uint8_t numHops); + + /** + * Record that the specified forwarder no longer has a route to the dest + */ + void removeRoute(NodeNum dest, NodeNum forwarder); + + /** + * Forward the specified packet to the specified node + */ + void sendNextHop(NodeNum n, const MeshPacket *p); + + /** + * Send a route error packet towards whoever originally sent this message + */ + void sendRouteError(const MeshPacket *p, RouteError err); }; \ No newline at end of file diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index c8e45a604..7056e36eb 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -50,7 +50,8 @@ void ReliableRouter::sniffReceived(const MeshPacket *p) DEBUG_MSG("Someone is retransmitting for us, generate implicit ack\n"); sendAckNak(true, p->from, p->id); } - } else if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (for now) + } else if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability + // - not DSR routing) if (p->want_ack) { sendAckNak(true, p->from, p->id); } @@ -60,8 +61,7 @@ void ReliableRouter::sniffReceived(const MeshPacket *p) 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) + // we are careful to only read wasSeenRecently - not update it (to not mess up broadcasts) if ((ackId || nakId) && !wasSeenRecently(p, false)) { if (ackId) { DEBUG_MSG("Received a ack=%d, stopping retransmissions\n", ackId); @@ -107,6 +107,14 @@ PendingPacket::PendingPacket(MeshPacket *p) setNextTx(); } +PendingPacket *ReliableRouter::findPendingPacket(GlobalPacketId key) +{ + auto old = pending.find(key); // If we have an old record, someone messed up because id got reused + if (old != pending.end()) { + return &old->second; + } else + return NULL; +} /** * Stop any retransmissions we are doing of the specified node/packet ID pair */ @@ -118,15 +126,16 @@ bool ReliableRouter::stopRetransmission(NodeNum from, PacketId id) bool ReliableRouter::stopRetransmission(GlobalPacketId key) { - auto old = pending.find(key); // If we have an old record, someone messed up because id got reused - if (old != pending.end()) { + auto old = findPendingPacket(key); + if (old) { auto numErased = pending.erase(key); assert(numErased == 1); - packetPool.release(old->second.packet); + packetPool.release(old->packet); return true; } else return false; } + /** * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. */ @@ -158,7 +167,9 @@ void ReliableRouter::doRetransmissions() DEBUG_MSG("Reliable send failed, returning a nak fr=0x%x,to=0x%x,id=%d\n", p.packet->from, p.packet->to, p.packet->id); sendAckNak(false, p.packet->from, p.packet->id); - stopRetransmission(it->first); + // Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived - which + // allows the DSR version to still be able to look at the PendingPacket + // stopRetransmission(it->first); } else { DEBUG_MSG("Sending reliable retransmission fr=0x%x,to=0x%x,id=%d, tries left=%d\n", p.packet->from, p.packet->to, p.packet->id, p.numRetransmissions); diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h index f8a238341..b96176864 100644 --- a/src/mesh/ReliableRouter.h +++ b/src/mesh/ReliableRouter.h @@ -92,6 +92,12 @@ class ReliableRouter : public FloodingRouter */ virtual void sniffReceived(const MeshPacket *p); + /** + * Try to find the pending packet record for this ID (or NULL if not found) + */ + PendingPacket *findPendingPacket(NodeNum from, PacketId id) { return findPendingPacket(GlobalPacketId(from, id)); } + PendingPacket *findPendingPacket(GlobalPacketId p); + private: /** * Send an ack or a nak packet back towards whoever sent idFrom diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index fba0aee4b..bf92f3f1e 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -96,6 +96,10 @@ ErrorCode Router::send(MeshPacket *p) { assert(p->to != nodeDB.getNodeNum()); // should have already been handled by sendLocal + PacketId nakId = p->decoded.which_ack == SubPacket_fail_id_tag ? p->decoded.ack.fail_id : 0; + assert( + !nakId); // I don't think we ever send 0hop naks over the wire (other than to the phone), test that assumption with assert + // Never set the want_ack flag on broadcast packets sent over the air. if (p->to == NODENUM_BROADCAST) p->want_ack = false; diff --git a/src/mesh/mesh.pb.c b/src/mesh/mesh.pb.c index 321576556..4eb53e1ba 100644 --- a/src/mesh/mesh.pb.c +++ b/src/mesh/mesh.pb.c @@ -58,3 +58,4 @@ PB_BIND(ManufacturingData, ManufacturingData, AUTO) + diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index 3a3241613..087af997e 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -14,6 +14,12 @@ extern "C" { #endif /* Enum definitions */ +typedef enum _RouteError { + RouteError_NONE = 0, + RouteError_NO_ROUTE = 1, + RouteError_GOT_NAK = 2 +} RouteError; + typedef enum _Constants { Constants_Unused = 0 } Constants; @@ -130,8 +136,9 @@ typedef struct _SubPacket { Position position; Data data; User user; - RouteDiscovery request; - RouteDiscovery reply; + RouteDiscovery route_request; + RouteDiscovery route_reply; + RouteError route_error; }; bool want_response; uint32_t dest; @@ -140,6 +147,7 @@ typedef struct _SubPacket { uint32_t success_id; uint32_t fail_id; } ack; + uint32_t source; } SubPacket; typedef PB_BYTES_ARRAY_T(256) MeshPacket_encrypted_t; @@ -200,6 +208,10 @@ typedef struct _ToRadio { /* Helper constants for enums */ +#define _RouteError_MIN RouteError_NONE +#define _RouteError_MAX RouteError_GOT_NAK +#define _RouteError_ARRAYSIZE ((RouteError)(RouteError_GOT_NAK+1)) + #define _Constants_MIN Constants_Unused #define _Constants_MAX Constants_Unused #define _Constants_ARRAYSIZE ((Constants)(Constants_Unused+1)) @@ -218,7 +230,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 {0, {Position_init_default}, 0, 0, 0, {0}} +#define SubPacket_init_default {0, {Position_init_default}, 0, 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} @@ -234,7 +246,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 {0, {Position_init_zero}, 0, 0, 0, {0}} +#define SubPacket_init_zero {0, {Position_init_zero}, 0, 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} @@ -302,12 +314,14 @@ typedef struct _ToRadio { #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_route_request_tag 6 +#define SubPacket_route_reply_tag 7 +#define SubPacket_route_error_tag 13 #define SubPacket_success_id_tag 10 #define SubPacket_fail_id_tag 11 #define SubPacket_want_response_tag 5 #define SubPacket_dest_tag 9 +#define SubPacket_source_tag 12 #define MeshPacket_decoded_tag 3 #define MeshPacket_encrypted_tag 8 #define MeshPacket_from_tag 1 @@ -370,19 +384,21 @@ X(a, STATIC, REPEATED, INT32, route, 2) 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, ONEOF, MESSAGE, (payload,route_request,route_request), 6) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,route_reply,route_reply), 7) \ +X(a, STATIC, ONEOF, ENUM, (payload,route_error,route_error), 13) \ X(a, STATIC, SINGULAR, BOOL, want_response, 5) \ 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) +X(a, STATIC, ONEOF, UINT32, (ack,fail_id,ack.fail_id), 11) \ +X(a, STATIC, SINGULAR, UINT32, source, 12) #define SubPacket_CALLBACK NULL #define SubPacket_DEFAULT NULL #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 SubPacket_payload_route_request_MSGTYPE RouteDiscovery +#define SubPacket_payload_route_reply_MSGTYPE RouteDiscovery #define MeshPacket_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, from, 1) \ @@ -554,17 +570,17 @@ extern const pb_msgdesc_t ManufacturingData_msg; #define Data_size 256 #define User_size 72 #define RouteDiscovery_size 88 -#define SubPacket_size 273 -#define MeshPacket_size 312 +#define SubPacket_size 279 +#define MeshPacket_size 318 #define ChannelSettings_size 60 #define RadioConfig_size 157 #define RadioConfig_UserPreferences_size 93 #define NodeInfo_size 132 #define MyNodeInfo_size 80 -#define DeviceState_size 15037 +#define DeviceState_size 15235 #define DebugString_size 258 -#define FromRadio_size 321 -#define ToRadio_size 315 +#define FromRadio_size 327 +#define ToRadio_size 321 /* ManufacturingData_size depends on runtime parameters */ #ifdef __cplusplus From 5bd3e4bcd08389574390e456c88924fb16cec08c Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 23 May 2020 17:39:08 -0700 Subject: [PATCH 044/131] DSR WIP --- proto | 2 +- src/mesh/DSRRouter.cpp | 57 +++++++++++++++++++++++++----------------- src/mesh/DSRRouter.h | 19 +++++++++++--- src/mesh/Router.cpp | 1 + src/mesh/mesh.pb.h | 24 ++++++++++-------- 5 files changed, 66 insertions(+), 37 deletions(-) diff --git a/proto b/proto index f713e0c03..adf4127fe 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit f713e0c039140a1f2189274c9c28f219943e88de +Subproject commit adf4127fe3e4140bfd97b48a2d11b53ee34a16c8 diff --git a/src/mesh/DSRRouter.cpp b/src/mesh/DSRRouter.cpp index d59fc36e2..cb22c5467 100644 --- a/src/mesh/DSRRouter.cpp +++ b/src/mesh/DSRRouter.cpp @@ -36,20 +36,32 @@ when we receive a routeError packet - fixme, eventually keep caches of possible other routes. */ +ErrorCode DSRRouter::send(MeshPacket *p) +{ + // If we have an entry in our routing tables, just send it, otherwise start a route discovery + + return ReliableRouter::send(p); +} + void DSRRouter::sniffReceived(const MeshPacket *p) { + // Learn 0 hop routes by just hearing any adjacent nodes + // But treat broadcasts carefully, because when flood broadcasts go out they keep the same original "from". So we want to + // ignore rebroadcasts. + // this will also add records for any ACKs we receive for our messages + if (p->to != NODENUM_BROADCAST || p->hop_limit != HOP_RELIABLE) { + addRoute(p->from, p->from, 0); // We are adjacent with zero hops + } - // FIXME, update nodedb - - // Handle route discovery packets (will be a broadcast message) - if (p->decoded.which_payload == SubPacket_route_request_tag) { + switch (p->decoded.which_payload) { + case SubPacket_route_request_tag: + // Handle route discovery packets (will be a broadcast message) // FIXME - always start request with the senders nodenum - if (weAreInRoute(p->decoded.route_request)) { DEBUG_MSG("Ignoring a route request that contains us\n"); } else { updateRoutes(p->decoded.route_request, - false); // Update our routing tables based on the route that came in so far on this request + true); // Update our routing tables based on the route that came in so far on this request if (p->decoded.dest == getNodeNum()) { // They were looking for us, send back a route reply (the sender address will be first in the list) @@ -66,23 +78,21 @@ void DSRRouter::sniffReceived(const MeshPacket *p) } } } - } + break; + case SubPacket_route_reply_tag: + updateRoutes(p->decoded.route_reply, false); - // Handle route reply packets - if (p->decoded.which_payload == SubPacket_route_reply_tag) { - updateRoutes(p->decoded.route_reply, true); - } + // FIXME, if any of our current pending packets were waiting for this route, send them (and leave them as regular pending + // packets until ack arrives) + // FIXME, if we don't get a route reply at all (or a route error), timeout and generate a routeerror TIMEOUT on our own... + break; + case SubPacket_route_error_tag: + removeRoute(p->decoded.dest); - // Handle route error packets - if (p->decoded.which_payload == SubPacket_route_error_tag) { - // FIXME - } - - // Learn 0 hop routes by just hearing any adjacent nodes - // But treat broadcasts carefully, because when flood broadcasts go out they keep the same original "from". So we want to - // ignore rebroadcasts. - if (p->to != NODENUM_BROADCAST || p->hop_limit != HOP_RELIABLE) { - addRoute(p->from, p->from, 0); // We are adjacent with zero hops + // FIXME: if any pending packets were waiting on this route, delete them + break; + default: + break; } // We simply ignore ACKs - because ReliableRouter will delete the pending packet for us @@ -92,7 +102,7 @@ void DSRRouter::sniffReceived(const MeshPacket *p) // We need to route this packet to some other node if (p->decoded.dest && p->decoded.dest != p->to) { - // FIXME if we have a route out, resend the packet to the next hop, otherwise return RouteError no-route available + // if we have a route out, resend the packet to the next hop, otherwise return RouteError no-route available NodeNum nextHop = getNextHop(p->decoded.dest); if (nextHop) { @@ -101,6 +111,7 @@ void DSRRouter::sniffReceived(const MeshPacket *p) // We don't have a route out assert(p->decoded.source); // I think this is guaranteed by now + // FIXME - what if the current packet _is_ a route error packet? sendRouteError(p, RouteError_NO_ROUTE); } @@ -113,7 +124,7 @@ void DSRRouter::sniffReceived(const MeshPacket *p) if (nakId) { auto pending = findPendingPacket(p->to, nakId); if (pending && pending->packet->decoded.source) { // if source not set, this was not a multihop packet, just ignore - removeRoute(pending->packet->decoded.dest, p->to); // We no longer have a route to the specified node + removeRoute(pending->packet->decoded.dest); // We no longer have a route to the specified node sendRouteError(p, RouteError_GOT_NAK); } diff --git a/src/mesh/DSRRouter.h b/src/mesh/DSRRouter.h index a6004260a..904f99980 100644 --- a/src/mesh/DSRRouter.h +++ b/src/mesh/DSRRouter.h @@ -10,6 +10,13 @@ class DSRRouter : public ReliableRouter */ virtual void sniffReceived(const MeshPacket *p); + /** + * 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); + private: /** * Does our node appear in the specified route @@ -18,8 +25,12 @@ class DSRRouter : public ReliableRouter /** * Given a DSR route, use that route to update our DB of possible routes + * + * Note: routes are always listed in the same order - from sender to receipient (i.e. route_replies also use this some order) + * + * @param isRequest is true if we are looking at a route request, else we are looking at a reply **/ - void updateRoutes(const RouteDiscovery &route, bool reverse); + void updateRoutes(const RouteDiscovery &route, bool isRequest); /** * send back a route reply (the sender address will be first in the list) @@ -34,6 +45,8 @@ class DSRRouter : public ReliableRouter NodeNum getNextHop(NodeNum dest); /** Not in our route cache, rebroadcast on their behalf (after adding ourselves to the request route) + * + * We will bump down hop_limit in this call. */ void resendRouteRequest(const MeshPacket *p); @@ -45,9 +58,9 @@ class DSRRouter : public ReliableRouter void addRoute(NodeNum dest, NodeNum forwarder, uint8_t numHops); /** - * Record that the specified forwarder no longer has a route to the dest + * Record that we no longer have a route to the dest */ - void removeRoute(NodeNum dest, NodeNum forwarder); + void removeRoute(NodeNum dest); /** * Forward the specified packet to the specified node diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index bf92f3f1e..1c8d1763e 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -141,6 +141,7 @@ ErrorCode Router::send(MeshPacket *p) void Router::sniffReceived(const MeshPacket *p) { DEBUG_MSG("FIXME-update-db Sniffing packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + // FIXME, update nodedb } bool Router::perhapsDecode(MeshPacket *p) diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index 087af997e..fe9cd575c 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -17,7 +17,8 @@ extern "C" { typedef enum _RouteError { RouteError_NONE = 0, RouteError_NO_ROUTE = 1, - RouteError_GOT_NAK = 2 + RouteError_GOT_NAK = 2, + RouteError_TIMEOUT = 3 } RouteError; typedef enum _Constants { @@ -140,6 +141,7 @@ typedef struct _SubPacket { RouteDiscovery route_reply; RouteError route_error; }; + uint32_t original_id; bool want_response; uint32_t dest; pb_size_t which_ack; @@ -209,8 +211,8 @@ typedef struct _ToRadio { /* Helper constants for enums */ #define _RouteError_MIN RouteError_NONE -#define _RouteError_MAX RouteError_GOT_NAK -#define _RouteError_ARRAYSIZE ((RouteError)(RouteError_GOT_NAK+1)) +#define _RouteError_MAX RouteError_TIMEOUT +#define _RouteError_ARRAYSIZE ((RouteError)(RouteError_TIMEOUT+1)) #define _Constants_MIN Constants_Unused #define _Constants_MAX Constants_Unused @@ -230,7 +232,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 {0, {Position_init_default}, 0, 0, 0, {0}, 0} +#define SubPacket_init_default {0, {Position_init_default}, 0, 0, 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} @@ -246,7 +248,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 {0, {Position_init_zero}, 0, 0, 0, {0}, 0} +#define SubPacket_init_zero {0, {Position_init_zero}, 0, 0, 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} @@ -322,6 +324,7 @@ typedef struct _ToRadio { #define SubPacket_want_response_tag 5 #define SubPacket_dest_tag 9 #define SubPacket_source_tag 12 +#define SubPacket_original_id_tag 2 #define MeshPacket_decoded_tag 3 #define MeshPacket_encrypted_tag 8 #define MeshPacket_from_tag 1 @@ -387,6 +390,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,user,user), 4) \ X(a, STATIC, ONEOF, MESSAGE, (payload,route_request,route_request), 6) \ X(a, STATIC, ONEOF, MESSAGE, (payload,route_reply,route_reply), 7) \ X(a, STATIC, ONEOF, ENUM, (payload,route_error,route_error), 13) \ +X(a, STATIC, SINGULAR, UINT32, original_id, 2) \ X(a, STATIC, SINGULAR, BOOL, want_response, 5) \ X(a, STATIC, SINGULAR, UINT32, dest, 9) \ X(a, STATIC, ONEOF, UINT32, (ack,success_id,ack.success_id), 10) \ @@ -570,17 +574,17 @@ extern const pb_msgdesc_t ManufacturingData_msg; #define Data_size 256 #define User_size 72 #define RouteDiscovery_size 88 -#define SubPacket_size 279 -#define MeshPacket_size 318 +#define SubPacket_size 285 +#define MeshPacket_size 324 #define ChannelSettings_size 60 #define RadioConfig_size 157 #define RadioConfig_UserPreferences_size 93 #define NodeInfo_size 132 #define MyNodeInfo_size 80 -#define DeviceState_size 15235 +#define DeviceState_size 15433 #define DebugString_size 258 -#define FromRadio_size 327 -#define ToRadio_size 321 +#define FromRadio_size 333 +#define ToRadio_size 327 /* ManufacturingData_size depends on runtime parameters */ #ifdef __cplusplus From 8f1b26bdda0fcbaedb8b04ae2135ea4d5489f170 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 24 May 2020 12:58:36 -0700 Subject: [PATCH 045/131] DSR wip still kinda busted (rx packets not working - even for regular router) --- docs/software/mesh-alg.md | 5 +++-- src/mesh/Router.cpp | 8 +++++--- src/mesh/mesh-pb-constants.cpp | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index d4ae71215..dc4203aba 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -23,8 +23,9 @@ reliable messaging tasks (stage one for DSR): dsr tasks -- Don't use broadcasts for the network pings (close open github issue) -- add ignoreSenders to radioconfig to allow testing different mesh topologies by refusing to see certain senders +- oops I might have broken message reception +- DONE Don't use broadcasts for the network pings (close open github issue) +- DONE 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/Router.cpp b/src/mesh/Router.cpp index 1c8d1763e..4a7fac5f7 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -201,14 +201,16 @@ void Router::handleReceived(MeshPacket *p) void Router::perhapsHandleReceived(MeshPacket *p) { assert(radioConfig.has_preferences); - bool inIgnore = is_in_repeated(radioConfig.preferences.ignore_incoming, p->from); + bool ignore = is_in_repeated(radioConfig.preferences.ignore_incoming, p->from); - if (inIgnore) + if (ignore) DEBUG_MSG("Ignoring incoming message, 0x%x is in our ignore list\n", p->from); + else if (ignore |= shouldFilterReceived(p)) + DEBUG_MSG("Incoming message was filtered 0x%x\n", p->from); // Note: we avoid calling shouldFilterReceived if we are supposed to ignore certain nodes - because some overrides might // cache/learn of the existence of nodes (i.e. FloodRouter) that they should not - if (!inIgnore && !shouldFilterReceived(p)) + if (!ignore) handleReceived(p); packetPool.release(p); diff --git a/src/mesh/mesh-pb-constants.cpp b/src/mesh/mesh-pb-constants.cpp index 894afe479..864317851 100644 --- a/src/mesh/mesh-pb-constants.cpp +++ b/src/mesh/mesh-pb-constants.cpp @@ -67,7 +67,7 @@ bool writecb(pb_ostream_t *stream, const uint8_t *buf, size_t count) bool is_in_helper(uint32_t n, const uint32_t *array, pb_size_t count) { - for (int i = 0; i < count; i++) + for (pb_size_t i = 0; i < count; i++) if (array[i] == n) return true; From e8f6504ec42514d3c4d1b7882891f89578d618b2 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 24 May 2020 14:15:49 -0700 Subject: [PATCH 046/131] Make an accelerated NRF52 implementation for AEX256-CTR crypto --- .gitmodules | 3 ++ .vscode/settings.json | 1 + docs/software/nrf52-TODO.md | 3 +- platformio.ini | 7 ++-- sdk-nrfxlib | 1 + src/esp32/ESP32CryptoEngine.cpp | 2 +- src/mesh/CryptoEngine.h | 2 + src/nrf52/NRF52CryptoEngine.cpp | 68 ++++++++++++++++++++++++++++++++- 8 files changed, 80 insertions(+), 7 deletions(-) create mode 160000 sdk-nrfxlib diff --git a/.gitmodules b/.gitmodules index ddc929e14..785c573a4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "proto"] path = proto url = https://github.com/meshtastic/Meshtastic-protobufs.git +[submodule "sdk-nrfxlib"] + path = sdk-nrfxlib + url = https://github.com/nrfconnect/sdk-nrfxlib.git diff --git a/.vscode/settings.json b/.vscode/settings.json index ebed64343..62c6cdf0f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -55,6 +55,7 @@ "NEMAGPS", "Ublox", "descs", + "ocrypto", "protobufs" ] } \ No newline at end of file diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 048c99035..47df1ef5b 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -6,6 +6,7 @@ Minimum items needed to make sure hardware is good. +- find out why we reboot while debugging - install a hardfault handler for null ptrs (if one isn't already installed) - test my hackedup bootloader on the real hardware - Use the PMU driver on real hardware @@ -20,7 +21,7 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At - DONE get serial API working - get full BLE api working -- make a file system implementation (preferably one that can see the files the bootloader also sees) - use https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.3.0/lib_fds_usage.html?cp=7_5_0_3_55_3 +- make a file system implementation (preferably one that can see the files the bootloader also sees) - preferably https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/libraries/InternalFileSytem/examples/Internal_ReadWrite/Internal_ReadWrite.ino else use https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.3.0/lib_fds_usage.html?cp=7_5_0_3_55_3 - make power management/sleep work properly - make a settimeofday implementation - DONE increase preamble length? - will break other clients? so all devices must update diff --git a/platformio.ini b/platformio.ini index 761bf298d..cff9006df 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = tbeam ; Note: the github actions CI test build can't yet build NRF52 targets +default_envs = nrf52dk ; Note: the github actions CI test build can't yet build NRF52 targets [common] ; common is not currently used @@ -84,7 +84,7 @@ src_filter = upload_speed = 921600 debug_init_break = tbreak setup build_flags = - ${env.build_flags} -Wall -Wextra -Isrc/esp32 + ${env.build_flags} -Wall -Wextra -Isrc/esp32 lib_ignore = segger_rtt ; The 1.0 release of the TBEAM board @@ -129,8 +129,9 @@ platform = nordicnrf52 framework = arduino debug_tool = jlink build_type = debug ; I'm debugging with ICE a lot now +; note: liboberon provides the AES256 implementation for NRF52 (though not using the hardware acceleration of the NRF52840 - FIXME) build_flags = - ${env.build_flags} -Wno-unused-variable -Isrc/nrf52 + ${env.build_flags} -Wno-unused-variable -Isrc/nrf52 -Isdk-nrfxlib/crypto/nrf_oberon/include -Lsdk-nrfxlib/crypto/nrf_oberon/lib/cortex-m4/hard-float/ -lliboberon_3.0.3 ;-DCFG_DEBUG=3 src_filter = ${env.src_filter} - diff --git a/sdk-nrfxlib b/sdk-nrfxlib new file mode 160000 index 000000000..17e845355 --- /dev/null +++ b/sdk-nrfxlib @@ -0,0 +1 @@ +Subproject commit 17e8453553d4cfc21ab87c53c9627f0cf1216429 diff --git a/src/esp32/ESP32CryptoEngine.cpp b/src/esp32/ESP32CryptoEngine.cpp index bccfae557..613d5cc17 100644 --- a/src/esp32/ESP32CryptoEngine.cpp +++ b/src/esp32/ESP32CryptoEngine.cpp @@ -11,7 +11,7 @@ #include "crypto/aes_wrap.h" #include "mbedtls/aes.h" -#define MAX_BLOCKSIZE 256 + class ESP32CryptoEngine : public CryptoEngine { diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h index 04e592e2c..b97abed55 100644 --- a/src/mesh/CryptoEngine.h +++ b/src/mesh/CryptoEngine.h @@ -7,6 +7,8 @@ * */ +#define MAX_BLOCKSIZE 256 + class CryptoEngine { protected: diff --git a/src/nrf52/NRF52CryptoEngine.cpp b/src/nrf52/NRF52CryptoEngine.cpp index ee1650ea2..2bf16f23f 100644 --- a/src/nrf52/NRF52CryptoEngine.cpp +++ b/src/nrf52/NRF52CryptoEngine.cpp @@ -1,5 +1,69 @@ #include "CryptoEngine.h" +#include "configuration.h" +#include "ocrypto_aes_ctr.h" -// FIXME, do a NRF52 version -CryptoEngine *crypto = new CryptoEngine(); \ No newline at end of file +class NRF52CryptoEngine : public CryptoEngine +{ + + /// How many bytes in our key + uint8_t keySize = 0; + const uint8_t *keyBytes; + + public: + NRF52CryptoEngine() {} + + ~NRF52CryptoEngine() {} + + /** + * Set the key used for encrypt, decrypt. + * + * As a special case: If all bytes are zero, we assume _no encryption_ and send all data in cleartext. + * + * @param numBytes must be 16 (AES128), 32 (AES256) or 0 (no crypt) + * @param bytes a _static_ buffer that will remain valid for the life of this crypto instance (i.e. this class will cache the + * provided pointer) + */ + virtual void setKey(size_t numBytes, uint8_t *bytes) + { + keySize = numBytes; + keyBytes = bytes; + } + + /** + * Encrypt a packet + * + * @param bytes is updated in place + */ + virtual void encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes) + { + // DEBUG_MSG("NRF52 encrypt!\n"); + + if (keySize != 0) { + ocrypto_aes_ctr_ctx ctx; + + initNonce(fromNode, packetNum); + ocrypto_aes_ctr_init(&ctx, keyBytes, keySize, nonce); + + ocrypto_aes_ctr_encrypt(&ctx, bytes, bytes, numBytes); + } + } + + virtual void decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes) + { + // DEBUG_MSG("NRF52 decrypt!\n"); + + if (keySize != 0) { + ocrypto_aes_ctr_ctx ctx; + + initNonce(fromNode, packetNum); + ocrypto_aes_ctr_init(&ctx, keyBytes, keySize, nonce); + + ocrypto_aes_ctr_decrypt(&ctx, bytes, bytes, numBytes); + } + } + + private: +}; + +CryptoEngine *crypto = new NRF52CryptoEngine(); From cda7487cbe676e5d896ddf183a04f41e899f3f7a Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 24 May 2020 16:08:58 -0700 Subject: [PATCH 047/131] add a NRF52 hardfault handler --- .vscode/settings.json | 3 ++ docs/software/nrf52-TODO.md | 2 +- src/nrf52/hardfault.cpp | 78 +++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/nrf52/hardfault.cpp diff --git a/.vscode/settings.json b/.vscode/settings.json index 62c6cdf0f..aebb3d4a5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -51,9 +51,12 @@ }, "cSpell.words": [ "Blox", + "HFSR", "Meshtastic", "NEMAGPS", "Ublox", + "bkpt", + "cfsr", "descs", "ocrypto", "protobufs" diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 47df1ef5b..e48320ae7 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -7,7 +7,7 @@ Minimum items needed to make sure hardware is good. - find out why we reboot while debugging -- install a hardfault handler for null ptrs (if one isn't already installed) +- DONE install a hardfault handler for null ptrs (if one isn't already installed) - test my hackedup bootloader on the real hardware - Use the PMU driver on real hardware - Use new radio driver on real hardware diff --git a/src/nrf52/hardfault.cpp b/src/nrf52/hardfault.cpp new file mode 100644 index 000000000..4cf4dff79 --- /dev/null +++ b/src/nrf52/hardfault.cpp @@ -0,0 +1,78 @@ +#include "configuration.h" +#include + +// Based on reading/modifying https://blog.feabhas.com/2013/02/developing-a-generic-hard-fault-handler-for-arm-cortex-m3cortex-m4/ + +enum { r0, r1, r2, r3, r12, lr, pc, psr }; + +// Per http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/Cihcfefj.html +static void printUsageErrorMsg(uint32_t cfsr) +{ + DEBUG_MSG("Usage fault: "); + cfsr >>= SCB_CFSR_USGFAULTSR_Pos; // right shift to lsb + if ((cfsr & (1 << 9)) != 0) + DEBUG_MSG("Divide by zero\n"); + if ((cfsr & (1 << 8)) != 0) + DEBUG_MSG("Unaligned\n"); +} + +static void printBusErrorMsg(uint32_t cfsr) +{ + DEBUG_MSG("Usage fault: "); + cfsr >>= SCB_CFSR_BUSFAULTSR_Pos; // right shift to lsb + if ((cfsr & (1 << 0)) != 0) + DEBUG_MSG("Instruction bus error\n"); + if ((cfsr & (1 << 1)) != 0) + DEBUG_MSG("Precise data bus error\n"); + if ((cfsr & (1 << 2)) != 0) + DEBUG_MSG("Imprecise data bus error\n"); +} + +static void printMemErrorMsg(uint32_t cfsr) +{ + DEBUG_MSG("Usage fault: "); + cfsr >>= SCB_CFSR_MEMFAULTSR_Pos; // right shift to lsb + if ((cfsr & (1 << 0)) != 0) + DEBUG_MSG("Instruction access violation\n"); + if ((cfsr & (1 << 1)) != 0) + DEBUG_MSG("Data access violation\n"); +} + +static void HardFault_Impl(uint32_t stack[]) +{ + DEBUG_MSG("In Hard Fault Handler\n"); + DEBUG_MSG("SCB->HFSR = 0x%08lx\n", SCB->HFSR); + + if ((SCB->HFSR & SCB_HFSR_FORCED_Msk) != 0) { + DEBUG_MSG("Forced Hard Fault\n"); + DEBUG_MSG("SCB->CFSR = 0x%08lx\n", SCB->CFSR); + + if ((SCB->CFSR & SCB_CFSR_USGFAULTSR_Msk) != 0) { + printUsageErrorMsg(SCB->CFSR); + } + if ((SCB->CFSR & SCB_CFSR_BUSFAULTSR_Msk) != 0) { + printBusErrorMsg(SCB->CFSR); + } + if ((SCB->CFSR & SCB_CFSR_MEMFAULTSR_Msk) != 0) { + printMemErrorMsg(SCB->CFSR); + } + + DEBUG_MSG("r0 = 0x%08lx\n", stack[r0]); + DEBUG_MSG("r1 = 0x%08lx\n", stack[r1]); + DEBUG_MSG("r2 = 0x%08lx\n", stack[r2]); + DEBUG_MSG("r3 = 0x%08lx\n", stack[r3]); + DEBUG_MSG("r12 = 0x%08lx\n", stack[r12]); + DEBUG_MSG("lr = 0x%08lx\n", stack[lr]); + DEBUG_MSG("pc = 0x%08lx\n", stack[pc]); + DEBUG_MSG("psr = 0x%08lx\n", stack[psr]); + asm volatile("bkpt #01"); + while (1) + ; + } +} + +void HardFault_Handler(void) +{ + asm volatile(" mrs r0,msp\n" + " b HardFault_Impl \n"); +} From 66b11bcbd79b04a1b32914dca6368c6bb5c80239 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 24 May 2020 16:20:21 -0700 Subject: [PATCH 048/131] print RF52 reset reason --- src/nrf52/main-nrf52.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/nrf52/main-nrf52.cpp b/src/nrf52/main-nrf52.cpp index a7bd02bcc..5c38f0f08 100644 --- a/src/nrf52/main-nrf52.cpp +++ b/src/nrf52/main-nrf52.cpp @@ -67,6 +67,10 @@ PmuBQ25703A pmu; void nrf52Setup() { + + auto why = NRF_POWER->RESETREAS; + DEBUG_MSG("Reset reason: 0x%x\n", why); + // Not yet on board // pmu.init(); DEBUG_MSG("FIXME, need to call randomSeed on nrf52!\n"); From 48de631e04920364a27a7341b9a79141b2e038d0 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 24 May 2020 16:34:18 -0700 Subject: [PATCH 049/131] disable activelyReceiving for sx1262 for now - it doesn't yet work --- docs/software/nrf52-TODO.md | 3 ++- src/mesh/SX1262Interface.cpp | 4 +++- src/nrf52/main-nrf52.cpp | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index e48320ae7..f29248c5e 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -6,7 +6,8 @@ Minimum items needed to make sure hardware is good. -- find out why we reboot while debugging +- fix activelyReceiving for sx1262 +- find out why we reboot while debugging - seems to be power? try using external supply - DONE install a hardfault handler for null ptrs (if one isn't already installed) - test my hackedup bootloader on the real hardware - Use the PMU driver on real hardware diff --git a/src/mesh/SX1262Interface.cpp b/src/mesh/SX1262Interface.cpp index ad305a4f1..bab1bf992 100644 --- a/src/mesh/SX1262Interface.cpp +++ b/src/mesh/SX1262Interface.cpp @@ -104,7 +104,9 @@ void SX1262Interface::startReceive() /** Could we send right now (i.e. either not actively receving or transmitting)? */ bool SX1262Interface::isActivelyReceiving() { - return lora.getPacketLength() > 0; + return false; // FIXME + // FIXME this is not correct - often always true - need to add an extra conditional + // return lora.getPacketLength() > 0; } bool SX1262Interface::sleep() diff --git a/src/nrf52/main-nrf52.cpp b/src/nrf52/main-nrf52.cpp index 5c38f0f08..e0d1ac5e6 100644 --- a/src/nrf52/main-nrf52.cpp +++ b/src/nrf52/main-nrf52.cpp @@ -69,6 +69,7 @@ void nrf52Setup() { auto why = NRF_POWER->RESETREAS; + // per https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fpower.html DEBUG_MSG("Reset reason: 0x%x\n", why); // Not yet on board From 2770cc7de360aacfab1e901f5e4719da3986c128 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 24 May 2020 19:23:50 -0700 Subject: [PATCH 050/131] Use the SX1262 receive duty cycle mode to get radio current draw down to about 2.5mA @ 3V while in receive mode. --- docs/software/nrf52-TODO.md | 9 ++++++--- src/mesh/Router.cpp | 5 +++-- src/mesh/SX1262Interface.cpp | 11 ++++++++++- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index f29248c5e..99477d761 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -6,7 +6,8 @@ Minimum items needed to make sure hardware is good. -- fix activelyReceiving for sx1262 +- write UC1701 wrapper +- scheduleOSCallback doesn't work yet - it is way too fast (causes rapid polling of busyTx, high power draw etc...) - find out why we reboot while debugging - seems to be power? try using external supply - DONE install a hardfault handler for null ptrs (if one isn't already installed) - test my hackedup bootloader on the real hardware @@ -35,14 +36,14 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At - make ble endpoints not require "start config", just have them start in config mode - measure power management and confirm battery life - use new PMU to provide battery voltage/% full to app (both bluetooth and screen) -- do initial power measurements +- do initial power measurements, measure effects of more preamble bits +- fix activelyReceiving for sx1262 ## Items to be 'feature complete' - change packet numbers to be 32 bits - check datasheet about sx1262 temperature compensation - stop polling for GPS characters, instead stay blocked on read in a thread -- 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 - 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. @@ -61,6 +62,7 @@ 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 +- enable monitor mode debuggin (need to use real jlink): https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/monitor-mode-debugging-with-j-link-and-gdbeclipse - 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 @@ -120,6 +122,7 @@ Nice ideas worth considering someday... #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. +- DONE use SX126x::startReceiveDutyCycleAuto to save power by sleeping and briefly waking to check for preamble bits. Change xmit rules to have more preamble bits. ``` diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 4a7fac5f7..5abf73cb5 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -205,8 +205,9 @@ void Router::perhapsHandleReceived(MeshPacket *p) if (ignore) DEBUG_MSG("Ignoring incoming message, 0x%x is in our ignore list\n", p->from); - else if (ignore |= shouldFilterReceived(p)) - DEBUG_MSG("Incoming message was filtered 0x%x\n", p->from); + else if (ignore |= shouldFilterReceived(p)) { + // DEBUG_MSG("Incoming message was filtered 0x%x\n", p->from); + } // Note: we avoid calling shouldFilterReceived if we are supposed to ignore certain nodes - because some overrides might // cache/learn of the existence of nodes (i.e. FloodRouter) that they should not diff --git a/src/mesh/SX1262Interface.cpp b/src/mesh/SX1262Interface.cpp index bab1bf992..84a969765 100644 --- a/src/mesh/SX1262Interface.cpp +++ b/src/mesh/SX1262Interface.cpp @@ -89,16 +89,25 @@ void SX1262Interface::addReceiveMetadata(MeshPacket *mp) mp->rx_snr = lora.getSNR(); } +// For power draw measurements, helpful to force radio to stay sleeping +// #define SLEEP_ONLY + void SX1262Interface::startReceive() { +#ifdef SLEEP_ONLY + sleep(); +#else setStandby(); - int err = lora.startReceive(); + // int err = lora.startReceive(); + int err = lora.startReceiveDutyCycleAuto(); // We use a 32 bit preamble so this should save some power by letting radio sit in + // standby mostly. assert(err == ERR_NONE); isReceiving = true; // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); +#endif } /** Could we send right now (i.e. either not actively receving or transmitting)? */ From 1656c8d0cb64e7afc6371504c2378d69f982ec14 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 25 May 2020 07:48:36 -0700 Subject: [PATCH 051/131] use my Timer class on all platforms, it works better than the freertos version --- docs/software/nrf52-TODO.md | 6 +++-- src/OSTimer.cpp | 12 ++++----- src/OSTimer.h | 10 -------- src/PeriodicTask.h | 9 ++++++- src/mesh/RadioLibInterface.cpp | 45 +++++++++++----------------------- src/mesh/RadioLibInterface.h | 11 +++++++-- src/mesh/SX1262Interface.cpp | 6 ++--- src/nrf52/main-nrf52.cpp | 5 +++- 8 files changed, 48 insertions(+), 56 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 99477d761..2e469f576 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -7,8 +7,6 @@ Minimum items needed to make sure hardware is good. - write UC1701 wrapper -- scheduleOSCallback doesn't work yet - it is way too fast (causes rapid polling of busyTx, high power draw etc...) -- find out why we reboot while debugging - seems to be power? try using external supply - DONE install a hardfault handler for null ptrs (if one isn't already installed) - test my hackedup bootloader on the real hardware - Use the PMU driver on real hardware @@ -43,6 +41,7 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At - change packet numbers to be 32 bits - check datasheet about sx1262 temperature compensation +- enable brownout detection and watchdog - stop polling for GPS characters, instead stay blocked on read in a thread - turn back on in-radio destaddr checking for RF95 - figure out what the correct current limit should be for the sx1262, currently we just use the default 100 @@ -63,6 +62,7 @@ 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 - enable monitor mode debuggin (need to use real jlink): https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/monitor-mode-debugging-with-j-link-and-gdbeclipse +- Improve efficiency of PeriodicTimer by only checking the next queued timer event, and carefully sorting based on schedule - 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 @@ -123,6 +123,8 @@ Nice ideas worth considering someday... - customize the bootloader to use proper button bindings - remove the MeshRadio wrapper - we don't need it anymore, just do everything in RadioInterface subclasses. - DONE use SX126x::startReceiveDutyCycleAuto to save power by sleeping and briefly waking to check for preamble bits. Change xmit rules to have more preamble bits. +- scheduleOSCallback doesn't work yet - it is way too fast (causes rapid polling of busyTx, high power draw etc...) +- find out why we reboot while debugging - it was bluetooth/softdevice ``` diff --git a/src/OSTimer.cpp b/src/OSTimer.cpp index 0da3b7d28..0978163ce 100644 --- a/src/OSTimer.cpp +++ b/src/OSTimer.cpp @@ -1,21 +1,21 @@ #include "OSTimer.h" #include "configuration.h" -#ifdef NO_ESP32 - /** * Schedule a callback to run. The callback must _not_ block, though it is called from regular thread level (not ISR) * - * NOTE! xTimerPend... seems to ignore the time passed in on ESP32 - I haven't checked on NRF52 + * NOTE! xTimerPend... seems to ignore the time passed in on ESP32 and on NRF52 + * The reason this didn't work is bcause xTimerPednFunctCall really isn't a timer function at all - it just means run the callback + * from the timer thread the next time you have spare cycles. * * @return true if successful, false if the timer fifo is too full. - */ + bool scheduleOSCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec) { return xTimerPendFunctionCall(callback, param1, param2, pdMS_TO_TICKS(delayMsec)); -} +} */ -#else +#ifndef NO_ESP32 // Super skanky quick hack to use hardware timers of the ESP32 static hw_timer_t *timer; diff --git a/src/OSTimer.h b/src/OSTimer.h index cdf2386a6..37415f3a6 100644 --- a/src/OSTimer.h +++ b/src/OSTimer.h @@ -4,15 +4,5 @@ typedef void (*PendableFunction)(void *pvParameter1, uint32_t ulParameter2); -/** - * Schedule a callback to run. The callback must _not_ block, though it is called from regular thread level (not ISR) - * - * NOTE! ESP32 implementation is busted - always waits 0 ticks - * - * @return true if successful, false if the timer fifo is too full. - */ - bool scheduleOSCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec); - - /// Uses a hardware timer, but calls the handler in _interrupt_ context bool scheduleHWCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec); \ No newline at end of file diff --git a/src/PeriodicTask.h b/src/PeriodicTask.h index 9d2a06b6d..951b8cbbb 100644 --- a/src/PeriodicTask.h +++ b/src/PeriodicTask.h @@ -1,6 +1,7 @@ #pragma once #include "lock.h" +#include #include #include @@ -66,7 +67,13 @@ class PeriodicTask * Set a new period in msecs (can be called from doTask or elsewhere and the scheduler will cope) * While zero this task is disabled and will not run */ - void setPeriod(uint32_t p) { period = p; } + void setPeriod(uint32_t p) + { + lastMsec = millis(); // reset starting from now + period = p; + } + + uint32_t getPeriod() const { return period; } /** * Syntatic sugar for suspending tasks diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 1471e3a98..44bcc0413 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -11,12 +11,18 @@ static SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi, PhysicalLayer *_iface) - : module(cs, irq, rst, busy, spi, spiSettings), iface(_iface) + : PeriodicTask(0), module(cs, irq, rst, busy, spi, spiSettings), iface(_iface) { assert(!instance); // We assume only one for now instance = this; } +bool RadioLibInterface::init() +{ + setup(); // init our timer + return RadioInterface::init(); +} + #ifndef NO_ESP32 // ESP32 doesn't use that flag #define YIELD_FROM_ISR(x) portYIELD_FROM_ISR() @@ -194,48 +200,25 @@ 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) +void RadioLibInterface::doTask() { - RadioLibInterface *t = (RadioLibInterface *)p1; - - t->timerRunning = false; + disable(); // Don't call this callback again // We use without overwrite, so that if there is already an interrupt pending to be handled, that gets handle properly (the // ISR handler will restart our timer) -#ifndef USE_HW_TIMER - t->notify(TRANSMIT_DELAY_COMPLETED, eSetValueWithoutOverwrite); -#else - BaseType_t xHigherPriorityTaskWoken; - instance->notifyFromISR(&xHigherPriorityTaskWoken, TRANSMIT_DELAY_COMPLETED, eSetValueWithoutOverwrite); - /* Force a context switch if xHigherPriorityTaskWoken is now set to pdTRUE. - The macro used to do this is dependent on the port and may be called - portEND_SWITCHING_ISR. */ - YIELD_FROM_ISR(xHigherPriorityTaskWoken); -#endif + notify(TRANSMIT_DELAY_COMPLETED, eSetValueWithoutOverwrite); } void RadioLibInterface::startTransmitTimer(bool withDelay) { // If we have work to do and the timer wasn't already scheduled, schedule it now - if (!timerRunning && !txQueue.isEmpty()) { - timerRunning = true; + if (getPeriod() == 0 && !txQueue.isEmpty()) { uint32_t delay = - !withDelay ? 0 : random(MIN_TX_WAIT_MSEC, MAX_TX_WAIT_MSEC); // See documentation for loop() wrt these values + !withDelay ? 1 : random(MIN_TX_WAIT_MSEC, MAX_TX_WAIT_MSEC); // See documentation for loop() wrt these values // DEBUG_MSG("xmit timer %d\n", delay); -#ifdef USE_HW_TIMER - bool okay = scheduleHWCallback(timerCallback, this, 0, delay); -#else - bool okay = scheduleOSCallback(timerCallback, this, 0, delay); -#endif - assert(okay); + + setPeriod(delay); } } diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 5f9149d71..1a0e5f450 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -1,5 +1,6 @@ #pragma once +#include "PeriodicTask.h" #include "RadioInterface.h" #include @@ -11,13 +12,12 @@ #define INTERRUPT_ATTR #endif -class RadioLibInterface : public RadioInterface +class RadioLibInterface : public RadioInterface, private PeriodicTask { /// Used as our notification from the ISR enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX, TRANSMIT_DELAY_COMPLETED }; volatile PendingISR pending = ISR_NONE; - volatile bool timerRunning = false; /** * Raw ISR handler that just calls our polymorphic method @@ -104,7 +104,14 @@ class RadioLibInterface : public RadioInterface static void timerCallback(void *p1, uint32_t p2); + virtual void doTask(); + protected: + /// Initialise the Driver transport hardware and software. + /// Make sure the Driver is properly configured before calling init(). + /// \return true if initialisation succeeded. + virtual bool init(); + /** * Convert our modemConfig enum into wf, sf, etc... * diff --git a/src/mesh/SX1262Interface.cpp b/src/mesh/SX1262Interface.cpp index 84a969765..dadd98d0f 100644 --- a/src/mesh/SX1262Interface.cpp +++ b/src/mesh/SX1262Interface.cpp @@ -113,9 +113,9 @@ void SX1262Interface::startReceive() /** Could we send right now (i.e. either not actively receving or transmitting)? */ bool SX1262Interface::isActivelyReceiving() { - return false; // FIXME - // FIXME this is not correct - often always true - need to add an extra conditional - // return lora.getPacketLength() > 0; + // return false; // FIXME + // FIXME this is not correct? - often always true - need to add an extra conditional + return lora.getPacketLength() > 0; } bool SX1262Interface::sleep() diff --git a/src/nrf52/main-nrf52.cpp b/src/nrf52/main-nrf52.cpp index e0d1ac5e6..6e0b486e0 100644 --- a/src/nrf52/main-nrf52.cpp +++ b/src/nrf52/main-nrf52.cpp @@ -43,12 +43,15 @@ void getMacAddr(uint8_t *dmac) NRF52Bluetooth *nrf52Bluetooth; +// FIXME, turn off soft device access for debugging +static bool isSoftDeviceAllowed = false; + static bool bleOn = false; void setBluetoothEnable(bool on) { if (on != bleOn) { if (on) { - if (!nrf52Bluetooth) { + if (!nrf52Bluetooth && isSoftDeviceAllowed) { nrf52Bluetooth = new NRF52Bluetooth(); nrf52Bluetooth->setup(); } From 829e0b6e26becccc3109f43c16e104d486188027 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 25 May 2020 08:19:14 -0700 Subject: [PATCH 052/131] fix extra free --- src/mesh/MeshService.cpp | 5 +---- src/mesh/RadioLibInterface.cpp | 1 - src/mesh/Router.h | 4 ++++ 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 540ca7cb1..5060851c3 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -247,10 +247,7 @@ void MeshService::sendToMesh(MeshPacket *p) } // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it - if (router.sendLocal(p) != ERRNO_OK) { - DEBUG_MSG("No radio was able to send packet, discarding...\n"); - releaseToPool(p); - } + router.sendLocal(p); } void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies) diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 44bcc0413..53f99aae9 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -1,6 +1,5 @@ #include "RadioLibInterface.h" #include "MeshTypes.h" -#include "OSTimer.h" #include "mesh-pb-constants.h" #include #include diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 45f0b762c..6903b7d4e 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -48,6 +48,8 @@ class Router /** * Works like send, but if we are sending to the local node, we directly put the message in the receive queue + * + * NOTE: This method will free the provided packet (even if we return an error code) */ ErrorCode sendLocal(MeshPacket *p); @@ -63,6 +65,8 @@ class Router * 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 + * + * NOTE: This method will free the provided packet (even if we return an error code) */ virtual ErrorCode send(MeshPacket *p); From d39e775c9522acbd9ed8fab34e116fc4ca2a7cdd Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 25 May 2020 10:07:42 -0700 Subject: [PATCH 053/131] make flash filesystem work on NRF52 --- docs/software/nrf52-TODO.md | 2 +- src/mesh/NodeDB.cpp | 25 +++++++++++++++++-------- src/mesh/mesh-pb-constants.cpp | 14 +++++++------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 2e469f576..515378d9d 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -21,7 +21,6 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At - DONE get serial API working - get full BLE api working -- make a file system implementation (preferably one that can see the files the bootloader also sees) - preferably https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/libraries/InternalFileSytem/examples/Internal_ReadWrite/Internal_ReadWrite.ino else use https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.3.0/lib_fds_usage.html?cp=7_5_0_3_55_3 - make power management/sleep work properly - make a settimeofday implementation - DONE increase preamble length? - will break other clients? so all devices must update @@ -125,6 +124,7 @@ Nice ideas worth considering someday... - DONE use SX126x::startReceiveDutyCycleAuto to save power by sleeping and briefly waking to check for preamble bits. Change xmit rules to have more preamble bits. - scheduleOSCallback doesn't work yet - it is way too fast (causes rapid polling of busyTx, high power draw etc...) - find out why we reboot while debugging - it was bluetooth/softdevice +- make a file system implementation (preferably one that can see the files the bootloader also sees) - preferably https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/libraries/InternalFileSytem/examples/Internal_ReadWrite/Internal_ReadWrite.ino else use https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.3.0/lib_fds_usage.html?cp=7_5_0_3_55_3 ``` diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index d9e45dd54..f46d1d8d3 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -33,6 +33,14 @@ DeviceState versions used to be defined in the .proto file but really only this #ifndef NO_ESP32 #define FS SPIFFS +#define FSBegin() FS.begin(true) +#define FILE_O_WRITE "w" +#define FILE_O_READ "r" +#else +#include "InternalFileSystem.h" +#define FS InternalFS +#define FSBegin() FS.begin() +using namespace Adafruit_LittleFS_Namespace; #endif // FIXME - move this somewhere else @@ -135,8 +143,15 @@ void NodeDB::init() info->user = owner; info->has_user = true; + if (!FSBegin()) // FIXME - do this in main? + { + DEBUG_MSG("ERROR filesystem mount Failed\n"); + // FIXME - report failure to phone + } + // saveToDisk(); loadFromDisk(); + // saveToDisk(); // We set these _after_ loading from disk - because they come from the build and are more trusted than // what is stored in flash @@ -180,13 +195,7 @@ void NodeDB::loadFromDisk() #ifdef FS static DeviceState scratch; - if (!FS.begin(true)) // FIXME - do this in main? - { - DEBUG_MSG("ERROR SPIFFS Mount Failed\n"); - // FIXME - report failure to phone - } - - File f = FS.open(preffile); + auto f = FS.open(preffile); if (f) { DEBUG_MSG("Loading saved preferences\n"); pb_istream_t stream = {&readcb, &f, DeviceState_size}; @@ -220,7 +229,7 @@ void NodeDB::loadFromDisk() void NodeDB::saveToDisk() { #ifdef FS - File f = FS.open(preftmp, "w"); + auto f = FS.open(preftmp, FILE_O_WRITE); if (f) { DEBUG_MSG("Writing preferences\n"); diff --git a/src/mesh/mesh-pb-constants.cpp b/src/mesh/mesh-pb-constants.cpp index 864317851..dc0d188ab 100644 --- a/src/mesh/mesh-pb-constants.cpp +++ b/src/mesh/mesh-pb-constants.cpp @@ -6,6 +6,11 @@ #include #include +#ifdef NO_ESP32 +#include "Adafruit_LittleFS.h" +using namespace Adafruit_LittleFS_Namespace; // To get File type +#endif + /// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic /// returns the encoded packet size size_t pb_encode_to_bytes(uint8_t *destbuf, size_t destbufsize, const pb_msgdesc_t *fields, const void *src_struct) @@ -36,7 +41,6 @@ bool pb_decode_from_bytes(const uint8_t *srcbuf, size_t srcbufsize, const pb_msg bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count) { bool status = false; -#ifndef NO_ESP32 File *file = (File *)stream->state; if (buf == NULL) { @@ -45,24 +49,20 @@ bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count) return count == 0; } - status = (file->read(buf, count) == count); + status = (file->read(buf, count) == (int) count); if (file->available() == 0) stream->bytes_left = 0; -#endif + return status; } /// Write to an arduino file bool writecb(pb_ostream_t *stream, const uint8_t *buf, size_t count) { -#ifndef NO_ESP32 File *file = (File *)stream->state; // DEBUG_MSG("writing %d bytes to protobuf file\n", count); return file->write(buf, count) == count; -#else - return false; -#endif } bool is_in_helper(uint32_t n, const uint32_t *array, pb_size_t count) From fdaed7e3232d5cf1ac9a34cbe107ca87f60cfbdf Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 25 May 2020 10:41:19 -0700 Subject: [PATCH 054/131] Fix MIN_BAT_MILLIVOLTS per @spattinson --- src/esp32/main-esp32.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index 7f9780862..e8512e2ea 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -183,7 +183,11 @@ uint32_t axpDebugRead() Periodic axpDebugOutput(axpDebugRead); #endif -#define MIN_BAT_MILLIVOLTS 3690 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/ +/** + * Per @spattinson + * MIN_BAT_MILLIVOLTS seems high. Typical 18650 are different chemistry to LiPo, even for LiPos that chart seems a bit off, other charts put 3690mV at about 30% for a lipo, for 18650 i think 10% remaining iis in the region of 3.2-3.3V. Reference 1st graph in [this test report](https://lygte-info.dk/review/batteries2012/Samsung%20INR18650-30Q%203000mAh%20%28Pink%29%20UK.html) looking at the red line - discharge at 0.2A - he gets a capacity of 2900mah, 90% of 2900 = 2610, that point in the graph looks to be a shade above 3.2V + */ +#define MIN_BAT_MILLIVOLTS 3250 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/ /// loop code specific to ESP32 targets void esp32Loop() From d5f177b1eed0b58b1c70d19fc75cd9ed58d35f43 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 25 May 2020 10:41:46 -0700 Subject: [PATCH 055/131] begin UC1701 driver --- docs/software/nrf52-TODO.md | 4 ++-- platformio.ini | 4 +++- src/UC1701Adapter.cpp | 2 ++ 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 src/UC1701Adapter.cpp diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 515378d9d..02367d612 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -7,7 +7,7 @@ Minimum items needed to make sure hardware is good. - write UC1701 wrapper -- DONE install a hardfault handler for null ptrs (if one isn't already installed) +- Test hardfault handler for null ptrs (if one isn't already installed) - test my hackedup bootloader on the real hardware - Use the PMU driver on real hardware - Use new radio driver on real hardware @@ -42,7 +42,6 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At - check datasheet about sx1262 temperature compensation - enable brownout detection and watchdog - stop polling for GPS characters, instead stay blocked on read in a thread -- turn back on in-radio destaddr checking for RF95 - 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 @@ -83,6 +82,7 @@ Nice ideas worth considering someday... '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 +- the SX126x class does SPI transfers on a byte by byte basis, which is very ineffecient. Much better to do block writes/reads. ## Old unorganized notes diff --git a/platformio.ini b/platformio.ini index cff9006df..042d4ed2e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = nrf52dk ; Note: the github actions CI test build can't yet build NRF52 targets +default_envs = tbeam ; Note: the github actions CI test build can't yet build NRF52 targets [common] ; common is not currently used @@ -151,6 +151,8 @@ debug_init_break = [env:nrf52dk] extends = nrf52_base board = nrf52840_dk_modified +lib_deps = + UC1701 ; for temp testing ; The PPR board [env:ppr] diff --git a/src/UC1701Adapter.cpp b/src/UC1701Adapter.cpp new file mode 100644 index 000000000..72d4992a7 --- /dev/null +++ b/src/UC1701Adapter.cpp @@ -0,0 +1,2 @@ +#include + From 03999e9d564d4015c704bd013ba52659fcc7d3e9 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 25 May 2020 10:46:26 -0700 Subject: [PATCH 056/131] fix build for esp32 --- src/{ => nrf52}/UC1701Adapter.cpp | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{ => nrf52}/UC1701Adapter.cpp (100%) diff --git a/src/UC1701Adapter.cpp b/src/nrf52/UC1701Adapter.cpp similarity index 100% rename from src/UC1701Adapter.cpp rename to src/nrf52/UC1701Adapter.cpp From f4b1678535796ec1f45ce501d5b894f7ce5ee70b Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 25 May 2020 11:55:42 -0700 Subject: [PATCH 057/131] my DSR changes broke acks for flood routing also. Fix #146 --- src/mesh/FloodingRouter.cpp | 5 +++-- src/mesh/ReliableRouter.cpp | 37 ++++++++++++++++++++++--------------- src/mesh/ReliableRouter.h | 5 +++++ 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 0b7aea64a..ffd652bf8 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -19,8 +19,9 @@ ErrorCode FloodingRouter::send(MeshPacket *p) bool FloodingRouter::shouldFilterReceived(const MeshPacket *p) { - if (wasSeenRecently(p)) { - DEBUG_MSG("Ignoring incoming msg, because we've already seen it\n"); + if (wasSeenRecently(p)) { // Note: this will also add a recent packet record + DEBUG_MSG("Ignoring incoming msg, because we've already seen it: fr=0x%x,to=0x%x,id=%d,hop_limit=%d\n", p->from, p->to, + p->id, p->hop_limit); return true; } diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 7056e36eb..603768a06 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -24,6 +24,23 @@ ErrorCode ReliableRouter::send(MeshPacket *p) return FloodingRouter::send(p); } +bool ReliableRouter::shouldFilterReceived(const MeshPacket *p) +{ + if (p->to == NODENUM_BROADCAST && p->from == getNodeNum()) { + DEBUG_MSG("Received someone rebroadcasting for us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + + // We are seeing someone rebroadcast one of our broadcast attempts. + // If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for + // the original sending process. + if (stopRetransmission(p->from, p->id)) { + DEBUG_MSG("Someone is retransmitting for us, generate implicit ack\n"); + sendAckNak(true, p->from, p->id); + } + } + + return FloodingRouter::shouldFilterReceived(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) @@ -40,18 +57,8 @@ void ReliableRouter::sniffReceived(const MeshPacket *p) { NodeNum ourNode = getNodeNum(); - if (p->from == ourNode && p->to == NODENUM_BROADCAST) { - DEBUG_MSG("Received someone rebroadcasting for us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - - // We are seeing someone rebroadcast one of our broadcast attempts. - // If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for - // the original sending process. - if (stopRetransmission(p->from, p->id)) { - DEBUG_MSG("Someone is retransmitting for us, generate implicit ack\n"); - sendAckNak(true, p->from, p->id); - } - } else if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability - // - not DSR routing) + if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability + // - not DSR routing) if (p->want_ack) { sendAckNak(true, p->from, p->id); } @@ -61,8 +68,8 @@ void ReliableRouter::sniffReceived(const MeshPacket *p) 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 wasSeenRecently - not update it (to not mess up broadcasts) - if ((ackId || nakId) && !wasSeenRecently(p, false)) { + // We intentionally don't check wasSeenRecently, because it is harmless to delete non existent retransmission records + if (ackId || nakId) { if (ackId) { DEBUG_MSG("Received a ack=%d, stopping retransmissions\n", ackId); stopRetransmission(p->to, ackId); @@ -169,7 +176,7 @@ void ReliableRouter::doRetransmissions() sendAckNak(false, p.packet->from, p.packet->id); // Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived - which // allows the DSR version to still be able to look at the PendingPacket - // stopRetransmission(it->first); + stopRetransmission(it->first); } else { DEBUG_MSG("Sending reliable retransmission fr=0x%x,to=0x%x,id=%d, tries left=%d\n", p.packet->from, p.packet->to, p.packet->id, p.numRetransmissions); diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h index b96176864..d984ea118 100644 --- a/src/mesh/ReliableRouter.h +++ b/src/mesh/ReliableRouter.h @@ -98,6 +98,11 @@ class ReliableRouter : public FloodingRouter PendingPacket *findPendingPacket(NodeNum from, PacketId id) { return findPendingPacket(GlobalPacketId(from, id)); } PendingPacket *findPendingPacket(GlobalPacketId p); + /** + * We hook this method so we can see packets before FloodingRouter says they should be discarded + */ + virtual bool shouldFilterReceived(const MeshPacket *p); + private: /** * Send an ack or a nak packet back towards whoever sent idFrom From 5d1614989ed05fdc959bab1df159e9329fac4c49 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 25 May 2020 15:47:45 -0700 Subject: [PATCH 058/131] Only add interfaces to the router if they can be initied --- src/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 3103335b5..dc6fe7ad6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -234,10 +234,10 @@ void setup() new SimRadio(); #endif - router.addInterface(rIf); - if (!rIf->init()) recordCriticalError(ErrNoRadio); + else + router.addInterface(rIf); // This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values PowerFSM_setup(); // we will transition to ON in a couple of seconds, FIXME, only do this for cold boots, not waking from SDS From fd386d9d7ff0c02b5557d222253f6a02a7a9fef9 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 25 May 2020 15:55:38 -0700 Subject: [PATCH 059/131] UC1701 WIP --- src/nrf52/UC1701Adapter.cpp | 2 -- src/nrf52/UC1701Spi.cpp | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) delete mode 100644 src/nrf52/UC1701Adapter.cpp create mode 100644 src/nrf52/UC1701Spi.cpp diff --git a/src/nrf52/UC1701Adapter.cpp b/src/nrf52/UC1701Adapter.cpp deleted file mode 100644 index 72d4992a7..000000000 --- a/src/nrf52/UC1701Adapter.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#include - diff --git a/src/nrf52/UC1701Spi.cpp b/src/nrf52/UC1701Spi.cpp new file mode 100644 index 000000000..d6f236cfd --- /dev/null +++ b/src/nrf52/UC1701Spi.cpp @@ -0,0 +1,36 @@ +#include + +class UC1701Spi : public OLEDDisplay +{ + private: + uint8_t _rst; + uint8_t _dc; + uint8_t _cs; + + public: + UC1701Spi() { setGeometry(GEOMETRY_128_64); } + + bool connect() + { + /* + pinMode(_dc, OUTPUT); + pinMode(_cs, OUTPUT); + pinMode(_rst, OUTPUT); + + SPI.begin(); + SPI.setClockDivider(SPI_CLOCK_DIV2); + + // Pulse Reset low for 10ms + digitalWrite(_rst, HIGH); + delay(1); + digitalWrite(_rst, LOW); + delay(10); + digitalWrite(_rst, HIGH); + */ + return true; + } + + void display(void) {} + + private: +}; \ No newline at end of file From da3ed9926bd3a70291fa87b66c733d5e80fddfe2 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 25 May 2020 15:56:06 -0700 Subject: [PATCH 060/131] add monitor mode debugging support --- docs/software/nrf52-TODO.md | 10 +- gdbinit | 8 + src/nrf52/JLINK_MONITOR.c | 120 ++++ src/nrf52/JLINK_MONITOR.h | 27 + src/nrf52/JLINK_MONITOR_ISR_SES.S | 888 ++++++++++++++++++++++++++++++ src/nrf52/main-nrf52.cpp | 4 + 6 files changed, 1051 insertions(+), 6 deletions(-) create mode 100644 src/nrf52/JLINK_MONITOR.c create mode 100644 src/nrf52/JLINK_MONITOR.h create mode 100644 src/nrf52/JLINK_MONITOR_ISR_SES.S diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 02367d612..1549dfe8c 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -27,14 +27,12 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At - DONE enable BLE DFU somehow - report appversion/hwversion in BLE - use new LCD driver from screen.cpp. Still need to hook it to a subclass of (poorly named) OLEDDisplay, and override display() to stream bytes out to the screen. -- we need to enable the external xtal for the sx1262 (on dio3) +- we need to enable the external tcxo for the sx1262 (on dio3)? - figure out which regulator mode the sx1262 is operating in - turn on security for BLE, make pairing work - make ble endpoints not require "start config", just have them start in config mode -- measure power management and confirm battery life - use new PMU to provide battery voltage/% full to app (both bluetooth and screen) -- do initial power measurements, measure effects of more preamble bits -- fix activelyReceiving for sx1262 +- do initial power measurements, measure effects of more preamble bits, measure power management and confirm battery life ## Items to be 'feature complete' @@ -59,7 +57,7 @@ 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 -- enable monitor mode debuggin (need to use real jlink): https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/monitor-mode-debugging-with-j-link-and-gdbeclipse +- enable monitor mode debugging (need to use real jlink): https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/monitor-mode-debugging-with-j-link-and-gdbeclipse - Improve efficiency of PeriodicTimer by only checking the next queued timer event, and carefully sorting based on schedule - 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 @@ -82,7 +80,7 @@ Nice ideas worth considering someday... '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 -- the SX126x class does SPI transfers on a byte by byte basis, which is very ineffecient. Much better to do block writes/reads. +- the SX126x class does SPI transfers on a byte by byte basis, which is very ineffecient. Much better to do block writes/reads. ## Old unorganized notes diff --git a/gdbinit b/gdbinit index 2af350b3a..977bed518 100644 --- a/gdbinit +++ b/gdbinit @@ -1,3 +1,11 @@ + +# Setup Monitor Mode Debugging +# Per .platformio/packages/framework-arduinoadafruitnrf52-old/cores/nRF5/linker/nrf52840_s140_v6.ld +# our appload starts at 0x26000 +# Disable for now because our version on board doesn't support monitor mode debugging +# mon exec SetMonModeDebug=1 +# mon exec SetMonModeVTableAddr=0x26000 + # the jlink debugger seems to want a pause after reset before we tell it to start running define restart monitor reset diff --git a/src/nrf52/JLINK_MONITOR.c b/src/nrf52/JLINK_MONITOR.c new file mode 100644 index 000000000..7355b089f --- /dev/null +++ b/src/nrf52/JLINK_MONITOR.c @@ -0,0 +1,120 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH & Co. KG * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2015 SEGGER Microcontroller GmbH & Co. KG * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** + +---------------------------------------------------------------------- +File : JLINK_MONITOR.c +Purpose : Implementation of debug monitor for J-Link monitor mode debug on Cortex-M devices. +-------- END-OF-HEADER --------------------------------------------- +*/ + +#include "JLINK_MONITOR.h" + +/********************************************************************* +* +* Configuration +* +********************************************************************** +*/ + +/********************************************************************* +* +* Defines +* +********************************************************************** +*/ + +/********************************************************************* +* +* Types +* +********************************************************************** +*/ + +/********************************************************************* +* +* Static data +* +********************************************************************** +*/ + +volatile int MAIN_MonCnt; // Incremented in JLINK_MONITOR_OnPoll() while CPU is in debug mode + +/********************************************************************* +* +* Local functions +* +********************************************************************** +*/ + +/********************************************************************* +* +* Global functions +* +********************************************************************** +*/ + +/********************************************************************* +* +* JLINK_MONITOR_OnExit() +* +* Function description +* Called from DebugMon_Handler(), once per debug exit. +* May perform some target specific operations to be done on debug mode exit. +* +* Notes +* (1) Must not keep the CPU busy for more than 100 ms +*/ +void JLINK_MONITOR_OnExit(void) { + // + // Add custom code here + // +// BSP_ClrLED(0); +} + +/********************************************************************* +* +* JLINK_MONITOR_OnEnter() +* +* Function description +* Called from DebugMon_Handler(), once per debug entry. +* May perform some target specific operations to be done on debug mode entry +* +* Notes +* (1) Must not keep the CPU busy for more than 100 ms +*/ +void JLINK_MONITOR_OnEnter(void) { + // + // Add custom code here + // +// BSP_SetLED(0); +// BSP_ClrLED(1); +} + +/********************************************************************* +* +* JLINK_MONITOR_OnPoll() +* +* Function description +* Called periodically from DebugMon_Handler(), to perform some actions that need to be performed periodically during debug mode. +* +* Notes +* (1) Must not keep the CPU busy for more than 100 ms +*/ +void JLINK_MONITOR_OnPoll(void) { + // + // Add custom code here + // + MAIN_MonCnt++; +// BSP_ToggleLED(0); +// _Delay(500000); +} + +/****** End Of File *************************************************/ diff --git a/src/nrf52/JLINK_MONITOR.h b/src/nrf52/JLINK_MONITOR.h new file mode 100644 index 000000000..8e2c45e0c --- /dev/null +++ b/src/nrf52/JLINK_MONITOR.h @@ -0,0 +1,27 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH & Co. KG * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2015 SEGGER Microcontroller GmbH & Co. KG * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** + +---------------------------------------------------------------------- +File : JLINK_MONITOR.h +Purpose : Header file of debug monitor for J-Link monitor mode debug on Cortex-M devices. +-------- END-OF-HEADER --------------------------------------------- +*/ + +#ifndef JLINK_MONITOR_H +#define JLINK_MONITOR_H + +void JLINK_MONITOR_OnExit (void); +void JLINK_MONITOR_OnEnter (void); +void JLINK_MONITOR_OnPoll (void); + +#endif + +/****** End Of File *************************************************/ diff --git a/src/nrf52/JLINK_MONITOR_ISR_SES.S b/src/nrf52/JLINK_MONITOR_ISR_SES.S new file mode 100644 index 000000000..e06e2cdb8 --- /dev/null +++ b/src/nrf52/JLINK_MONITOR_ISR_SES.S @@ -0,0 +1,888 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH & Co. KG * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2015 SEGGER Microcontroller GmbH & Co. KG * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** + +---------------------------------------------------------------------- +File : JLINK_MONITOR_ISR_SES.s +Purpose : Implementation of debug monitor for J-Link monitor mode + debug on Cortex-M devices, supporting SES compiler. +-------- END-OF-HEADER --------------------------------------------- +*/ + + .name JLINK_MONITOR_ISR + .syntax unified + + .extern JLINK_MONITOR_OnEnter + .extern JLINK_MONITOR_OnExit + .extern JLINK_MONITOR_OnPoll + + .global DebugMon_Handler + +/********************************************************************* +* +* Defines, configurable +* +********************************************************************** +*/ + +#define _MON_VERSION 100 // V x.yy + +/********************************************************************* +* +* Defines, fixed +* +********************************************************************** +*/ + +#define _APP_SP_OFF_R0 0x00 +#define _APP_SP_OFF_R1 0x04 +#define _APP_SP_OFF_R2 0x08 +#define _APP_SP_OFF_R3 0x0C +#define _APP_SP_OFF_R12 0x10 +#define _APP_SP_OFF_R14_LR 0x14 +#define _APP_SP_OFF_PC 0x18 +#define _APP_SP_OFF_XPSR 0x1C +#define _APP_SP_OFF_S0 0x20 +#define _APP_SP_OFF_S1 0x24 +#define _APP_SP_OFF_S2 0x28 +#define _APP_SP_OFF_S3 0x2C +#define _APP_SP_OFF_S4 0x30 +#define _APP_SP_OFF_S5 0x34 +#define _APP_SP_OFF_S6 0x38 +#define _APP_SP_OFF_S7 0x3C +#define _APP_SP_OFF_S8 0x40 +#define _APP_SP_OFF_S9 0x44 +#define _APP_SP_OFF_S10 0x48 +#define _APP_SP_OFF_S11 0x4C +#define _APP_SP_OFF_S12 0x50 +#define _APP_SP_OFF_S13 0x54 +#define _APP_SP_OFF_S14 0x58 +#define _APP_SP_OFF_S15 0x5C +#define _APP_SP_OFF_FPSCR 0x60 + +#define _NUM_BYTES_BASIC_STACKFRAME 32 +#define _NUM_BYTES_EXTENDED_STACKFRAME 72 + +#define _SYSTEM_DCRDR_OFF 0x00 +#define _SYSTEM_DEMCR_OFF 0x04 + +#define _SYSTEM_DHCSR 0xE000EDF0 // Debug Halting Control and Status Register (DHCSR) +#define _SYSTEM_DCRSR 0xE000EDF4 // Debug Core Register Selector Register (DCRSR) +#define _SYSTEM_DCRDR 0xE000EDF8 // Debug Core Register Data Register (DCRDR) +#define _SYSTEM_DEMCR 0xE000EDFC // Debug Exception and Monitor Control Register (DEMCR) + +#define _SYSTEM_FPCCR 0xE000EF34 // Floating-Point Context Control Register (FPCCR) +#define _SYSTEM_FPCAR 0xE000EF38 // Floating-Point Context Address Register (FPCAR) +#define _SYSTEM_FPDSCR 0xE000EF3C // Floating-Point Default Status Control Register (FPDSCR) +#define _SYSTEM_MVFR0 0xE000EF40 // Media and FP Feature Register 0 (MVFR0) +#define _SYSTEM_MVFR1 0xE000EF44 // Media and FP Feature Register 1 (MVFR1) + +/* +* Defines for determining if the current debug config supports FPU registers +* For some compilers like IAR EWARM when disabling the FPU in the compiler settings an error is thrown when +*/ +#ifdef __FPU_PRESENT + #if __FPU_PRESENT + #define _HAS_FPU_REGS 1 + #else + #define _HAS_FPU_REGS 0 + #endif +#else + #define _HAS_FPU_REGS 0 +#endif + +/********************************************************************* +* +* Signature of monitor +* +* Function description +* Needed for targets where also a boot ROM is present that possibly specifies a vector table with a valid debug monitor exception entry +*/ + .section .text, "ax" + + // + // JLINKMONHANDLER + // + .byte 0x4A + .byte 0x4C + .byte 0x49 + .byte 0x4E + .byte 0x4B + .byte 0x4D + .byte 0x4F + .byte 0x4E + .byte 0x48 + .byte 0x41 + .byte 0x4E + .byte 0x44 + .byte 0x4C + .byte 0x45 + .byte 0x52 + .byte 0x00 // Align to 8-bytes + +/********************************************************************* +* +* DebugMon_Handler() +* +* Function description +* Debug monitor handler. CPU enters this handler in case a "halt" request is made from the debugger. +* This handler is also responsible for handling commands that are sent by the debugger. +* +* Notes +* This is actually the ISR for the debug inerrupt (exception no. 12) +*/ + .thumb_func + +DebugMon_Handler: + /* + General procedure: + DCRDR is used as communication register + DEMCR[19] is used as ready flag + For the command J-Link sends to the monitor: DCRDR[7:0] == Cmd, DCRDR[31:8] == ParamData + + 1) Monitor sets DEMCR[19] whenever it is ready to receive new commands/data + DEMCR[19] is initially set on debug monitor entry + 2) J-Link will clear once it has placed conmmand/data in DCRDR for J-Link + 3) Monitor will wait for DEMCR[19] to be cleared + 4) Monitor will process command (May cause additional data transfers etc., depends on command + 5) No restart-CPU command? => Back to 2), Otherwise => 6) + 6) Monitor will clear DEMCR[19] 19 to indicate that it is no longer ready + */ + PUSH {LR} + BL JLINK_MONITOR_OnEnter + POP {LR} + LDR.N R3,_AddrDCRDR // 0xe000edf8 == _SYSTEM_DCRDR + B.N _IndicateMonReady +_WaitProbeReadIndicateMonRdy: // while(_SYSTEM_DEMCR & (1uL << 19)); => Wait until J-Link has read item + LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR + LSLS R0,R0,#+12 + BMI.N _WaitProbeReadIndicateMonRdy +_IndicateMonReady: + LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR |= (1uL << 19); => Set MON_REQ bit, so J-Link knows monitor is ready to receive commands + ORR R0,R0,#0x80000 + STR R0,[R3, #+_SYSTEM_DEMCR_OFF] + /* + During command loop: + R0 = Tmp + R1 = Tmp + R2 = Tmp + R3 = &_SYSTEM_DCRDR (allows also access to DEMCR with offset) + R12 = Tmp + + Outside command loop R0-R3 and R12 may be overwritten by MONITOR_OnPoll() + */ +_WaitForJLinkCmd: // do { + PUSH {LR} + BL JLINK_MONITOR_OnPoll + POP {LR} + LDR.N R3,_AddrDCRDR // 0xe000edf8 == _SYSTEM_DCRDR + LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] + LSRS R0,R0,#+20 // DEMCR[19] -> Carry Clear? => J-Link has placed command for us + BCS _WaitForJLinkCmd + /* + Perform command + Command is placed by J-Link in DCRDR[7:0] and additional parameter data is stored in DCRDR[31:8] + J-Link clears DEMCR[19] to indicate that it placed a command/data or read data + Monitor sets DEMCR[19] to indicate that it placed data or read data / is ready for a new command + Setting DEMCR[19] indicates "monitor ready for new command / data" and also indicates: "data has been placed in DCRDR by monitor, for J-Link" + Therefore it is responsibility of the commands to respond to the commands accordingly + + Commands for debug monitor + Commands must not exceed 0xFF (255) as we only defined 8-bits for command-part. Higher 24-bits are parameter info for current command + + Protocol for different commands: + J-Link: Cmd -> DCRDR, DEMCR[19] -> 0 => Cmd placed by probe + */ + LDR R0,[R3, #+_SYSTEM_DCRDR_OFF] // ParamInfo = _SYSTEM_DCRDR + LSRS R1,R0,#+8 // ParamInfo >>= 8 + LSLS R0,R0,#+24 + LSRS R0,R0,#+24 // Cmd = ParamInfo & 0xFF + // + // switch (Cmd) + // + CMP R0,#+0 + BEQ.N _HandleGetMonVersion // case _MON_CMD_GET_MONITOR_VERSION + CMP R0,#+2 + BEQ.N _HandleReadReg // case _MON_CMD_READ_REG + BCC.N _HandleRestartCPU // case _MON_CMD_RESTART_CPU + CMP R0,#+3 + BEQ.N _HandleWriteReg_Veneer // case _MON_CMD_WRITE_REG + B.N _IndicateMonReady // default : while (1); + /* + Return + _MON_CMD_RESTART_CPU + CPU: DEMCR[19] -> 0 => Monitor no longer ready + */ +_HandleRestartCPU: + LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR &= ~(1uL << 19); => Clear MON_REQ to indicate that monitor is no longer active + BIC R0,R0,#0x80000 + STR R0,[R3, #+_SYSTEM_DEMCR_OFF] + PUSH {LR} + BL JLINK_MONITOR_OnExit + POP {PC} + // + // Place data section here to not get in trouble with load-offsets + // + .section .text, "ax", %progbits + .align 2 +_AddrDCRDR: + .long 0xE000EDF8 +_AddrCPACR: + .long 0xE000ED88 + + .section .text, "ax" + .thumb_func + +;/********************************************************************* +;* +;* _HandleGetMonVersion +;* +;*/ +_HandleGetMonVersion: + /* + _MON_CMD_GET_MONITOR_VERSION + CPU: Data -> DCRDR, DEMCR[19] -> 1 => Data ready + J-Link: DCRDR -> Read, DEMCR[19] -> 0 => Data read + CPU: DEMCR[19] -> 1 => Mon ready + */ + MOVS R0,#+_MON_VERSION + STR R0,[R3, #+_SYSTEM_DCRDR_OFF] // _SYSTEM_DCRDR = x + LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR |= (1uL << 19); => Set MON_REQ bit, so J-Link knows monitor is ready to receive commands + ORR R0,R0,#0x80000 + STR R0,[R3, #+_SYSTEM_DEMCR_OFF] // Indicate data ready + B _WaitProbeReadIndicateMonRdy + +/********************************************************************* +* +* _HandleReadReg +* +*/ +_HandleWriteReg_Veneer: + B.N _HandleWriteReg +_HandleReadReg: + /* + _MON_CMD_READ_REG + CPU: Data -> DCRDR, DEMCR[19] -> 1 => Data ready + J-Link: DCRDR -> Read, DEMCR[19] -> 0 => Data read + CPU: DEMCR[19] -> 1 => Mon ready + + + Register indexes + 0-15: R0-R15 (13 == R13 reserved => is banked ... Has to be read as PSP / MSP. Decision has to be done by J-Link DLL side!) + 16: XPSR + 17: MSP + 18: PSP + 19: CFBP CONTROL/FAULTMASK/BASEPRI/PRIMASK (packed into 4 bytes of word. CONTROL = CFBP[31:24], FAULTMASK = CFBP[16:23], BASEPRI = CFBP[15:8], PRIMASK = CFBP[7:0] + 20: FPSCR + 21-52: FPS0-FPS31 + + + Register usage when entering this "subroutine": + R0 Cmd + R1 ParamInfo + R2 --- + R3 = &_SYSTEM_DCRDR (allows also access to DEMCR with offset) + R12 --- + + Table B1-9 EXC_RETURN definition of exception return behavior, with FP extension + LR Return to Return SP Frame type + --------------------------------------------------------- + 0xFFFFFFE1 Handler mode. MSP Extended + 0xFFFFFFE9 Thread mode MSP Extended + 0xFFFFFFED Thread mode PSP Extended + 0xFFFFFFF1 Handler mode. MSP Basic + 0xFFFFFFF9 Thread mode MSP Basic + 0xFFFFFFFD Thread mode PSP Basic + + So LR[2] == 1 => Return stack == PSP else MSP + + R0-R3, R12, PC, xPSR can be read from application stackpointer + Other regs can be read directly + */ + LSRS R2,LR,#+3 // Shift LR[2] into carry => Carry clear means that CPU was running on MSP + ITE CS + MRSCS R2,PSP + MRSCC R2,MSP + CMP R1,#+4 // if (RegIndex < 4) { (R0-R3) + BCS _HandleReadRegR4 + LDR R0,[R2, R1, LSL #+2] // v = [SP + Rx * 4] (R0-R3) + B.N _HandleReadRegDone +_HandleReadRegR4: + CMP R1,#+5 // if (RegIndex < 5) { (R4) + BCS _HandleReadRegR5 + MOV R0,R4 + B.N _HandleReadRegDone +_HandleReadRegR5: + CMP R1,#+6 // if (RegIndex < 6) { (R5) + BCS _HandleReadRegR6 + MOV R0,R5 + B.N _HandleReadRegDone +_HandleReadRegR6: + CMP R1,#+7 // if (RegIndex < 7) { (R6) + BCS _HandleReadRegR7 + MOV R0,R6 + B.N _HandleReadRegDone +_HandleReadRegR7: + CMP R1,#+8 // if (RegIndex < 8) { (R7) + BCS _HandleReadRegR8 + MOV R0,R7 + B.N _HandleReadRegDone +_HandleReadRegR8: + CMP R1,#+9 // if (RegIndex < 9) { (R8) + BCS _HandleReadRegR9 + MOV R0,R8 + B.N _HandleReadRegDone +_HandleReadRegR9: + CMP R1,#+10 // if (RegIndex < 10) { (R9) + BCS _HandleReadRegR10 + MOV R0,R9 + B.N _HandleReadRegDone +_HandleReadRegR10: + CMP R1,#+11 // if (RegIndex < 11) { (R10) + BCS _HandleReadRegR11 + MOV R0,R10 + B.N _HandleReadRegDone +_HandleReadRegR11: + CMP R1,#+12 // if (RegIndex < 12) { (R11) + BCS _HandleReadRegR12 + MOV R0,R11 + B.N _HandleReadRegDone +_HandleReadRegR12: + CMP R1,#+14 // if (RegIndex < 14) { (R12) + BCS _HandleReadRegR14 + LDR R0,[R2, #+_APP_SP_OFF_R12] + B.N _HandleReadRegDone +_HandleReadRegR14: + CMP R1,#+15 // if (RegIndex < 15) { (R14 / LR) + BCS _HandleReadRegR15 + LDR R0,[R2, #+_APP_SP_OFF_R14_LR] + B.N _HandleReadRegDone +_HandleReadRegR15: + CMP R1,#+16 // if (RegIndex < 16) { (R15 / PC) + BCS _HandleReadRegXPSR + LDR R0,[R2, #+_APP_SP_OFF_PC] + B.N _HandleReadRegDone +_HandleReadRegXPSR: + CMP R1,#+17 // if (RegIndex < 17) { (xPSR) + BCS _HandleReadRegMSP + LDR R0,[R2, #+_APP_SP_OFF_XPSR] + B.N _HandleReadRegDone +_HandleReadRegMSP: + /* + Stackpointer is tricky because we need to get some info about the SP used in the user app, first + + Handle reading R0-R3 which can be read right from application stackpointer + + Table B1-9 EXC_RETURN definition of exception return behavior, with FP extension + LR Return to Return SP Frame type + --------------------------------------------------------- + 0xFFFFFFE1 Handler mode. MSP Extended + 0xFFFFFFE9 Thread mode MSP Extended + 0xFFFFFFED Thread mode PSP Extended + 0xFFFFFFF1 Handler mode. MSP Basic + 0xFFFFFFF9 Thread mode MSP Basic + 0xFFFFFFFD Thread mode PSP Basic + + So LR[2] == 1 => Return stack == PSP else MSP + Per architecture definition: Inside monitor (exception) SP = MSP + + Stack pointer handling is complicated because it is different what is pushed on the stack before entering the monitor ISR... + Cortex-M: 8 regs + Cortex-M + forced-stack-alignment: 8 regs + 1 dummy-word if stack was not 8-byte aligned + Cortex-M + FPU: 8 regs + 17 FPU regs + 1 dummy-word + 1-dummy word if stack was not 8-byte aligned + Cortex-M + FPU + lazy mode: 8 regs + 17 dummy-words + 1 dummy-word + 1-dummy word if stack was not 8-byte aligned + */ + CMP R1,#+18 // if (RegIndex < 18) { (MSP) + BCS _HandleReadRegPSP + MRS R0,MSP + LSRS R1,LR,#+3 // LR[2] -> Carry == 0 => CPU was running on MSP => Needs correction + BCS _HandleReadRegDone_Veneer // CPU was running on PSP? => No correction necessary +_HandleSPCorrection: + LSRS R1,LR,#+5 // LR[4] -> Carry == 0 => extended stack frame has been allocated. See ARM DDI0403D, B1.5.7 Stack alignment on exception entry + ITE CS + ADDCS R0,R0,#+_NUM_BYTES_BASIC_STACKFRAME + ADDCC R0,R0,#+_NUM_BYTES_EXTENDED_STACKFRAME + LDR R1,[R2, #+_APP_SP_OFF_XPSR] // Get xPSR from application stack (R2 has been set to app stack on beginning of _HandleReadReg) + LSRS R1,R1,#+5 // xPSR[9] -> Carry == 1 => Stack has been force-aligned before pushing regs. See ARM DDI0403D, B1.5.7 Stack alignment on exception entry + IT CS + ADDCS R0,R0,#+4 + B _HandleReadRegDone +_HandleReadRegPSP: // RegIndex == 18 + CMP R1,#+19 // if (RegIndex < 19) { + BCS _HandleReadRegCFBP + MRS R0,PSP // PSP is not touched by monitor + LSRS R1,LR,#+3 // LR[2] -> Carry == 1 => CPU was running on PSP => Needs correction + BCC _HandleReadRegDone_Veneer // CPU was running on MSP? => No correction of PSP necessary + B _HandleSPCorrection +_HandleReadRegCFBP: + /* + CFBP is a register that can only be read via debug probe and is a merger of the following regs: + CONTROL/FAULTMASK/BASEPRI/PRIMASK (packed into 4 bytes of word. CONTROL = CFBP[31:24], FAULTMASK = CFBP[16:23], BASEPRI = CFBP[15:8], PRIMASK = CFBP[7:0] + To keep J-Link side the same for monitor and halt mode, we also return CFBP in monitor mode + */ + CMP R1,#+20 // if (RegIndex < 20) { (CFBP) + BCS _HandleReadRegFPU + MOVS R0,#+0 + MRS R2,PRIMASK + ORRS R0,R2 // Merge PRIMASK into CFBP[7:0] + MRS R2,BASEPRI + LSLS R2,R2,#+8 // Merge BASEPRI into CFBP[15:8] + ORRS R0,R2 + MRS R2,FAULTMASK + LSLS R2,R2,#+16 // Merge FAULTMASK into CFBP[23:16] + ORRS R0,R2 + MRS R2,CONTROL + LSRS R1,LR,#3 // LR[2] -> Carry. CONTROL.SPSEL is saved to LR[2] on exception entry => ARM DDI0403D, B1.5.6 Exception entry behavior + IT CS // As J-Link sees value of CONTROL at application time, we need reconstruct original value of CONTROL + ORRCS R2,R2,#+2 // CONTROL.SPSEL (CONTROL[1]) == 0 inside monitor + LSRS R1,LR,#+5 // LR[4] == NOT(CONTROL.FPCA) -> Carry + ITE CS // Merge original value of FPCA (CONTROL[2]) into read data + BICCS R2,R2,#+0x04 // Remember LR contains NOT(CONTROL) + ORRCC R2,R2,#+0x04 + LSLS R2,R2,#+24 + ORRS R0,R2 + B.N _HandleReadRegDone +_HandleReadRegFPU: +#if _HAS_FPU_REGS + CMP R1,#+53 // if (RegIndex < 53) { (20 (FPSCR), 21-52 FPS0-FPS31) + BCS _HandleReadRegDone_Veneer + /* + Read Coprocessor Access Control Register (CPACR) to check if CP10 and CP11 are enabled + If not, access to floating point is not possible + CPACR[21:20] == CP10 enable. 0b01 = Privileged access only. 0b11 = Full access. Other = reserved + CPACR[23:22] == CP11 enable. 0b01 = Privileged access only. 0b11 = Full access. Other = reserved + */ + LDR R0,_AddrCPACR + LDR R0,[R0] + LSLS R0,R0,#+8 + LSRS R0,R0,#+28 + CMP R0,#+0xF + BEQ _HandleReadRegFPU_Allowed + CMP R0,#+0x5 + BNE _HandleReadRegDone_Veneer +_HandleReadRegFPU_Allowed: + CMP R1,#+21 // if (RegIndex < 21) (20 == FPSCR) + BCS _HandleReadRegFPS0_FPS31 + LSRS R0,LR,#+5 // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack + BCS _HandleReadFPSCRLazyMode // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame + LDR R0,=_SYSTEM_FPCCR + LDR R0,[R0] + LSLS R0,R0,#+2 // FPCCR[30] -> Carry == 1 indicates if lazy mode is active, so space on stack is reserved but FPU registers are not saved on stack + BCS _HandleReadFPSCRLazyMode + LDR R0,[R2, #+_APP_SP_OFF_FPSCR] + B _HandleReadRegDone +_HandleReadFPSCRLazyMode: + VMRS R0,FPSCR + B _HandleReadRegDone +_HandleReadRegFPS0_FPS31: // RegIndex == 21-52 + LSRS R0,LR,#+5 // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack + BCS _HandleReadFPS0_FPS31LazyMode // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame + LDR R0,=_SYSTEM_FPCCR + LDR R0,[R0] + LSLS R0,R0,#+2 // FPCCR[30] -> Carry == 1 indicates if lazy mode is active, so space on stack is reserved but FPU registers are not saved on stack + BCS _HandleReadFPS0_FPS31LazyMode + SUBS R1,#+21 // Convert absolute reg index into rel. one + LSLS R1,R1,#+2 // RegIndex to position on stack + ADDS R1,#+_APP_SP_OFF_S0 + LDR R0,[R2, R1] +_HandleReadRegDone_Veneer: + B _HandleReadRegDone +_HandleReadFPS0_FPS31LazyMode: + SUBS R1,#+20 // convert abs. RegIndex into rel. one + MOVS R0,#+6 + MULS R1,R0,R1 + LDR R0,=_HandleReadRegUnknown + SUB R0,R0,R1 // _HandleReadRegUnknown - 6 * ((RegIndex - 21) + 1) + ORR R0,R0,#1 // Thumb bit needs to be set in DestAddr + BX R0 + // + // Table for reading FPS0-FPS31 + // + VMOV R0,S31 // v = FPSx + B _HandleReadRegDone + VMOV R0,S30 + B _HandleReadRegDone + VMOV R0,S29 + B _HandleReadRegDone + VMOV R0,S28 + B _HandleReadRegDone + VMOV R0,S27 + B _HandleReadRegDone + VMOV R0,S26 + B _HandleReadRegDone + VMOV R0,S25 + B _HandleReadRegDone + VMOV R0,S24 + B _HandleReadRegDone + VMOV R0,S23 + B _HandleReadRegDone + VMOV R0,S22 + B _HandleReadRegDone + VMOV R0,S21 + B _HandleReadRegDone + VMOV R0,S20 + B _HandleReadRegDone + VMOV R0,S19 + B _HandleReadRegDone + VMOV R0,S18 + B _HandleReadRegDone + VMOV R0,S17 + B _HandleReadRegDone + VMOV R0,S16 + B _HandleReadRegDone + VMOV R0,S15 + B _HandleReadRegDone + VMOV R0,S14 + B _HandleReadRegDone + VMOV R0,S13 + B _HandleReadRegDone + VMOV R0,S12 + B _HandleReadRegDone + VMOV R0,S11 + B _HandleReadRegDone + VMOV R0,S10 + B _HandleReadRegDone + VMOV R0,S9 + B _HandleReadRegDone + VMOV R0,S8 + B _HandleReadRegDone + VMOV R0,S7 + B _HandleReadRegDone + VMOV R0,S6 + B _HandleReadRegDone + VMOV R0,S5 + B _HandleReadRegDone + VMOV R0,S4 + B _HandleReadRegDone + VMOV R0,S3 + B _HandleReadRegDone + VMOV R0,S2 + B _HandleReadRegDone + VMOV R0,S1 + B _HandleReadRegDone + VMOV R0,S0 + B _HandleReadRegDone +#else + B _HandleReadRegUnknown +_HandleReadRegDone_Veneer: + B _HandleReadRegDone +#endif +_HandleReadRegUnknown: + MOVS R0,#+0 // v = 0 + B.N _HandleReadRegDone +_HandleReadRegDone: + + // Send register content to J-Link and wait until J-Link has read the data + + STR R0,[R3, #+_SYSTEM_DCRDR_OFF] // DCRDR = v; + LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR |= (1uL << 19); => Set MON_REQ bit, so J-Link knows monitor is ready to receive commands + ORR R0,R0,#0x80000 + STR R0,[R3, #+_SYSTEM_DEMCR_OFF] // Indicate data ready + B _WaitProbeReadIndicateMonRdy + + // Data section for register addresses + +_HandleWriteReg: + /* + _MON_CMD_WRITE_REG + CPU: DEMCR[19] -> 1 => Mon ready + J-Link: Data -> DCRDR, DEMCR[19] -> 0 => Data placed by probe + CPU: DCRDR -> Read, Process command, DEMCR[19] -> 1 => Data read & mon ready + + Register indexes + 0-15: R0-R15 (13 == R13 reserved => is banked ... Has to be read as PSP / MSP. Decision has to be done by J-Link DLL side!) + 16: XPSR + 17: MSP + 18: PSP + 19: CFBP CONTROL/FAULTMASK/BASEPRI/PRIMASK (packed into 4 bytes of word. CONTROL = CFBP[31:24], FAULTMASK = CFBP[16:23], BASEPRI = CFBP[15:8], PRIMASK = CFBP[7:0] + 20: FPSCR + 21-52: FPS0-FPS31 + + + Register usage when entering this "subroutine": + R0 Cmd + R1 ParamInfo + R2 --- + R3 = &_SYSTEM_DCRDR (allows also access to DEMCR with offset) + R12 --- + + Table B1-9 EXC_RETURN definition of exception return behavior, with FP extension + LR Return to Return SP Frame type + --------------------------------------------------------- + 0xFFFFFFE1 Handler mode. MSP Extended + 0xFFFFFFE9 Thread mode MSP Extended + 0xFFFFFFED Thread mode PSP Extended + 0xFFFFFFF1 Handler mode. MSP Basic + 0xFFFFFFF9 Thread mode MSP Basic + 0xFFFFFFFD Thread mode PSP Basic + + So LR[2] == 1 => Return stack == PSP else MSP + + R0-R3, R12, PC, xPSR can be written via application stackpointer + Other regs can be written directly + + + Read register data from J-Link into R0 + */ + LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] // _SYSTEM_DEMCR |= (1uL << 19); => Monitor is ready to receive register data + ORR R0,R0,#0x80000 + STR R0,[R3, #+_SYSTEM_DEMCR_OFF] +_HandleWRegWaitUntilDataRecv: + LDR R0,[R3, #+_SYSTEM_DEMCR_OFF] + LSLS R0,R0,#+12 + BMI.N _HandleWRegWaitUntilDataRecv // DEMCR[19] == 0 => J-Link has placed new data for us + LDR R0,[R3, #+_SYSTEM_DCRDR_OFF] // Get register data + // + // Determine application SP + // + LSRS R2,LR,#+3 // Shift LR[2] into carry => Carry clear means that CPU was running on MSP + ITE CS + MRSCS R2,PSP + MRSCC R2,MSP + CMP R1,#+4 // if (RegIndex < 4) { (R0-R3) + BCS _HandleWriteRegR4 + STR R0,[R2, R1, LSL #+2] // v = [SP + Rx * 4] (R0-R3) + B.N _HandleWriteRegDone +_HandleWriteRegR4: + CMP R1,#+5 // if (RegIndex < 5) { (R4) + BCS _HandleWriteRegR5 + MOV R4,R0 + B.N _HandleWriteRegDone +_HandleWriteRegR5: + CMP R1,#+6 // if (RegIndex < 6) { (R5) + BCS _HandleWriteRegR6 + MOV R5,R0 + B.N _HandleWriteRegDone +_HandleWriteRegR6: + CMP R1,#+7 // if (RegIndex < 7) { (R6) + BCS _HandleWriteRegR7 + MOV R6,R0 + B.N _HandleWriteRegDone +_HandleWriteRegR7: + CMP R1,#+8 // if (RegIndex < 8) { (R7) + BCS _HandleWriteRegR8 + MOV R7,R0 + B.N _HandleWriteRegDone +_HandleWriteRegR8: + CMP R1,#+9 // if (RegIndex < 9) { (R8) + BCS _HandleWriteRegR9 + MOV R8,R0 + B.N _HandleWriteRegDone +_HandleWriteRegR9: + CMP R1,#+10 // if (RegIndex < 10) { (R9) + BCS _HandleWriteRegR10 + MOV R9,R0 + B.N _HandleWriteRegDone +_HandleWriteRegR10: + CMP R1,#+11 // if (RegIndex < 11) { (R10) + BCS _HandleWriteRegR11 + MOV R10,R0 + B.N _HandleWriteRegDone +_HandleWriteRegR11: + CMP R1,#+12 // if (RegIndex < 12) { (R11) + BCS _HandleWriteRegR12 + MOV R11,R0 + B.N _HandleWriteRegDone +_HandleWriteRegR12: + CMP R1,#+14 // if (RegIndex < 14) { (R12) + BCS _HandleWriteRegR14 + STR R0,[R2, #+_APP_SP_OFF_R12] + B.N _HandleWriteRegDone +_HandleWriteRegR14: + CMP R1,#+15 // if (RegIndex < 15) { (R14 / LR) + BCS _HandleWriteRegR15 + STR R0,[R2, #+_APP_SP_OFF_R14_LR] + B.N _HandleWriteRegDone +_HandleWriteRegR15: + CMP R1,#+16 // if (RegIndex < 16) { (R15 / PC) + BCS _HandleWriteRegXPSR + STR R0,[R2, #+_APP_SP_OFF_PC] + B.N _HandleWriteRegDone +_HandleWriteRegXPSR: + CMP R1,#+17 // if (RegIndex < 17) { (xPSR) + BCS _HandleWriteRegMSP + STR R0,[R2, #+_APP_SP_OFF_XPSR] + B.N _HandleWriteRegDone +_HandleWriteRegMSP: + // + // For now, SP cannot be modified because it is needed to jump back from monitor mode + // + CMP R1,#+18 // if (RegIndex < 18) { (MSP) + BCS _HandleWriteRegPSP + B.N _HandleWriteRegDone +_HandleWriteRegPSP: // RegIndex == 18 + CMP R1,#+19 // if (RegIndex < 19) { + BCS _HandleWriteRegCFBP + B.N _HandleWriteRegDone +_HandleWriteRegCFBP: + /* + CFBP is a register that can only be read via debug probe and is a merger of the following regs: + CONTROL/FAULTMASK/BASEPRI/PRIMASK (packed into 4 bytes of word. CONTROL = CFBP[31:24], FAULTMASK = CFBP[16:23], BASEPRI = CFBP[15:8], PRIMASK = CFBP[7:0] + To keep J-Link side the same for monitor and halt mode, we also return CFBP in monitor mode + */ + CMP R1,#+20 // if (RegIndex < 20) { (CFBP) + BCS _HandleWriteRegFPU + LSLS R1,R0,#+24 + LSRS R1,R1,#+24 // Extract CFBP[7:0] => PRIMASK + MSR PRIMASK,R1 + LSLS R1,R0,#+16 + LSRS R1,R1,#+24 // Extract CFBP[15:8] => BASEPRI + MSR BASEPRI,R1 + LSLS R1,R0,#+8 // Extract CFBP[23:16] => FAULTMASK + LSRS R1,R1,#+24 + MSR FAULTMASK,R1 + LSRS R1,R0,#+24 // Extract CFBP[31:24] => CONTROL + LSRS R0,R1,#2 // Current CONTROL[1] -> Carry + ITE CS // Update saved CONTROL.SPSEL (CONTROL[1]). CONTROL.SPSEL is saved to LR[2] on exception entry => ARM DDI0403D, B1.5.6 Exception entry behavior + ORRCS LR,LR,#+4 + BICCC LR,LR,#+4 + BIC R1,R1,#+2 // CONTROL.SPSEL (CONTROL[1]) == 0 inside monitor. Otherwise behavior is UNPREDICTABLE + LSRS R0,R1,#+3 // New CONTROL.FPCA (CONTROL[2]) -> Carry + ITE CS // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack + BICCS LR,LR,#+0x10 // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame + ORRCC LR,LR,#+0x10 + MRS R0,CONTROL + LSRS R0,R0,#+3 // CONTROL[2] -> Carry + ITE CS // Preserve original value of current CONTROL[2] + ORRCS R1,R1,#+0x04 + BICCC R1,R1,#+0x04 + MSR CONTROL,R1 + ISB // Necessary after writing to CONTROL, see ARM DDI0403D, B1.4.4 The special-purpose CONTROL register + B.N _HandleWriteRegDone +_HandleWriteRegFPU: +#if _HAS_FPU_REGS + CMP R1,#+53 // if (RegIndex < 53) { (20 (FPSCR), 21-52 FPS0-FPS31) + BCS _HandleWriteRegDone_Veneer + /* + Read Coprocessor Access Control Register (CPACR) to check if CP10 and CP11 are enabled + If not, access to floating point is not possible + CPACR[21:20] == CP10 enable. 0b01 = Privileged access only. 0b11 = Full access. Other = reserved + CPACR[23:22] == CP11 enable. 0b01 = Privileged access only. 0b11 = Full access. Other = reserved + */ + MOV R12,R0 // Save register data + LDR R0,_AddrCPACR + LDR R0,[R0] + LSLS R0,R0,#+8 + LSRS R0,R0,#+28 + CMP R0,#+0xF + BEQ _HandleWriteRegFPU_Allowed + CMP R0,#+0x5 + BNE _HandleWriteRegDone_Veneer +_HandleWriteRegFPU_Allowed: + CMP R1,#+21 // if (RegIndex < 21) (20 == FPSCR) + BCS _HandleWriteRegFPS0_FPS31 + LSRS R0,LR,#+5 // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack + BCS _HandleWriteFPSCRLazyMode // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame + LDR R0,=_SYSTEM_FPCCR + LDR R0,[R0] + LSLS R0,R0,#+2 // FPCCR[30] -> Carry == 1 indicates if lazy mode is active, so space on stack is reserved but FPU registers are not saved on stack + BCS _HandleWriteFPSCRLazyMode + STR R12,[R2, #+_APP_SP_OFF_FPSCR] + B _HandleWriteRegDone +_HandleWriteFPSCRLazyMode: + VMSR FPSCR,R12 + B _HandleWriteRegDone +_HandleWriteRegFPS0_FPS31: // RegIndex == 21-52 + LDR R0,=_SYSTEM_FPCCR + LDR R0,[R0] + LSLS R0,R0,#+2 // FPCCR[30] -> Carry == 1 indicates if lazy mode is active, so space on stack is reserved but FPU registers are not saved on stack + BCS _HandleWriteFPS0_FPS31LazyMode + LSRS R0,LR,#+5 // CONTROL[2] == FPCA => NOT(FPCA) saved to LR[4]. LR[4] == 0 => Extended stack frame, so FPU regs possibly on stack + BCS _HandleWriteFPS0_FPS31LazyMode // Remember: NOT(FPCA) is stored to LR. == 0 means: Extended stack frame + SUBS R1,#+21 // Convert absolute reg index into rel. one + LSLS R1,R1,#+2 // RegIndex to position on stack + ADDS R1,#+_APP_SP_OFF_S0 + STR R12,[R2, R1] +_HandleWriteRegDone_Veneer: + B _HandleWriteRegDone +_HandleWriteFPS0_FPS31LazyMode: + SUBS R1,#+20 // Convert abs. RegIndex into rel. one + MOVS R0,#+6 + MULS R1,R0,R1 + LDR R0,=_HandleReadRegUnknown + SUB R0,R0,R1 // _HandleReadRegUnknown - 6 * ((RegIndex - 21) + 1) + ORR R0,R0,#1 // Thumb bit needs to be set in DestAddr + BX R0 + // + // Table for reading FPS0-FPS31 + // + VMOV S31,R12 // v = FPSx + B _HandleWriteRegDone + VMOV S30,R12 + B _HandleWriteRegDone + VMOV S29,R12 + B _HandleWriteRegDone + VMOV S28,R12 + B _HandleWriteRegDone + VMOV S27,R12 + B _HandleWriteRegDone + VMOV S26,R12 + B _HandleWriteRegDone + VMOV S25,R12 + B _HandleWriteRegDone + VMOV S24,R12 + B _HandleWriteRegDone + VMOV S23,R12 + B _HandleWriteRegDone + VMOV S22,R12 + B _HandleWriteRegDone + VMOV S21,R12 + B _HandleWriteRegDone + VMOV S20,R12 + B _HandleWriteRegDone + VMOV S19,R12 + B _HandleWriteRegDone + VMOV S18,R12 + B _HandleWriteRegDone + VMOV S17,R12 + B _HandleWriteRegDone + VMOV S16,R12 + B _HandleWriteRegDone + VMOV S15,R12 + B _HandleWriteRegDone + VMOV S14,R12 + B _HandleWriteRegDone + VMOV S13,R12 + B _HandleWriteRegDone + VMOV S12,R12 + B _HandleWriteRegDone + VMOV S11,R12 + B _HandleWriteRegDone + VMOV S10,R12 + B _HandleWriteRegDone + VMOV S9,R12 + B _HandleWriteRegDone + VMOV S8,R12 + B _HandleWriteRegDone + VMOV S7,R12 + B _HandleWriteRegDone + VMOV S6,R12 + B _HandleWriteRegDone + VMOV S5,R12 + B _HandleWriteRegDone + VMOV S4,R12 + B _HandleWriteRegDone + VMOV S3,R12 + B _HandleWriteRegDone + VMOV S2,R12 + B _HandleWriteRegDone + VMOV S1,R12 + B _HandleWriteRegDone + VMOV S0,R12 + B _HandleWriteRegDone +#else + B _HandleWriteRegUnknown +#endif +_HandleWriteRegUnknown: + B.N _HandleWriteRegDone +_HandleWriteRegDone: + B _IndicateMonReady // Indicate that monitor has read data, processed command and is ready for a new one + .end +/****** End Of File *************************************************/ diff --git a/src/nrf52/main-nrf52.cpp b/src/nrf52/main-nrf52.cpp index 6e0b486e0..559a28cf4 100644 --- a/src/nrf52/main-nrf52.cpp +++ b/src/nrf52/main-nrf52.cpp @@ -75,6 +75,10 @@ void nrf52Setup() // per https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fpower.html DEBUG_MSG("Reset reason: 0x%x\n", why); + // Per https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/monitor-mode-debugging-with-j-link-and-gdbeclipse + // This is the recommended setting for Monitor Mode Debugging + NVIC_SetPriority(DebugMonitor_IRQn, 6UL); + // Not yet on board // pmu.init(); DEBUG_MSG("FIXME, need to call randomSeed on nrf52!\n"); From a96c8fd4db69af4421bdb85424db30958a36dbd1 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 25 May 2020 17:16:09 -0700 Subject: [PATCH 061/131] nrf52 debugging is supported as long as BLE is not advertising --- platformio.ini | 2 +- src/nrf52/NRF52Bluetooth.cpp | 14 ++++++++++---- src/nrf52/main-nrf52.cpp | 5 +---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/platformio.ini b/platformio.ini index 042d4ed2e..d06a02f5e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = tbeam ; Note: the github actions CI test build can't yet build NRF52 targets +default_envs = nrf52dk ; Note: the github actions CI test build can't yet build NRF52 targets [common] ; common is not currently used diff --git a/src/nrf52/NRF52Bluetooth.cpp b/src/nrf52/NRF52Bluetooth.cpp index 9cf6e3708..22ee69dc2 100644 --- a/src/nrf52/NRF52Bluetooth.cpp +++ b/src/nrf52/NRF52Bluetooth.cpp @@ -148,6 +148,9 @@ void setupHRM(void) bslc.write8(2); // Set the characteristic to 'Wrist' (2) } +// FIXME, turn off soft device access for debugging +static bool isSoftDeviceAllowed = false; + void NRF52Bluetooth::setup() { // Initialise the Bluefruit module @@ -179,11 +182,14 @@ void NRF52Bluetooth::setup() DEBUG_MSG("Configuring the Heart Rate Monitor Service\n"); setupHRM(); - // Setup the advertising packet(s) - DEBUG_MSG("Setting up the advertising payload(s)\n"); - startAdv(); + // Supposedly debugging works with soft device if you disable advertising + if (isSoftDeviceAllowed) { + // Setup the advertising packet(s) + DEBUG_MSG("Setting up the advertising payload(s)\n"); + startAdv(); - DEBUG_MSG("Advertising\n"); + DEBUG_MSG("Advertising\n"); + } } /* diff --git a/src/nrf52/main-nrf52.cpp b/src/nrf52/main-nrf52.cpp index 559a28cf4..c7e5a38c8 100644 --- a/src/nrf52/main-nrf52.cpp +++ b/src/nrf52/main-nrf52.cpp @@ -43,15 +43,12 @@ void getMacAddr(uint8_t *dmac) NRF52Bluetooth *nrf52Bluetooth; -// FIXME, turn off soft device access for debugging -static bool isSoftDeviceAllowed = false; - static bool bleOn = false; void setBluetoothEnable(bool on) { if (on != bleOn) { if (on) { - if (!nrf52Bluetooth && isSoftDeviceAllowed) { + if (!nrf52Bluetooth) { nrf52Bluetooth = new NRF52Bluetooth(); nrf52Bluetooth->setup(); } From f3a1c5e6791c6473817a14eff321fa3a9e8cea15 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 26 May 2020 13:10:34 -0700 Subject: [PATCH 062/131] Possible fix for https://meshtastic.discourse.group/t/a-note-about-limited-support-for-the-neo-8m-gps-boards/233/3?u=geeksville --- 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 ca8f955c5..cd400fe70 100644 --- a/src/gps/UBloxGPS.cpp +++ b/src/gps/UBloxGPS.cpp @@ -49,7 +49,7 @@ bool UBloxGPS::setup() // assert(ok); // ok = ublox.setDynamicModel(DYN_MODEL_BIKE); // probably PEDESTRIAN but just in case assume bike speeds // assert(ok); - ok = ublox.powerSaveMode(); // use power save mode + ok = ublox.powerSaveMode(true, 2000); // use power save mode, the default timeout (1100ms seems a bit too tight) assert(ok); } ok = ublox.saveConfiguration(3000); From 6a3033fa85372cf0cf9a6708798073ad075882e4 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 26 May 2020 15:55:36 -0700 Subject: [PATCH 063/131] improve NRF52 debugging environment --- bin/nrf52-console.sh | 5 ++++- bin/nrf52-gdbserver.sh | 3 +++ docs/hardware/nrf52/nrf52-programming.png | Bin 0 -> 145798 bytes gdbinit | 5 +++++ platformio.ini | 3 +++ 5 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 bin/nrf52-gdbserver.sh create mode 100644 docs/hardware/nrf52/nrf52-programming.png diff --git a/bin/nrf52-console.sh b/bin/nrf52-console.sh index c7ad903c1..55c586b8c 100755 --- a/bin/nrf52-console.sh +++ b/bin/nrf52-console.sh @@ -1 +1,4 @@ -JLinkRTTViewer + + +# JLinkRTTViewer +JLinkRTTClient \ No newline at end of file diff --git a/bin/nrf52-gdbserver.sh b/bin/nrf52-gdbserver.sh new file mode 100644 index 000000000..15fb8fbff --- /dev/null +++ b/bin/nrf52-gdbserver.sh @@ -0,0 +1,3 @@ + + +JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52840_XXAA diff --git a/docs/hardware/nrf52/nrf52-programming.png b/docs/hardware/nrf52/nrf52-programming.png new file mode 100644 index 0000000000000000000000000000000000000000..b99a2691c8eace1da708c23b8bce63ad586bc276 GIT binary patch literal 145798 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
ebofc3LO- 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 literal 0 HcmV?d00001 diff --git a/gdbinit b/gdbinit index 977bed518..bb3458136 100644 --- a/gdbinit +++ b/gdbinit @@ -6,9 +6,14 @@ # mon exec SetMonModeDebug=1 # mon exec SetMonModeVTableAddr=0x26000 +echo setting RTTAddr +eval "monitor exec SetRTTAddr %p", &_SEGGER_RTT + # the jlink debugger seems to want a pause after reset before we tell it to start running define restart + echo Restarting monitor reset shell sleep 1 cont end + diff --git a/platformio.ini b/platformio.ini index d06a02f5e..61d69d3e0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -142,6 +142,9 @@ monitor_port = /dev/ttyACM1 debug_extra_cmds = source gdbinit +; after programming the flash, reset the initial PC +; debug_load_cmds = load + ; Set initial breakpoint (defaults to main) debug_init_break = ;debug_init_break = tbreak loop From da2ef0ac6190828450c19b7307aaac4d3b8f8e2a Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 27 May 2020 15:31:23 -0700 Subject: [PATCH 064/131] misc nrf52 todo --- docs/software/nrf52-TODO.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 1549dfe8c..137bcb04e 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -6,12 +6,15 @@ Minimum items needed to make sure hardware is good. +- set power UICR per https://devzone.nordicsemi.com/f/nordic-q-a/28562/nrf52840-regulator-configuration +- switch charge controller into / out of performance mode (see 8.3.1 in datasheet) - write UC1701 wrapper - Test hardfault handler for null ptrs (if one isn't already installed) - test my hackedup bootloader on the real hardware - 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. Make sure SPI is atomic. +- set vbus voltage per https://infocenter.nordicsemi.com/topic/ps_nrf52840/power.html?cp=4_0_0_4_2 - test the LEDs - test the buttons @@ -33,6 +36,7 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At - make ble endpoints not require "start config", just have them start in config mode - use new PMU to provide battery voltage/% full to app (both bluetooth and screen) - do initial power measurements, measure effects of more preamble bits, measure power management and confirm battery life +- set UICR.CUSTOMER to indicate board model & version ## Items to be 'feature complete' @@ -50,6 +54,7 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At - currently using soft device SD140, is that ideal? - turn on the watchdog timer, require servicing from key application threads - nrf52setup should call randomSeed(tbd) +- implement SYSTEMOFF behavior per https://infocenter.nordicsemi.com/topic/ps_nrf52840/power.html?cp=4_0_0_4_2 ## Things to do 'someday' From f56ff2ca2081d9e851a28c2cb43d0b683fd76555 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 27 May 2020 15:31:32 -0700 Subject: [PATCH 065/131] DSR WIP --- src/main.cpp | 5 ++--- src/mesh/DSRRouter.cpp | 21 +++++++++++++++++++-- src/mesh/ReliableRouter.cpp | 4 +++- src/mesh/ReliableRouter.h | 10 +++++----- src/nrf52/main-nrf52.cpp | 9 ++++++++- 5 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index dc6fe7ad6..d7fb21bd1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,13 +27,12 @@ #include "NodeDB.h" #include "Periodic.h" #include "PowerFSM.h" -#include "Router.h" #include "UBloxGPS.h" #include "configuration.h" #include "error.h" #include "power.h" // #include "rom/rtc.h" -#include "ReliableRouter.h" +#include "DSRRouter.h" #include "main.h" #include "screen.h" #include "sleep.h" @@ -53,7 +52,7 @@ meshtastic::PowerStatus powerStatus; bool ssd1306_found; bool axp192_found; -ReliableRouter realRouter; +DSRRouter realRouter; Router &router = realRouter; // Users of router don't care what sort of subclass implements that API // ----------------------------------------------------------------------------- diff --git a/src/mesh/DSRRouter.cpp b/src/mesh/DSRRouter.cpp index cb22c5467..7d788441e 100644 --- a/src/mesh/DSRRouter.cpp +++ b/src/mesh/DSRRouter.cpp @@ -38,9 +38,26 @@ when we receive a routeError packet ErrorCode DSRRouter::send(MeshPacket *p) { - // If we have an entry in our routing tables, just send it, otherwise start a route discovery + // We only consider multihop routing packets (i.e. those with dest set) + if (p->decoded.dest) { + // add an entry for this pending message + auto pending = startRetransmission(p); + // FIXME - when acks come in for this packet, we should _not_ delete the record unless the ack was from + // the final dest. We need to keep that record around until FIXME + // Also we should not retransmit multihop entries in that table at all - return ReliableRouter::send(p); + // If we have an entry in our routing tables, just send it, otherwise start a route discovery + NodeNum nextHop = getNextHop(p->decoded.dest); + if (nextHop) { + sendNextHop(nextHop, p); // start a reliable single hop send + } else { + pending->wantRoute = true; + + // start discovery, but only if we don't already a discovery in progress for that node number + startDiscovery(p->decoded.dest); + } + } else + return ReliableRouter::send(p); } void DSRRouter::sniffReceived(const MeshPacket *p) diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 603768a06..3499c51b6 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -146,13 +146,15 @@ bool ReliableRouter::stopRetransmission(GlobalPacketId key) /** * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. */ -void ReliableRouter::startRetransmission(MeshPacket *p) +PendingPacket *ReliableRouter::startRetransmission(MeshPacket *p) { auto id = GlobalPacketId(p); auto rec = PendingPacket(p); stopRetransmission(p->from, p->id); pending[id] = rec; + + return &pending[id]; } /** diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h index d984ea118..51cb587a8 100644 --- a/src/mesh/ReliableRouter.h +++ b/src/mesh/ReliableRouter.h @@ -103,6 +103,11 @@ class ReliableRouter : public FloodingRouter */ virtual bool shouldFilterReceived(const MeshPacket *p); + /** + * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. + */ + PendingPacket *startRetransmission(MeshPacket *p); + private: /** * Send an ack or a nak packet back towards whoever sent idFrom @@ -117,11 +122,6 @@ class ReliableRouter : public FloodingRouter bool stopRetransmission(NodeNum from, PacketId id); bool stopRetransmission(GlobalPacketId p); - /** - * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. - */ - void startRetransmission(MeshPacket *p); - /** * Do any retransmissions that are scheduled (FIXME - for the time being called from loop) */ diff --git a/src/nrf52/main-nrf52.cpp b/src/nrf52/main-nrf52.cpp index c7e5a38c8..ce3fe7e31 100644 --- a/src/nrf52/main-nrf52.cpp +++ b/src/nrf52/main-nrf52.cpp @@ -78,5 +78,12 @@ void nrf52Setup() // Not yet on board // pmu.init(); - DEBUG_MSG("FIXME, need to call randomSeed on nrf52!\n"); + + // Init random seed + // FIXME - use this to get random numbers + // #include "nrf_rng.h" + // uint32_t r; + // ble_controller_rand_vector_get_blocking(&r, sizeof(r)); + // randomSeed(r); + DEBUG_MSG("FIXME, call randomSeed\n"); } \ No newline at end of file From 313380381b8b40ae3a139addd0a5dbf8754f6647 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 27 May 2020 15:40:47 -0700 Subject: [PATCH 066/131] no need for this old debug output --- src/sleep.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sleep.cpp b/src/sleep.cpp index 4b8db06b0..270ecc5cc 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -273,7 +273,7 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); if (cause == ESP_SLEEP_WAKEUP_GPIO) - DEBUG_MSG("Exit light sleep gpio: btn=%d, rf95=%d\n", !digitalRead(BUTTON_PIN), digitalRead(RF95_IRQ_GPIO)); + DEBUG_MSG("Exit light sleep gpio: btn=%d\n", !digitalRead(BUTTON_PIN)); return cause; } From 1b34a0c6d8190689d9f9c2fcc1aebcd625ec5802 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 27 May 2020 15:47:59 -0700 Subject: [PATCH 067/131] Help make sx1262 go for @dafeman 's board. See below for details: Hi, I think the problem you were having building for ESP32 was due to a funny thing. Notice the #define for INTERRUPT_ATTR. That macro expands to IRAM_ATTR - which is a special flag the ESP32 requires for _any_ code that is going to be called from an ISR. So that the code is guaranteed to be in RAM (the ESP32 uses a clever scheme where the FLASH is actually high speed serial flash and all reads/writes are actually only happening to a small number of pages in RAM and they have a driver that is constantly copying blocks they need into that ram. This essentially how VM works for desktop computers, but in their case they are paging to FLASH. But for code that runs in an interrupt handler must _always_ be in RAM because if you took a 'page fault' for that code being missing in RAM they can't nicely do their clever VM scheme. So that's all good. The problem was - apparently GCC for the ESP32 has a a bug when that attribute is applied in the class declaration. So I moved it out into the cpp file and all seems well now. --- src/mesh/SX1262Interface.cpp | 5 +++++ src/mesh/SX1262Interface.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/mesh/SX1262Interface.cpp b/src/mesh/SX1262Interface.cpp index dadd98d0f..69e7f8ee7 100644 --- a/src/mesh/SX1262Interface.cpp +++ b/src/mesh/SX1262Interface.cpp @@ -71,6 +71,11 @@ bool SX1262Interface::reconfigure() return ERR_NONE; } +void INTERRUPT_ATTR SX1262Interface::disableInterrupt() +{ + lora.clearDio1Action(); +} + void SX1262Interface::setStandby() { int err = lora.standby(); diff --git a/src/mesh/SX1262Interface.h b/src/mesh/SX1262Interface.h index c1e1fb1c6..92b301bfc 100644 --- a/src/mesh/SX1262Interface.h +++ b/src/mesh/SX1262Interface.h @@ -29,7 +29,7 @@ class SX1262Interface : public RadioLibInterface /** * Glue functions called from ISR land */ - virtual void INTERRUPT_ATTR disableInterrupt() { lora.clearDio1Action(); } + virtual void disableInterrupt(); /** * Enable a particular ISR callback glue function From e522e47544f446760ea55ab5b200d6bf0bb9b9b9 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 3 Jun 2020 12:49:36 -0700 Subject: [PATCH 068/131] Full DSR WIP --- docs/software/mesh-alg.md | 1 + src/mesh/DSRRouter.cpp | 93 +++++++++++++++++++++++++++++++++++++++ src/mesh/DSRRouter.h | 6 +++ 3 files changed, 100 insertions(+) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index dc4203aba..7d7837be9 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -30,6 +30,7 @@ dsr tasks optimizations / low priority: +- read @cyclomies long email with good ideas on optimizations and reply - 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 diff --git a/src/mesh/DSRRouter.cpp b/src/mesh/DSRRouter.cpp index 7d788441e..f1ec32e32 100644 --- a/src/mesh/DSRRouter.cpp +++ b/src/mesh/DSRRouter.cpp @@ -56,6 +56,8 @@ ErrorCode DSRRouter::send(MeshPacket *p) // start discovery, but only if we don't already a discovery in progress for that node number startDiscovery(p->decoded.dest); } + + return ERRNO_OK; } else return ReliableRouter::send(p); } @@ -149,4 +151,95 @@ void DSRRouter::sniffReceived(const MeshPacket *p) } return ReliableRouter::sniffReceived(p); +} + +/** + * Does our node appear in the specified route + */ +bool DSRRouter::weAreInRoute(const RouteDiscovery &route) +{ + return true; // FIXME +} + +/** + * Given a DSR route, use that route to update our DB of possible routes + * + * Note: routes are always listed in the same order - from sender to receipient (i.e. route_replies also use this some order) + * + * @param isRequest is true if we are looking at a route request, else we are looking at a reply + **/ +void DSRRouter::updateRoutes(const RouteDiscovery &route, bool isRequest) +{ + DEBUG_MSG("FIXME not implemented"); +} + +/** + * send back a route reply (the sender address will be first in the list) + */ +void DSRRouter::sendRouteReply(const RouteDiscovery &route, NodeNum toAppend) +{ + DEBUG_MSG("FIXME not implemented"); +} + +/** + * Given a nodenum return the next node we should forward to if we want to reach that node. + * + * @return 0 if no route found + */ +NodeNum DSRRouter::getNextHop(NodeNum dest) +{ + DEBUG_MSG("FIXME not implemented"); + return 0; +} + +/** Not in our route cache, rebroadcast on their behalf (after adding ourselves to the request route) + * + * We will bump down hop_limit in this call. + */ +void DSRRouter::resendRouteRequest(const MeshPacket *p) +{ + DEBUG_MSG("FIXME not implemented"); +} + +/** + * Record that forwarder can reach dest for us, but they will need numHops to get there. + * If our routing tables already have something that can reach that node in fewer hops we will keep the existing route + * instead. + */ +void DSRRouter::addRoute(NodeNum dest, NodeNum forwarder, uint8_t numHops) +{ + DEBUG_MSG("FIXME not implemented"); +} + +/** + * Record that we no longer have a route to the dest + */ +void DSRRouter::removeRoute(NodeNum dest) +{ + DEBUG_MSG("FIXME not implemented"); +} + +/** + * Forward the specified packet to the specified node + */ +void DSRRouter::sendNextHop(NodeNum n, const MeshPacket *p) +{ + DEBUG_MSG("FIXME not implemented"); +} + +/** + * Send a route error packet towards whoever originally sent this message + */ +void DSRRouter::sendRouteError(const MeshPacket *p, RouteError err) +{ + DEBUG_MSG("FIXME not implemented"); +} + +/** make a copy of p, start discovery, but only if we don't + * already a discovery in progress for that node number. Caller has already scheduled this message for retransmission + * when the discovery is complete. + */ +void DSRRouter::startDiscovery(NodeNum dest) +{ + DEBUG_MSG("FIXME not implemented"); } \ No newline at end of file diff --git a/src/mesh/DSRRouter.h b/src/mesh/DSRRouter.h index 904f99980..3bc461571 100644 --- a/src/mesh/DSRRouter.h +++ b/src/mesh/DSRRouter.h @@ -71,4 +71,10 @@ class DSRRouter : public ReliableRouter * Send a route error packet towards whoever originally sent this message */ void sendRouteError(const MeshPacket *p, RouteError err); + + /** make a copy of p, start discovery, but only if we don't + * already a discovery in progress for that node number. Caller has already scheduled this message for retransmission + * when the discovery is complete. + */ + void startDiscovery(NodeNum dest); }; \ No newline at end of file From 8031c47602df7a3fdebfbfc135481d4a0c72bfd4 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 3 Jun 2020 12:55:55 -0700 Subject: [PATCH 069/131] put nrf52 on back burner for a couple of days --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 61d69d3e0..c51e0954e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = nrf52dk ; Note: the github actions CI test build can't yet build NRF52 targets +default_envs = tbeam ; Note: the github actions CI test build can't yet build NRF52 targets [common] ; common is not currently used From a34cfb0ee0b3df6ba4b62261eb0bb7cbae7054f9 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 3 Jun 2020 13:15:45 -0700 Subject: [PATCH 070/131] Populate metainfo for apps to allow 32 bit node and packet ids --- proto | 2 +- src/mesh/NodeDB.cpp | 6 ++++++ src/mesh/PacketHistory.cpp | 3 +-- src/mesh/PacketHistory.h | 3 +++ src/mesh/Router.cpp | 10 ++++++---- src/mesh/mesh.pb.h | 22 +++++++++++++++++----- 6 files changed, 34 insertions(+), 12 deletions(-) diff --git a/proto b/proto index adf4127fe..e9c7f9b95 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit adf4127fe3e4140bfd97b48a2d11b53ee34a16c8 +Subproject commit e9c7f9b95d490aea3f0f213d4666d2dbf7e2111c diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index f46d1d8d3..faa035930 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -8,7 +8,9 @@ #include "CryptoEngine.h" #include "GPS.h" #include "NodeDB.h" +#include "PacketHistory.h" #include "PowerFSM.h" +#include "Router.h" #include "configuration.h" #include "error.h" #include "mesh-pb-constants.h" @@ -122,6 +124,10 @@ void NodeDB::init() // default to no GPS, until one has been found by probing myNodeInfo.has_gps = false; + myNodeInfo.node_num_bits = sizeof(NodeNum) * 8; + myNodeInfo.packet_id_bits = sizeof(PacketId) * 8; + myNodeInfo.message_timeout_msec = FLOOD_EXPIRE_TIME; + generatePacketId(); // FIXME - ugly way to init current_packet_id; // Init our blank owner info to reasonable defaults getMacAddr(ourMacAddr); diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index 11accf4c5..75005d408 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -2,8 +2,7 @@ #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) + PacketHistory::PacketHistory() { diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h index 38edf7d77..3cc81084f 100644 --- a/src/mesh/PacketHistory.h +++ b/src/mesh/PacketHistory.h @@ -6,6 +6,9 @@ using namespace std; +/// We clear our old flood record five minute after we see the last of it +#define FLOOD_EXPIRE_TIME (5 * 60 * 1000L) + /** * A record of a recent message broadcast */ diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 5abf73cb5..dc7409a07 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -44,8 +44,6 @@ void Router::loop() } } -#define NUM_PACKET_ID 255 // 0 is consider invalid - /// Generate a unique packet id // FIXME, move this someplace better PacketId generatePacketId() @@ -53,14 +51,18 @@ PacketId generatePacketId() static uint32_t i; // Note: trying to keep this in noinit didn't help for working across reboots static bool didInit = false; + uint32_t numPacketId = 255; // 0 is consider invalid + if (!didInit) { didInit = true; - i = random(0, NUM_PACKET_ID + + i = random(0, numPacketId + 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 + PacketId id = (i % numPacketId) + 1; // return number between 1 and 255 (ie - never zero) + myNodeInfo.current_packet_id = id; // Kinda crufty - we keep updating this so the phone can see a current value + return id; } MeshPacket *Router::allocForSending() diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index fe9cd575c..ae7edb1ff 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -74,6 +74,10 @@ typedef struct _MyNodeInfo { uint32_t error_code; uint32_t error_address; uint32_t error_count; + uint32_t packet_id_bits; + uint32_t current_packet_id; + uint32_t node_num_bits; + uint32_t message_timeout_msec; } MyNodeInfo; typedef struct _Position { @@ -238,7 +242,7 @@ typedef struct _ToRadio { #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, 0, {0, 0, 0}} #define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0} -#define MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0} +#define MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0} #define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default, 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 {""} #define FromRadio_init_default {0, 0, {MeshPacket_init_default}} @@ -254,7 +258,7 @@ typedef struct _ToRadio { #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, 0, {0, 0, 0}} #define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0} -#define MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0} +#define MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0} #define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero, 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 {""} #define FromRadio_init_zero {0, 0, {MeshPacket_init_zero}} @@ -282,6 +286,10 @@ typedef struct _ToRadio { #define MyNodeInfo_error_code_tag 7 #define MyNodeInfo_error_address_tag 8 #define MyNodeInfo_error_count_tag 9 +#define MyNodeInfo_packet_id_bits_tag 10 +#define MyNodeInfo_current_packet_id_tag 11 +#define MyNodeInfo_node_num_bits_tag 12 +#define MyNodeInfo_message_timeout_msec_tag 13 #define Position_latitude_i_tag 7 #define Position_longitude_i_tag 8 #define Position_altitude_tag 3 @@ -472,7 +480,11 @@ X(a, STATIC, SINGULAR, STRING, hw_model, 5) \ X(a, STATIC, SINGULAR, STRING, firmware_version, 6) \ X(a, STATIC, SINGULAR, UINT32, error_code, 7) \ X(a, STATIC, SINGULAR, UINT32, error_address, 8) \ -X(a, STATIC, SINGULAR, UINT32, error_count, 9) +X(a, STATIC, SINGULAR, UINT32, error_count, 9) \ +X(a, STATIC, SINGULAR, UINT32, packet_id_bits, 10) \ +X(a, STATIC, SINGULAR, UINT32, current_packet_id, 11) \ +X(a, STATIC, SINGULAR, UINT32, node_num_bits, 12) \ +X(a, STATIC, SINGULAR, UINT32, message_timeout_msec, 13) #define MyNodeInfo_CALLBACK NULL #define MyNodeInfo_DEFAULT NULL @@ -580,8 +592,8 @@ extern const pb_msgdesc_t ManufacturingData_msg; #define RadioConfig_size 157 #define RadioConfig_UserPreferences_size 93 #define NodeInfo_size 132 -#define MyNodeInfo_size 80 -#define DeviceState_size 15433 +#define MyNodeInfo_size 104 +#define DeviceState_size 15457 #define DebugString_size 258 #define FromRadio_size 333 #define ToRadio_size 327 From 5b1488ddf07715601f812eaf2791df2db8124ff3 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 3 Jun 2020 13:46:31 -0700 Subject: [PATCH 071/131] Allow packet and nodenums to be 32 bits long (but don't change yet) --- docs/software/mesh-alg.md | 3 +++ src/mesh/MeshTypes.h | 2 +- src/mesh/NodeDB.cpp | 6 ++++-- src/mesh/RadioInterface.cpp | 2 +- src/mesh/RadioInterface.h | 4 +++- src/mesh/Router.cpp | 5 +++-- 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index 7d7837be9..ecad282ab 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -31,6 +31,9 @@ dsr tasks optimizations / low priority: - read @cyclomies long email with good ideas on optimizations and reply +- Remove NodeNum assignment algorithm (now that we use 4 byte node nums) +- make android app warn if firmware is too old or too new to talk to +- change nodenums and packetids in protobuf to be fixed32 - 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 diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index f491ce508..adba78415 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -9,7 +9,7 @@ typedef uint8_t NodeNum; typedef uint8_t PacketId; // A packet sequence number -#define NODENUM_BROADCAST 255 +#define NODENUM_BROADCAST (sizeof(NodeNum) == 4 ? UINT32_MAX : UINT8_MAX) #define ERRNO_OK 0 #define ERRNO_NO_INTERFACES 33 #define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index faa035930..1e263134e 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -178,8 +178,10 @@ void NodeDB::init() */ void NodeDB::pickNewNodeNum() { - // FIXME not the right way to guess node numes - uint8_t r = ourMacAddr[5]; + // Pick an initial nodenum based on the macaddr + NodeNum r = sizeof(NodeNum) == 1 ? ourMacAddr[5] + : ((ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5]); + if (r == 0xff || r < NUM_RESERVED) r = NUM_RESERVED; // don't pick a reserved node number diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 45ecc42a8..c7edf74de 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -26,7 +26,7 @@ separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts RadioInterface::RadioInterface() : txQueue(MAX_TX_QUEUE) { - assert(sizeof(PacketHeader) == 4); // make sure the compiler did what we expected + assert(sizeof(PacketHeader) == 4 || sizeof(PacketHeader) == 16); // make sure the compiler did what we expected myNodeInfo.num_channels = NUM_CHANNELS; diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 419763750..64222a76a 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -19,7 +19,9 @@ * wtih the old radiohead implementation. */ typedef struct { - uint8_t to, from, id; + NodeNum to, from; // can be 1 byte or four bytes + + PacketId id; // can be 1 byte or 4 bytes /** * Usage of flags: diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index dc7409a07..eb8a1ab32 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -51,7 +51,8 @@ PacketId generatePacketId() static uint32_t i; // Note: trying to keep this in noinit didn't help for working across reboots static bool didInit = false; - uint32_t numPacketId = 255; // 0 is consider invalid + assert(sizeof(PacketId) == 4 || sizeof(PacketId) == 1); // only supported values + uint32_t numPacketId = sizeof(PacketId) == 1 ? UINT8_MAX : UINT32_MAX; // 0 is consider invalid if (!didInit) { didInit = true; @@ -60,7 +61,7 @@ PacketId generatePacketId() } i++; - PacketId id = (i % numPacketId) + 1; // return number between 1 and 255 (ie - never zero) + PacketId id = (i % numPacketId) + 1; // return number between 1 and numPacketId (ie - never zero) myNodeInfo.current_packet_id = id; // Kinda crufty - we keep updating this so the phone can see a current value return id; } From c753ea7cd16a52539a06f2f5d7a2b8469cbb2285 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 3 Jun 2020 13:51:53 -0700 Subject: [PATCH 072/131] don't use a fixed randomSeed. --- src/mesh/NodeDB.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 1e263134e..8f8c0f5b8 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -134,16 +134,13 @@ void NodeDB::init() sprintf(owner.id, "!%02x%02x%02x%02x%02x%02x", ourMacAddr[0], ourMacAddr[1], ourMacAddr[2], ourMacAddr[3], ourMacAddr[4], ourMacAddr[5]); memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr)); - - // make each node start with ad different random seed (but okay that the sequence is the same each boot) - randomSeed((ourMacAddr[2] << 24L) | (ourMacAddr[3] << 16L) | (ourMacAddr[4] << 8L) | ourMacAddr[5]); - sprintf(owner.long_name, "Unknown %02x%02x", ourMacAddr[4], ourMacAddr[5]); - sprintf(owner.short_name, "?%02X", ourMacAddr[5]); // Crummy guess at our nodenum pickNewNodeNum(); + sprintf(owner.short_name, "?%02X", myNodeInfo.my_node_num & 0xff); + // Include our owner in the node db under our nodenum NodeInfo *info = getOrCreateNode(getNodeNum()); info->user = owner; @@ -182,7 +179,7 @@ void NodeDB::pickNewNodeNum() NodeNum r = sizeof(NodeNum) == 1 ? ourMacAddr[5] : ((ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5]); - if (r == 0xff || r < NUM_RESERVED) + if (r == NODENUM_BROADCAST || r < NUM_RESERVED) r = NUM_RESERVED; // don't pick a reserved node number NodeInfo *found; From 49b5738f4f59591837c14d641c7fbdf14efccab7 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 3 Jun 2020 13:57:30 -0700 Subject: [PATCH 073/131] add min_app_version so apps can warn if they are too old --- proto | 2 +- src/mesh/NodeDB.cpp | 1 + src/mesh/mesh.pb.h | 13 ++++++++----- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/proto b/proto index e9c7f9b95..9d083d5d4 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit e9c7f9b95d490aea3f0f213d4666d2dbf7e2111c +Subproject commit 9d083d5d4ff4ef095135b18468004eaba77cb691 diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 8f8c0f5b8..7ffd92e3a 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -127,6 +127,7 @@ void NodeDB::init() myNodeInfo.node_num_bits = sizeof(NodeNum) * 8; myNodeInfo.packet_id_bits = sizeof(PacketId) * 8; myNodeInfo.message_timeout_msec = FLOOD_EXPIRE_TIME; + myNodeInfo.min_app_version = 167; generatePacketId(); // FIXME - ugly way to init current_packet_id; // Init our blank owner info to reasonable defaults diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index ae7edb1ff..0eae7ecad 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -78,6 +78,7 @@ typedef struct _MyNodeInfo { uint32_t current_packet_id; uint32_t node_num_bits; uint32_t message_timeout_msec; + uint32_t min_app_version; } MyNodeInfo; typedef struct _Position { @@ -242,7 +243,7 @@ typedef struct _ToRadio { #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, 0, {0, 0, 0}} #define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0} -#define MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0} +#define MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0} #define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default, 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 {""} #define FromRadio_init_default {0, 0, {MeshPacket_init_default}} @@ -258,7 +259,7 @@ typedef struct _ToRadio { #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, 0, {0, 0, 0}} #define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0} -#define MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0} +#define MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0} #define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero, 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 {""} #define FromRadio_init_zero {0, 0, {MeshPacket_init_zero}} @@ -290,6 +291,7 @@ typedef struct _ToRadio { #define MyNodeInfo_current_packet_id_tag 11 #define MyNodeInfo_node_num_bits_tag 12 #define MyNodeInfo_message_timeout_msec_tag 13 +#define MyNodeInfo_min_app_version_tag 14 #define Position_latitude_i_tag 7 #define Position_longitude_i_tag 8 #define Position_altitude_tag 3 @@ -484,7 +486,8 @@ X(a, STATIC, SINGULAR, UINT32, error_count, 9) \ X(a, STATIC, SINGULAR, UINT32, packet_id_bits, 10) \ X(a, STATIC, SINGULAR, UINT32, current_packet_id, 11) \ X(a, STATIC, SINGULAR, UINT32, node_num_bits, 12) \ -X(a, STATIC, SINGULAR, UINT32, message_timeout_msec, 13) +X(a, STATIC, SINGULAR, UINT32, message_timeout_msec, 13) \ +X(a, STATIC, SINGULAR, UINT32, min_app_version, 14) #define MyNodeInfo_CALLBACK NULL #define MyNodeInfo_DEFAULT NULL @@ -592,8 +595,8 @@ extern const pb_msgdesc_t ManufacturingData_msg; #define RadioConfig_size 157 #define RadioConfig_UserPreferences_size 93 #define NodeInfo_size 132 -#define MyNodeInfo_size 104 -#define DeviceState_size 15457 +#define MyNodeInfo_size 110 +#define DeviceState_size 15463 #define DebugString_size 258 #define FromRadio_size 333 #define ToRadio_size 327 From 5166717298ffeb2ddf4afd529b78c03a4f1547fe Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 3 Jun 2020 14:24:34 -0700 Subject: [PATCH 074/131] confirm randomSeed is set correctly --- src/esp32/main-esp32.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index e8512e2ea..67256bc7f 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -156,7 +156,9 @@ void axp192Init() void esp32Setup() { - randomSeed(esp_random()); // ESP docs say this is fairly random + uint32_t seed = esp_random(); + DEBUG_MSG("Setting random seed %u\n", seed); + randomSeed(seed); // ESP docs say this is fairly random #ifdef AXP192_SLAVE_ADDRESS axp192Init(); @@ -185,7 +187,11 @@ Periodic axpDebugOutput(axpDebugRead); /** * Per @spattinson - * MIN_BAT_MILLIVOLTS seems high. Typical 18650 are different chemistry to LiPo, even for LiPos that chart seems a bit off, other charts put 3690mV at about 30% for a lipo, for 18650 i think 10% remaining iis in the region of 3.2-3.3V. Reference 1st graph in [this test report](https://lygte-info.dk/review/batteries2012/Samsung%20INR18650-30Q%203000mAh%20%28Pink%29%20UK.html) looking at the red line - discharge at 0.2A - he gets a capacity of 2900mah, 90% of 2900 = 2610, that point in the graph looks to be a shade above 3.2V + * MIN_BAT_MILLIVOLTS seems high. Typical 18650 are different chemistry to LiPo, even for LiPos that chart seems a bit off, other + * charts put 3690mV at about 30% for a lipo, for 18650 i think 10% remaining iis in the region of 3.2-3.3V. Reference 1st graph + * in [this test report](https://lygte-info.dk/review/batteries2012/Samsung%20INR18650-30Q%203000mAh%20%28Pink%29%20UK.html) + * looking at the red line - discharge at 0.2A - he gets a capacity of 2900mah, 90% of 2900 = 2610, that point in the graph looks + * to be a shade above 3.2V */ #define MIN_BAT_MILLIVOLTS 3250 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/ From 99437d931ee06509ddcbe95ebda16a5a6087c31c Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 3 Jun 2020 16:08:11 -0700 Subject: [PATCH 075/131] fix #153 --- src/mesh/NodeDB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 7ffd92e3a..0fee4b7b2 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -30,7 +30,7 @@ DeviceState versions used to be defined in the .proto file but really only this #define here. */ -#define DEVICESTATE_CUR_VER 7 +#define DEVICESTATE_CUR_VER 8 #define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER #ifndef NO_ESP32 From 4e5a445d8bcd6e984a27b7f1cc480f4bc4735c15 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 4 Jun 2020 10:37:08 -0700 Subject: [PATCH 076/131] 0.6.7 --- bin/version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/version.sh b/bin/version.sh index 2887e511d..a13f8e90b 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=0.6.4 \ No newline at end of file +export VERSION=0.6.7 \ No newline at end of file From 96594516af423d84fdc372e7e000ed09b715d808 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 4 Jun 2020 11:25:06 -0700 Subject: [PATCH 077/131] now in beta --- README.md | 40 +++++++++++++++++++--------------------- docs/README.md | 2 +- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index fbf6ab42d..df4b8d272 100644 --- a/README.md +++ b/README.md @@ -14,30 +14,33 @@ will optionally work with your phone, but no phone is required. Typical time between recharging the radios should be about eight days. -This project is currently early-alpha, but if you have questions please [join our discussion forum](https://meshtastic.discourse.group/). +This project is is currently in beta-testing - if you have questions please [join our discussion forum](https://meshtastic.discourse.group/). This software is 100% open source and developed by a group of hobbyist experimenters. No warranty is provided, if you'd like to improve it - we'd love your help. Please post in the chat. ## Supported hardware We currently support three models of radios. + - TTGO T-Beam - - [T-Beam V1.0 w/ NEO-M8N](https://www.aliexpress.com/item/33047631119.html) (Recommended) - - [T-Beam V1.0 w/ NEO-6M](https://www.aliexpress.com/item/33050391850.html) - - 3D printable cases - - [T-Beam V0](https://www.thingiverse.com/thing:3773717) - - [T-Beam V1](https://www.thingiverse.com/thing:3830711) + + - [T-Beam V1.0 w/ NEO-M8N](https://www.aliexpress.com/item/33047631119.html) (Recommended) + - [T-Beam V1.0 w/ NEO-6M](https://www.aliexpress.com/item/33050391850.html) + - 3D printable cases + - [T-Beam V0](https://www.thingiverse.com/thing:3773717) + - [T-Beam V1](https://www.thingiverse.com/thing:3830711) - [TTGO LORA32](https://www.aliexpress.com/item/4000211331316.html) - No GPS - [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/) - No GPS - - [3D Printable case](https://www.thingiverse.com/thing:3125854) + - [3D Printable case](https://www.thingiverse.com/thing:3125854) **Make sure to get the frequency for your country** - - US/JP/AU/NZ - 915MHz - - CN - 470MHz - - EU - 870MHz - + +- US/JP/AU/NZ - 915MHz +- CN - 470MHz +- EU - 870MHz + Getting a version that includes a screen is optional, but highly recommended. ## Firmware Installation @@ -57,7 +60,7 @@ Please post comments on our [group chat](https://meshtastic.discourse.group/) if 7. Browse to the previously downloaded firmware and select the correct firmware based on the board type, country and frequency. 8. Select Flash ESP. 9. Once complete, “Done! Flashing is complete!” will be shown. -10. Debug messages sent from the Meshtastic device can be viewed with a terminal program such as [PuTTY](https://www.putty.org/) (Windows only). Within PuTTY, click “Serial”, enter the “Serial line” com port (can be found at step 4), enter “Speed” as 921600, then click “Open”. +10. Debug messages sent from the Meshtastic device can be viewed with a terminal program such as [PuTTY](https://www.putty.org/) (Windows only). Within PuTTY, click “Serial”, enter the “Serial line” com port (can be found at step 4), enter “Speed” as 921600, then click “Open”. ### Installing from a commandline @@ -87,10 +90,10 @@ Hard resetting via RTS pin... ``` 5. cd into the directory where the release zip file was expanded. -6. Install the correct firmware for your board with `device-install.sh firmware-_board_-_country_.bin`. - - Example: `./device-install.sh firmware-HELTEC-US-0.0.3.bin`. +6. Install the correct firmware for your board with `device-install.sh firmware-_board_-_country_.bin`. + - Example: `./device-install.sh firmware-HELTEC-US-0.0.3.bin`. 7. To update run `device-update.sh firmware-_board_-_country_.bin` - - Example: `./device-update.sh firmware-HELTEC-US-0.0.3.bin`. + - Example: `./device-update.sh firmware-HELTEC-US-0.0.3.bin`. Note: If you have previously installed meshtastic, you don't need to run this full script instead just run `esptool.py --baud 921600 write_flash 0x10000 firmware-_board_-_country_-_version_.bin`. This will be faster, also all of your current preferences will be preserved. @@ -165,12 +168,7 @@ Hard resetting via RTS pin... # Meshtastic Android app -The source code for the (optional) Meshtastic Android app is [here](https://github.com/meshtastic/Meshtastic-Android). - -Alpha test builds available by opting into our alpha test group. See (www.meshtastic.org) for instructions. - -If you don't want to live on the 'bleeding edge' you can opt-in to the beta-test or use the released version: -[![Download at https://play.google.com/store/apps/details?id=com.geeksville.mesh](https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png)](https://play.google.com/store/apps/details?id=com.geeksville.mesh&referrer=utm_source%3Dgithub%26utm_medium%3Desp32-readme%26utm_campaign%3Dmeshtastic-esp32%2520readme%26anid%3Dadmob&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1) +The companion (optional) Meshtastic Android app is [here](https://github.com/meshtastic/Meshtastic-Android). You can also download it on Google Play. # Python API diff --git a/docs/README.md b/docs/README.md index 47281dbeb..db48dda51 100644 --- a/docs/README.md +++ b/docs/README.md @@ -31,7 +31,7 @@ Not all of these features are fully implemented yet - see **important** disclaim - Eventually (within a couple of months) we should have a modified version of Signal that works with this project. - Very easy sharing of private secured channels. Just share a special link or QR code with friends and they can join your encrypted mesh -This project is currently in early alpha - if you have questions please [join our discussion forum](https://meshtastic.discourse.group/). +This project is currently in beta testing but it is fairly stable and feature complete - if you have questions please [join our discussion forum](https://meshtastic.discourse.group/). This software is 100% open source and developed by a group of hobbyist experimenters. No warranty is provided, if you'd like to improve it - we'd love your help. Please post in the [forum](https://meshtastic.discourse.group/). From 4b5cfaf9baf6b0f4bd2a0740b9590268841e3d14 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 5 Jun 2020 11:00:18 -0700 Subject: [PATCH 078/131] changes from bringing up PPR --- bin/nrf52-gdbserver.sh | 0 gdbinit | 4 ++-- src/nrf52/UC1701Spi.cpp | 16 +++++++++++++++- variants/ppr/variant.h | 2 +- 4 files changed, 18 insertions(+), 4 deletions(-) mode change 100644 => 100755 bin/nrf52-gdbserver.sh diff --git a/bin/nrf52-gdbserver.sh b/bin/nrf52-gdbserver.sh old mode 100644 new mode 100755 diff --git a/gdbinit b/gdbinit index bb3458136..9950fac4c 100644 --- a/gdbinit +++ b/gdbinit @@ -6,8 +6,8 @@ # mon exec SetMonModeDebug=1 # mon exec SetMonModeVTableAddr=0x26000 -echo setting RTTAddr -eval "monitor exec SetRTTAddr %p", &_SEGGER_RTT +# echo setting RTTAddr +# eval "monitor exec SetRTTAddr %p", &_SEGGER_RTT # the jlink debugger seems to want a pause after reset before we tell it to start running define restart diff --git a/src/nrf52/UC1701Spi.cpp b/src/nrf52/UC1701Spi.cpp index d6f236cfd..d653b6454 100644 --- a/src/nrf52/UC1701Spi.cpp +++ b/src/nrf52/UC1701Spi.cpp @@ -33,4 +33,18 @@ class UC1701Spi : public OLEDDisplay void display(void) {} private: -}; \ No newline at end of file +}; + +#include "variant.h" +#include +static UC1701 lcd(PIN_SPI_SCK, PIN_SPI_MOSI, ERC12864_CS, ERC12864_CD); + + +void testLCD() { + // PCD8544-compatible displays may have a different resolution... + lcd.begin(); + + // Write a piece of text on the first line... + lcd.setCursor(0, 0); + lcd.print("Hello, World!"); +} \ No newline at end of file diff --git a/variants/ppr/variant.h b/variants/ppr/variant.h index d59e17503..95b2803bd 100644 --- a/variants/ppr/variant.h +++ b/variants/ppr/variant.h @@ -132,7 +132,7 @@ static const uint8_t SCK = PIN_SPI_SCK; #define SX1262_CS (10) #define SX1262_DIO1 (20) #define SX1262_DIO2 (26) -#define SX1262_BUSY (18) +#define SX1262_BUSY (31) // Supposed to be P0.18 but because of reworks, now on P0.31 (18) #define SX1262_RESET (17) // #define SX1262_ANT_SW (32 + 10) #define SX1262_RXEN (22) From 4db176867b05826f429a7d7a91dc9cbffb7f1a13 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 5 Jun 2020 11:00:58 -0700 Subject: [PATCH 079/131] WIP - bringup on PPR --- platformio.ini | 4 +--- src/main.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/platformio.ini b/platformio.ini index c51e0954e..c10e7f1ce 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = tbeam ; Note: the github actions CI test build can't yet build NRF52 targets +default_envs = ppr ; Note: the github actions CI test build can't yet build NRF52 targets [common] ; common is not currently used @@ -154,8 +154,6 @@ debug_init_break = [env:nrf52dk] extends = nrf52_base board = nrf52840_dk_modified -lib_deps = - UC1701 ; for temp testing ; The PPR board [env:ppr] diff --git a/src/main.cpp b/src/main.cpp index d7fb21bd1..728cd5bde 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -151,7 +151,8 @@ void setup() #else Wire.begin(); #endif - scanI2Cdevice(); + // i2c still busted on new board + // scanI2Cdevice(); // Buttons & LED #ifdef BUTTON_PIN @@ -180,6 +181,9 @@ void setup() nrf52Setup(); #endif + extern void testLCD(); + // testLCD(); + // Initialize the screen first so we can show the logo while we start up everything else. if (ssd1306_found) screen.setup(); @@ -233,7 +237,7 @@ void setup() new SimRadio(); #endif - if (!rIf->init()) + if (!rIf || !rIf->init()) recordCriticalError(ErrNoRadio); else router.addInterface(rIf); From 9f61c78c0ed6a6d7285f3e1766db10c084bdd9ed Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 5 Jun 2020 11:05:36 -0700 Subject: [PATCH 080/131] doc merge --- README.md | 2 +- docs/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index df4b8d272..e52590122 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This is the device side code for the [meshtastic.org](https://www.meshtastic.org ![Continuous Integration](https://github.com/meshtastic/Meshtastic-esp32/workflows/Continuous%20Integration/badge.svg) -Meshtastic is a project that lets you use +Meshtastic™ is a project that lets you use inexpensive GPS mesh 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. diff --git a/docs/README.md b/docs/README.md index db48dda51..3f4bfa499 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ # What is Meshtastic? -Meshtastic is a project that lets you use +Meshtastic™ is a project that lets you use 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. From 52b01db306add5eb6656c51d477113be39613625 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 5 Jun 2020 11:33:19 -0700 Subject: [PATCH 081/131] announce beta --- docs/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index 3f4bfa499..48e8d998e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -24,11 +24,11 @@ Not all of these features are fully implemented yet - see **important** disclaim - Very long battery life (should be about eight days with the beta software) - Built in GPS and [LoRa](https://en.wikipedia.org/wiki/LoRa) radio, but we manage the radio automatically for you - Long range - a few miles per node but each node will forward packets as needed +- Secure - channels are encrypted by AES256 (But see important disclaimers below wrt this feature) - Shows direction and distance to all members of your channel - Directed or broadcast text messages for channel members - Open and extensible codebase supporting multiple hardware vendors - no lock in to one vendor -- Communication API for bluetooth devices (such as our Android app) to use the mesh. So if you have some application that needs long range low power networking, this might work for you. -- Eventually (within a couple of months) we should have a modified version of Signal that works with this project. +- Communication API for bluetooth devices (such as our Android app) to use the mesh. An iOS application is in the works. And [Meshtastic-python](https://pypi.org/project/meshtastic/) provides access from desktop computers. - Very easy sharing of private secured channels. Just share a special link or QR code with friends and they can join your encrypted mesh This project is currently in beta testing but it is fairly stable and feature complete - if you have questions please [join our discussion forum](https://meshtastic.discourse.group/). @@ -39,6 +39,7 @@ This software is 100% open source and developed by a group of hobbyist experimen Note: Updates are happening almost daily, only major updates are listed below. For more details see our forum. +- 06/04/2020 - 0.6.7 Beta releases of both the application and the device code are released. Features are fairly solid now with a sizable number of users. - 04/28/2020 - 0.6.0 [Python API](https://pypi.org/project/meshtastic/) released. Makes it easy to use meshtastic devices as "zero config / just works" mesh transport adapters for other projects. - 04/20/2020 - 0.4.3 Pretty solid now both for the android app and the device code. Many people have donated translations and code. Probably going to call it a beta soon. - 03/03/2020 - 0.0.9 of the Android app and device code is released. Still an alpha but fairly functional. From 8d14e97dfaf52372303c99b037204ecf3d49f6ed Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 6 Jun 2020 08:07:21 -0700 Subject: [PATCH 082/131] oops - we were not saving radio state --- src/mesh/NodeDB.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 0fee4b7b2..46560de44 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -114,7 +114,6 @@ void NodeDB::init() devicestate.has_my_node = true; devicestate.has_radio = true; devicestate.has_owner = true; - devicestate.has_radio = false; devicestate.radio.has_channel_settings = true; devicestate.radio.has_preferences = true; devicestate.node_db_count = 0; @@ -137,7 +136,7 @@ void NodeDB::init() memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr)); sprintf(owner.long_name, "Unknown %02x%02x", ourMacAddr[4], ourMacAddr[5]); - // Crummy guess at our nodenum + // Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts) pickNewNodeNum(); sprintf(owner.short_name, "?%02X", myNodeInfo.my_node_num & 0xff); From 9ea65c6793fba9356f746fff419c9366299f8664 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 6 Jun 2020 08:30:01 -0700 Subject: [PATCH 083/131] Fix #153 - details below Somehow nodenum was getting reset to zero (and saved to flash - which is bad because it makes the failure permanent). So I've changed nodenum selection to occur after we load the saved preferences (and we try to keep nodenum stable in that case). I'm puzzled as to how it ever got set to zero (unless there *shudder* is some errant pointer that clobbered it). But next week I'm turning 4 byte nodenums back on, which will make this moot - because they will always be based on macaddr and the current process where nodes haggle with the mesh to pick a unique one-byte nodenum will be gone. --- src/mesh/NodeDB.cpp | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 46560de44..334e178f6 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -136,16 +136,8 @@ void NodeDB::init() memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr)); sprintf(owner.long_name, "Unknown %02x%02x", ourMacAddr[4], ourMacAddr[5]); - // Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts) - pickNewNodeNum(); - sprintf(owner.short_name, "?%02X", myNodeInfo.my_node_num & 0xff); - // Include our owner in the node db under our nodenum - NodeInfo *info = getOrCreateNode(getNodeNum()); - info->user = owner; - info->has_user = true; - if (!FSBegin()) // FIXME - do this in main? { DEBUG_MSG("ERROR filesystem mount Failed\n"); @@ -156,6 +148,15 @@ void NodeDB::init() loadFromDisk(); // saveToDisk(); + // Note! We do this after loading saved settings, so that if somehow an invalid nodenum was stored in preferences we won't + // keep using that nodenum forever. Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts) + pickNewNodeNum(); + + // Include our owner in the node db under our nodenum + NodeInfo *info = getOrCreateNode(getNodeNum()); + info->user = owner; + info->has_user = true; + // 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)); @@ -175,9 +176,12 @@ void NodeDB::init() */ void NodeDB::pickNewNodeNum() { - // Pick an initial nodenum based on the macaddr - NodeNum r = sizeof(NodeNum) == 1 ? ourMacAddr[5] - : ((ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5]); + NodeNum r = myNodeInfo.my_node_num; + + // If we don't have a nodenum at app - pick an initial nodenum based on the macaddr + if (r == 0) + r = sizeof(NodeNum) == 1 ? ourMacAddr[5] + : ((ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5]); if (r == NODENUM_BROADCAST || r < NUM_RESERVED) r = NUM_RESERVED; // don't pick a reserved node number @@ -246,15 +250,18 @@ void NodeDB::saveToDisk() if (!pb_encode(&stream, DeviceState_fields, &devicestate)) { DEBUG_MSG("Error: can't write protobuf %s\n", PB_GET_ERROR(&stream)); // FIXME - report failure to phone + + f.close(); + } else { + // Success - replace the old file + f.close(); + + // brief window of risk here ;-) + if (!FS.remove(preffile)) + DEBUG_MSG("Warning: Can't remove old pref file\n"); + if (!FS.rename(preftmp, preffile)) + DEBUG_MSG("Error: can't rename new pref file\n"); } - - f.close(); - - // brief window of risk here ;-) - if (!FS.remove(preffile)) - DEBUG_MSG("Warning: Can't remove old pref file\n"); - if (!FS.rename(preftmp, preffile)) - DEBUG_MSG("Error: can't rename new pref file\n"); } else { DEBUG_MSG("ERROR: can't write prefs\n"); // FIXME report to app } From 6a3853ef35040ac107351dd553923c6367468041 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 6 Jun 2020 08:33:20 -0700 Subject: [PATCH 084/131] 0.6.8 --- bin/version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/version.sh b/bin/version.sh index a13f8e90b..4b3996936 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=0.6.7 \ No newline at end of file +export VERSION=0.6.8 \ No newline at end of file From e124d2094f91aa039bec808a126f40eb01fdfbad Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 6 Jun 2020 13:16:36 -0700 Subject: [PATCH 085/131] PROTOCOL CHANGE! activate 32 bit nodenums/packetids --- docs/software/crypto.md | 3 --- docs/software/nrf52-TODO.md | 2 +- proto | 2 +- src/mesh/MeshService.cpp | 8 +++++--- src/mesh/MeshTypes.h | 4 ++-- src/mesh/Router.cpp | 7 +++++-- src/mesh/mesh.pb.c | 2 +- src/mesh/mesh.pb.h | 14 +++++++------- 8 files changed, 22 insertions(+), 20 deletions(-) diff --git a/docs/software/crypto.md b/docs/software/crypto.md index 7f5709c48..755cb3369 100644 --- a/docs/software/crypto.md +++ b/docs/software/crypto.md @@ -34,7 +34,4 @@ 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/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 137bcb04e..500180d03 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -40,7 +40,6 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At ## Items to be 'feature complete' -- change packet numbers to be 32 bits - check datasheet about sx1262 temperature compensation - enable brownout detection and watchdog - stop polling for GPS characters, instead stay blocked on read in a thread @@ -128,6 +127,7 @@ Nice ideas worth considering someday... - scheduleOSCallback doesn't work yet - it is way too fast (causes rapid polling of busyTx, high power draw etc...) - find out why we reboot while debugging - it was bluetooth/softdevice - make a file system implementation (preferably one that can see the files the bootloader also sees) - preferably https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/libraries/InternalFileSytem/examples/Internal_ReadWrite/Internal_ReadWrite.ino else use https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.3.0/lib_fds_usage.html?cp=7_5_0_3_55_3 +- change packet numbers to be 32 bits ``` diff --git a/proto b/proto index 9d083d5d4..3ba76bbe4 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 9d083d5d4ff4ef095135b18468004eaba77cb691 +Subproject commit 3ba76bbe4c98ee9c9e422d8dc10844cc9fb5272a diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 5060851c3..49cd0fe79 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -86,12 +86,14 @@ void MeshService::sendOurOwner(NodeNum dest, bool wantReplies) const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp) { bool wasBroadcast = mp->to == NODENUM_BROADCAST; - bool isCollision = mp->from == myNodeInfo.my_node_num; - // we win if we have a lower macaddr - bool weWin = memcmp(&owner.macaddr, &mp->decoded.user.macaddr, sizeof(owner.macaddr)) < 0; + // Disable this collision testing if we use 32 bit nodenums + bool isCollision = (sizeof(NodeNum) == 1) && (mp->from == myNodeInfo.my_node_num); if (isCollision) { + // we win if we have a lower macaddr + bool weWin = memcmp(&owner.macaddr, &mp->decoded.user.macaddr, sizeof(owner.macaddr)) < 0; + if (weWin) { DEBUG_MSG("NOTE! Received a nodenum collision and we are vetoing\n"); diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index adba78415..32ec2b08d 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -6,8 +6,8 @@ #include "mesh.pb.h" #include -typedef uint8_t NodeNum; -typedef uint8_t PacketId; // A packet sequence number +typedef uint32_t NodeNum; +typedef uint32_t PacketId; // A packet sequence number #define NODENUM_BROADCAST (sizeof(NodeNum) == 4 ? UINT32_MAX : UINT8_MAX) #define ERRNO_OK 0 diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index eb8a1ab32..2f8dd5e28 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -56,8 +56,11 @@ PacketId generatePacketId() if (!didInit) { didInit = true; - i = random(0, numPacketId + - 1); // pick a random initial sequence number at boot (to prevent repeated reboots always starting at 0) + + // pick a random initial sequence number at boot (to prevent repeated reboots always starting at 0) + // Note: we mask the high order bit to ensure that we never pass a 'negative' number to random + i = random(numPacketId & 0x7fffffff); + DEBUG_MSG("Initial packet id %u, numPacketId %u\n", i, numPacketId); } i++; diff --git a/src/mesh/mesh.pb.c b/src/mesh/mesh.pb.c index 4eb53e1ba..88db1a219 100644 --- a/src/mesh/mesh.pb.c +++ b/src/mesh/mesh.pb.c @@ -9,7 +9,7 @@ PB_BIND(Position, Position, AUTO) -PB_BIND(Data, Data, 2) +PB_BIND(Data, Data, AUTO) PB_BIND(User, User, AUTO) diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index 0eae7ecad..8e4ba34bb 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -47,7 +47,7 @@ typedef struct _ChannelSettings { char name[12]; } ChannelSettings; -typedef PB_BYTES_ARRAY_T(251) Data_payload_t; +typedef PB_BYTES_ARRAY_T(240) Data_payload_t; typedef struct _Data { Data_Type typ; Data_payload_t payload; @@ -586,20 +586,20 @@ extern const pb_msgdesc_t ManufacturingData_msg; /* Maximum encoded size of messages (where known) */ #define Position_size 39 -#define Data_size 256 +#define Data_size 245 #define User_size 72 #define RouteDiscovery_size 88 -#define SubPacket_size 285 -#define MeshPacket_size 324 +#define SubPacket_size 274 +#define MeshPacket_size 313 #define ChannelSettings_size 60 #define RadioConfig_size 157 #define RadioConfig_UserPreferences_size 93 #define NodeInfo_size 132 #define MyNodeInfo_size 110 -#define DeviceState_size 15463 +#define DeviceState_size 15100 #define DebugString_size 258 -#define FromRadio_size 333 -#define ToRadio_size 327 +#define FromRadio_size 322 +#define ToRadio_size 316 /* ManufacturingData_size depends on runtime parameters */ #ifdef __cplusplus From a5f05019db3059f3e61dc5eedfc05c39f95340a9 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 6 Jun 2020 14:30:15 -0700 Subject: [PATCH 086/131] fix build instructions --- docs/software/build-instructions.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/software/build-instructions.md b/docs/software/build-instructions.md index 073ce4e8e..c4ac9b8e1 100644 --- a/docs/software/build-instructions.md +++ b/docs/software/build-instructions.md @@ -6,10 +6,11 @@ in these instructions I describe use of their command line tool. 1. Purchase a suitable radio (see above) 2. Install [PlatformIO](https://platformio.org/platformio-ide) 3. Download this git repo and cd into it -4. If you are outside the USA, edit [platformio.ini](/platformio.ini) to set the correct frequency range for your country. The line you need to change starts with "hw_version" and instructions are provided above that line. Options are provided for EU433, EU835, CN, JP and US. Pull-requests eagerly accepted for other countries. -5. Plug the radio into your USB port -6. Type "pio run --environment XXX -t upload" (This command will fetch dependencies, build the project and install it on the board via USB). For XXX, use the board type you have (either tbeam, heltec, ttgo-lora32-v1, ttgo-lora32-v2). -7. Platform IO also installs a very nice VisualStudio Code based IDE, see their [tutorial](https://docs.platformio.org/en/latest/tutorials/espressif32/arduino_debugging_unit_testing.html) if you'd like to use it. +4. Run "git submodule update --init --recursive" to pull in dependencies this project needs. +5. If you are outside the USA, edit [platformio.ini](/platformio.ini) to set the correct frequency range for your country. The line you need to change starts with "hw_version" and instructions are provided above that line. Options are provided for EU433, EU835, CN, JP and US. Pull-requests eagerly accepted for other countries. +6. Plug the radio into your USB port +7. Type "pio run --environment XXX -t upload" (This command will fetch dependencies, build the project and install it on the board via USB). For XXX, use the board type you have (either tbeam, heltec, ttgo-lora32-v1, ttgo-lora32-v2). +8. Platform IO also installs a very nice VisualStudio Code based IDE, see their [tutorial](https://docs.platformio.org/en/latest/tutorials/espressif32/arduino_debugging_unit_testing.html) if you'd like to use it. ## Decoding stack traces From 871a85d6888573f66c82a4f3acad25f7752e9638 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 7 Jun 2020 17:22:07 -0700 Subject: [PATCH 087/131] force all devices to discard old settings --- src/mesh/NodeDB.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 334e178f6..108fa0562 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -30,7 +30,7 @@ DeviceState versions used to be defined in the .proto file but really only this #define here. */ -#define DEVICESTATE_CUR_VER 8 +#define DEVICESTATE_CUR_VER 9 #define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER #ifndef NO_ESP32 @@ -134,8 +134,11 @@ void NodeDB::init() sprintf(owner.id, "!%02x%02x%02x%02x%02x%02x", ourMacAddr[0], ourMacAddr[1], ourMacAddr[2], ourMacAddr[3], ourMacAddr[4], ourMacAddr[5]); memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr)); - sprintf(owner.long_name, "Unknown %02x%02x", ourMacAddr[4], ourMacAddr[5]); + // Set default owner name + pickNewNodeNum(); // Note: we will repick later, just in case the settings are corrupted, but we need a valid + // owner.short_name now + sprintf(owner.long_name, "Unknown %02x%02x", ourMacAddr[4], ourMacAddr[5]); sprintf(owner.short_name, "?%02X", myNodeInfo.my_node_num & 0xff); if (!FSBegin()) // FIXME - do this in main? From 2d2ed591e9704881494d1f73ee2c56381e9f1eeb Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 7 Jun 2020 22:12:06 -0700 Subject: [PATCH 088/131] set num_bits for nodenum and packet id after loading save file --- src/mesh/NodeDB.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 334e178f6..428639e92 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -123,8 +123,6 @@ void NodeDB::init() // default to no GPS, until one has been found by probing myNodeInfo.has_gps = false; - myNodeInfo.node_num_bits = sizeof(NodeNum) * 8; - myNodeInfo.packet_id_bits = sizeof(PacketId) * 8; myNodeInfo.message_timeout_msec = FLOOD_EXPIRE_TIME; myNodeInfo.min_app_version = 167; generatePacketId(); // FIXME - ugly way to init current_packet_id; @@ -148,6 +146,11 @@ void NodeDB::init() loadFromDisk(); // saveToDisk(); + // We set node_num and packet_id _after_ loading from disk, because we always want to use the values this + // rom was compiled for, not what happens to be in the save file. + myNodeInfo.node_num_bits = sizeof(NodeNum) * 8; + myNodeInfo.packet_id_bits = sizeof(PacketId) * 8; + // Note! We do this after loading saved settings, so that if somehow an invalid nodenum was stored in preferences we won't // keep using that nodenum forever. Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts) pickNewNodeNum(); From 63affdd2e73a55dd8d8c02d0a8016f0c4f688018 Mon Sep 17 00:00:00 2001 From: rradar <34582688+rradar@users.noreply.github.com> Date: Mon, 8 Jun 2020 10:55:03 +0100 Subject: [PATCH 089/131] Update build-instructions.md to use code tags Update build-instructions.md to make (more) use of code tags --- docs/software/build-instructions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/software/build-instructions.md b/docs/software/build-instructions.md index c4ac9b8e1..5074fbaf1 100644 --- a/docs/software/build-instructions.md +++ b/docs/software/build-instructions.md @@ -6,10 +6,10 @@ in these instructions I describe use of their command line tool. 1. Purchase a suitable radio (see above) 2. Install [PlatformIO](https://platformio.org/platformio-ide) 3. Download this git repo and cd into it -4. Run "git submodule update --init --recursive" to pull in dependencies this project needs. -5. If you are outside the USA, edit [platformio.ini](/platformio.ini) to set the correct frequency range for your country. The line you need to change starts with "hw_version" and instructions are provided above that line. Options are provided for EU433, EU835, CN, JP and US. Pull-requests eagerly accepted for other countries. +4. Run `git submodule update --init --recursive` to pull in dependencies this project needs. +5. If you are outside the USA, edit [platformio.ini](/platformio.ini) to set the correct frequency range for your country. The line you need to change starts with `hw_version` and instructions are provided above that line. Options are provided for `EU433`, `EU835`, `CN`, `JP` and `US` (default). Pull-requests eagerly accepted for other countries. 6. Plug the radio into your USB port -7. Type "pio run --environment XXX -t upload" (This command will fetch dependencies, build the project and install it on the board via USB). For XXX, use the board type you have (either tbeam, heltec, ttgo-lora32-v1, ttgo-lora32-v2). +7. Type `pio run --environment XXX -t upload` (This command will fetch dependencies, build the project and install it on the board via USB). For XXX, use the board type you have (either `tbeam`, `heltec`, `ttgo-lora32-v1`, `ttgo-lora32-v2`). 8. Platform IO also installs a very nice VisualStudio Code based IDE, see their [tutorial](https://docs.platformio.org/en/latest/tutorials/espressif32/arduino_debugging_unit_testing.html) if you'd like to use it. ## Decoding stack traces From a02175cec0f42d3ff6f33e91c77ce68644ca7a7a Mon Sep 17 00:00:00 2001 From: Slavomir Hustaty Date: Mon, 8 Jun 2020 21:34:02 +0200 Subject: [PATCH 090/131] Update README.md https://www.everythingrf.com/community/lora-frequency-in-europe The LoRa Alliance has defined two frequency bands for the usage of LoRa technology in Europe. These bands are EU433 from 433.05 to 434.79 MHz and EU863 from 863 to 870 MHz. EU433 (433.05 to 434.79 MHz) The end devices in EU433 band operate from 433.05 to 434.79 MHz and use a channel data structure to support at least 16 channels. and so on... https://lora-alliance.org/sites/default/files/2018-04/lorawantm_regional_parameters_v1.1rb_-_final.pdf --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e52590122..011a8b246 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ We currently support three models of radios. - US/JP/AU/NZ - 915MHz - CN - 470MHz -- EU - 870MHz +- EU - 868MHz, 433MHz Getting a version that includes a screen is optional, but highly recommended. From 71a4cfefd50ae6517e34a7c969fd2479918bf818 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 8 Jun 2020 15:01:55 -0700 Subject: [PATCH 091/131] bringup WIP --- docs/software/nrf52-TODO.md | 10 +++++++++- proto | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 137bcb04e..efa6b3fde 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -2,11 +2,19 @@ ## Misc work items +platform.json + + "framework-arduinoadafruitnrf52": { + "type": "framework", + "optional": true, + "version": "https://github.com/meshtastic/Adafruit_nRF52_Arduino.git" + }, + ## Initial work items Minimum items needed to make sure hardware is good. -- set power UICR per https://devzone.nordicsemi.com/f/nordic-q-a/28562/nrf52840-regulator-configuration +- DONE set power UICR per https://devzone.nordicsemi.com/f/nordic-q-a/28562/nrf52840-regulator-configuration - switch charge controller into / out of performance mode (see 8.3.1 in datasheet) - write UC1701 wrapper - Test hardfault handler for null ptrs (if one isn't already installed) diff --git a/proto b/proto index 9d083d5d4..3ba76bbe4 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 9d083d5d4ff4ef095135b18468004eaba77cb691 +Subproject commit 3ba76bbe4c98ee9c9e422d8dc10844cc9fb5272a From 7473a6c27a10014c8db6fcb4b7488319e3702129 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 8 Jun 2020 16:06:59 -0700 Subject: [PATCH 092/131] unify activity detection in PhoneAPI, turn off BLE API while serial API in use --- src/SerialConsole.cpp | 7 +++++++ src/SerialConsole.h | 4 ++++ src/esp32/BluetoothSoftwareUpdate.cpp | 6 ------ src/esp32/CallbackCharacteristic.h | 29 ++++----------------------- src/esp32/MeshBluetoothService.cpp | 23 ++++----------------- src/mesh/PhoneAPI.cpp | 22 ++++++++++++++++++++ src/mesh/PhoneAPI.h | 11 ++++++++++ src/mesh/StreamAPI.cpp | 1 + 8 files changed, 53 insertions(+), 50 deletions(-) diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 9e84a958f..9490e72c9 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -1,5 +1,6 @@ #include "SerialConsole.h" #include "configuration.h" +#include "target_specific.h" #include #define Port Serial @@ -34,4 +35,10 @@ void SerialConsole::handleToRadio(const uint8_t *buf, size_t len) canWrite = true; StreamAPI::handleToRadio(buf, len); +} + +/// Hookable to find out when connection changes +void SerialConsole::onConnectionChanged(bool connected) +{ + setBluetoothEnable(!connected); // To prevent user confusion, turn off bluetooth while using the serial port api } \ No newline at end of file diff --git a/src/SerialConsole.h b/src/SerialConsole.h index b39eda23b..17cb16943 100644 --- a/src/SerialConsole.h +++ b/src/SerialConsole.h @@ -26,6 +26,10 @@ class SerialConsole : public StreamAPI, public RedirectablePrint RedirectablePrint::write('\r'); return RedirectablePrint::write(c); } + + protected: + /// Hookable to find out when connection changes + virtual void onConnectionChanged(bool connected); }; extern SerialConsole console; diff --git a/src/esp32/BluetoothSoftwareUpdate.cpp b/src/esp32/BluetoothSoftwareUpdate.cpp index 0f56cecaa..f5f98a47f 100644 --- a/src/esp32/BluetoothSoftwareUpdate.cpp +++ b/src/esp32/BluetoothSoftwareUpdate.cpp @@ -30,8 +30,6 @@ class TotalSizeCharacteristic : public CallbackCharacteristic void onWrite(BLECharacteristic *c) { - BLEKeepAliveCallbacks::onWrite(c); - LockGuard g(updateLock); // Check if there is enough to OTA Update uint32_t len = getValue32(c, 0); @@ -67,8 +65,6 @@ class DataCharacteristic : public CallbackCharacteristic void onWrite(BLECharacteristic *c) { - BLEKeepAliveCallbacks::onWrite(c); - LockGuard g(updateLock); std::string value = c->getValue(); uint32_t len = value.length(); @@ -92,8 +88,6 @@ class CRC32Characteristic : public CallbackCharacteristic void onWrite(BLECharacteristic *c) { - BLEKeepAliveCallbacks::onWrite(c); - LockGuard g(updateLock); uint32_t expectedCRC = getValue32(c, 0); uint32_t actualCRC = crc.finalize(); diff --git a/src/esp32/CallbackCharacteristic.h b/src/esp32/CallbackCharacteristic.h index daec1d500..9c4f59a05 100644 --- a/src/esp32/CallbackCharacteristic.h +++ b/src/esp32/CallbackCharacteristic.h @@ -1,33 +1,12 @@ #pragma once -#include "PowerFSM.h" // FIXME - someday I want to make this OTA thing a separate lb at at that point it can't touch this #include "BLECharacteristic.h" - -/** - * This mixin just lets the power management state machine know the phone is still talking to us - */ -class BLEKeepAliveCallbacks : public BLECharacteristicCallbacks -{ -public: - void onRead(BLECharacteristic *c) - { - powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); - } - - void onWrite(BLECharacteristic *c) - { - powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); - } -}; +#include "PowerFSM.h" // FIXME - someday I want to make this OTA thing a separate lb at at that point it can't touch this /** * A characterstic with a set of overridable callbacks */ -class CallbackCharacteristic : public BLECharacteristic, public BLEKeepAliveCallbacks +class CallbackCharacteristic : public BLECharacteristic, public BLECharacteristicCallbacks { -public: - CallbackCharacteristic(const char *uuid, uint32_t btprops) - : BLECharacteristic(uuid, btprops) - { - setCallbacks(this); - } + public: + CallbackCharacteristic(const char *uuid, uint32_t btprops) : BLECharacteristic(uuid, btprops) { setCallbacks(this); } }; diff --git a/src/esp32/MeshBluetoothService.cpp b/src/esp32/MeshBluetoothService.cpp index ecda8bf40..8c5386157 100644 --- a/src/esp32/MeshBluetoothService.cpp +++ b/src/esp32/MeshBluetoothService.cpp @@ -58,17 +58,12 @@ class ProtobufCharacteristic : public CallbackCharacteristic void onRead(BLECharacteristic *c) { - BLEKeepAliveCallbacks::onRead(c); size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), fields, my_struct); DEBUG_MSG("pbread from %s returns %d bytes\n", c->getUUID().toString().c_str(), numbytes); c->setValue(trBytes, numbytes); } - void onWrite(BLECharacteristic *c) - { - BLEKeepAliveCallbacks::onWrite(c); - writeToDest(c, my_struct); - } + void onWrite(BLECharacteristic *c) { writeToDest(c, my_struct); } protected: /// like onWrite, but we provide an different destination to write to, for use by subclasses that @@ -84,7 +79,7 @@ class ProtobufCharacteristic : public CallbackCharacteristic }; #ifdef SUPPORT_OLD_BLE_API -class NodeInfoCharacteristic : public BLECharacteristic, public BLEKeepAliveCallbacks +class NodeInfoCharacteristic : public BLECharacteristic, public BLECharacteristicCallbacks { public: NodeInfoCharacteristic() @@ -96,8 +91,6 @@ class NodeInfoCharacteristic : public BLECharacteristic, public BLEKeepAliveCall void onRead(BLECharacteristic *c) { - BLEKeepAliveCallbacks::onRead(c); - const NodeInfo *info = nodeDB.readNextInfo(); if (info) { @@ -113,7 +106,6 @@ class NodeInfoCharacteristic : public BLECharacteristic, public BLEKeepAliveCall void onWrite(BLECharacteristic *c) { - BLEKeepAliveCallbacks::onWrite(c); DEBUG_MSG("Reset nodeinfo read pointer\n"); nodeDB.resetReadPointer(); } @@ -156,8 +148,7 @@ class OwnerCharacteristic : public ProtobufCharacteristic void onWrite(BLECharacteristic *c) { - BLEKeepAliveCallbacks::onWrite( - c); // NOTE: We do not call the standard ProtobufCharacteristic superclass, because we want custom write behavior + // NOTE: We do not call the standard ProtobufCharacteristic superclass, because we want custom write behavior static User o; // if the phone doesn't set ID we are careful to keep ours, we also always keep our macaddr if (writeToDest(c, &o)) { @@ -196,7 +187,6 @@ class ToRadioCharacteristic : public CallbackCharacteristic void onWrite(BLECharacteristic *c) { - BLEKeepAliveCallbacks::onWrite(c); DEBUG_MSG("Got on write\n"); bluetoothPhoneAPI->handleToRadio(c->getData(), c->getValue().length()); @@ -212,7 +202,6 @@ class FromRadioCharacteristic : public CallbackCharacteristic void onRead(BLECharacteristic *c) { - BLEKeepAliveCallbacks::onRead(c); size_t numBytes = bluetoothPhoneAPI->getFromRadio(trBytes); // Someone is going to read our value as soon as this callback returns. So fill it with the next message in the queue @@ -236,11 +225,7 @@ class FromNumCharacteristic : public CallbackCharacteristic // observe(&service.fromNumChanged); } - void onRead(BLECharacteristic *c) - { - BLEKeepAliveCallbacks::onRead(c); - DEBUG_MSG("FIXME implement fromnum read\n"); - } + void onRead(BLECharacteristic *c) { DEBUG_MSG("FIXME implement fromnum read\n"); } }; /* diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 194a15e68..9a6595fc8 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -1,6 +1,7 @@ #include "PhoneAPI.h" #include "MeshService.h" #include "NodeDB.h" +#include "PowerFSM.h" #include PhoneAPI::PhoneAPI() @@ -14,11 +15,30 @@ void PhoneAPI::init() observe(&service.fromNumChanged); } +void PhoneAPI::checkConnectionTimeout() +{ + if (isConnected) { + bool newConnected = (millis() - lastContactMsec < radioConfig.preferences.phone_timeout_secs); + if (!newConnected) { + isConnected = false; + onConnectionChanged(isConnected); + } + } +} + /** * Handle a ToRadio protobuf */ void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) { + powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // As long as the phone keeps talking to us, don't let the radio go to sleep + lastContactMsec = millis(); + if (!isConnected) { + isConnected = true; + onConnectionChanged(isConnected); + } + // return (lastContactMsec != 0) && + if (pb_decode_from_bytes(buf, bufLength, ToRadio_fields, &toRadioScratch)) { switch (toRadioScratch.which_variant) { case ToRadio_packet_tag: { @@ -227,6 +247,8 @@ void PhoneAPI::handleToRadioPacket(MeshPacket *p) {} /// If the mesh service tells us fromNum has changed, tell the phone int PhoneAPI::onNotify(uint32_t newValue) { + checkConnectionTimeout(); // a handy place to check if we've heard from the phone (since the BLE version doesn't call this from idle) + if (state == STATE_SEND_PACKETS || state == STATE_LEGACY) { DEBUG_MSG("Telling client we have new packets %u\n", newValue); onNowHasData(newValue); diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index f08c9009f..129ecb5db 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -50,6 +50,11 @@ class PhoneAPI /// Use to ensure that clients don't get confused about old messages from the radio uint32_t config_nonce = 0; + /** the last msec we heard from the client on the other side of this link */ + uint32_t lastContactMsec = 0; + + bool isConnected = false; + public: PhoneAPI(); @@ -85,6 +90,12 @@ class PhoneAPI /// Our fromradio packet while it is being assembled FromRadio fromRadioScratch; + /// Hookable to find out when connection changes + virtual void onConnectionChanged(bool connected) {} + + /// If we haven't heard from the other side in a while then say not connected + void checkConnectionTimeout(); + /** * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) */ diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 49ccb3d6b..06b80b2fa 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -9,6 +9,7 @@ void StreamAPI::loop() { writeStream(); readStream(); + checkConnectionTimeout(); } /** From bdbaf9c6553aa808f5cef42b04d51d8c1d7e84d7 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 8 Jun 2020 16:08:02 -0700 Subject: [PATCH 093/131] remove old BLE api --- src/esp32/MeshBluetoothService.cpp | 111 ----------------------------- 1 file changed, 111 deletions(-) diff --git a/src/esp32/MeshBluetoothService.cpp b/src/esp32/MeshBluetoothService.cpp index 8c5386157..3e803ad66 100644 --- a/src/esp32/MeshBluetoothService.cpp +++ b/src/esp32/MeshBluetoothService.cpp @@ -23,9 +23,6 @@ static CallbackCharacteristic *meshFromNumCharacteristic; BLEService *meshService; -// If defined we will also support the old API -#define SUPPORT_OLD_BLE_API - class BluetoothPhoneAPI : public PhoneAPI { /** @@ -78,108 +75,6 @@ class ProtobufCharacteristic : public CallbackCharacteristic } }; -#ifdef SUPPORT_OLD_BLE_API -class NodeInfoCharacteristic : public BLECharacteristic, public BLECharacteristicCallbacks -{ - public: - NodeInfoCharacteristic() - : BLECharacteristic("d31e02e0-c8ab-4d3f-9cc9-0b8466bdabe8", - BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ) - { - setCallbacks(this); - } - - void onRead(BLECharacteristic *c) - { - const NodeInfo *info = nodeDB.readNextInfo(); - - if (info) { - DEBUG_MSG("Sending nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", info->num, info->position.time, info->user.id, - info->user.long_name); - size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), NodeInfo_fields, info); - c->setValue(trBytes, numbytes); - } else { - c->setValue(trBytes, 0); // Send an empty response - DEBUG_MSG("Done sending nodeinfos\n"); - } - } - - void onWrite(BLECharacteristic *c) - { - DEBUG_MSG("Reset nodeinfo read pointer\n"); - nodeDB.resetReadPointer(); - } -}; - -// wrap our protobuf version with something that forces the service to reload the config -class RadioCharacteristic : public ProtobufCharacteristic -{ - public: - RadioCharacteristic() - : ProtobufCharacteristic("b56786c8-839a-44a1-b98e-a1724c4a0262", - BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ, RadioConfig_fields, - &radioConfig) - { - } - - void onRead(BLECharacteristic *c) - { - DEBUG_MSG("Reading radio config, sdsecs %u\n", radioConfig.preferences.sds_secs); - ProtobufCharacteristic::onRead(c); - } - - void onWrite(BLECharacteristic *c) - { - DEBUG_MSG("Writing radio config\n"); - ProtobufCharacteristic::onWrite(c); - bluetoothPhoneAPI->handleSetRadio(radioConfig); - } -}; - -// wrap our protobuf version with something that forces the service to reload the owner -class OwnerCharacteristic : public ProtobufCharacteristic -{ - public: - OwnerCharacteristic() - : ProtobufCharacteristic("6ff1d8b6-e2de-41e3-8c0b-8fa384f64eb6", - BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ, User_fields, &owner) - { - } - - void onWrite(BLECharacteristic *c) - { - // NOTE: We do not call the standard ProtobufCharacteristic superclass, because we want custom write behavior - - static User o; // if the phone doesn't set ID we are careful to keep ours, we also always keep our macaddr - if (writeToDest(c, &o)) { - bluetoothPhoneAPI->handleSetOwner(o); - } - } -}; - -class MyNodeInfoCharacteristic : public ProtobufCharacteristic -{ - public: - MyNodeInfoCharacteristic() - : ProtobufCharacteristic("ea9f3f82-8dc4-4733-9452-1f6da28892a2", BLECharacteristic::PROPERTY_READ, MyNodeInfo_fields, - &myNodeInfo) - { - } - - void onRead(BLECharacteristic *c) - { - // update gps connection state - myNodeInfo.has_gps = gps->isConnected; - - ProtobufCharacteristic::onRead(c); - - myNodeInfo.error_code = 0; // The phone just read us, so throw it away - myNodeInfo.error_address = 0; - } -}; - -#endif - class ToRadioCharacteristic : public CallbackCharacteristic { public: @@ -248,12 +143,6 @@ BLEService *createMeshBluetoothService(BLEServer *server) addWithDesc(service, meshFromNumCharacteristic, "fromRadio"); addWithDesc(service, new ToRadioCharacteristic, "toRadio"); addWithDesc(service, new FromRadioCharacteristic, "fromNum"); -#ifdef SUPPORT_OLD_BLE_API - addWithDesc(service, new MyNodeInfoCharacteristic, "myNode"); - addWithDesc(service, new RadioCharacteristic, "radio"); - addWithDesc(service, new OwnerCharacteristic, "owner"); - addWithDesc(service, new NodeInfoCharacteristic, "nodeinfo"); -#endif meshFromNumCharacteristic->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification From ce9bac34d617989a07f6369232657902f43e238b Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 8 Jun 2020 16:35:26 -0700 Subject: [PATCH 094/131] add a new SERIAL psm state, to ensure device doesn't sleep while connected to the phone over USB. In support of https://github.com/meshtastic/Meshtastic-Android/issues/38 --- docs/software/power.md | 6 ++++++ src/PowerFSM.cpp | 17 ++++++++++++++++- src/PowerFSM.h | 2 ++ src/SerialConsole.cpp | 8 ++++++-- src/mesh/PhoneAPI.cpp | 7 ++++--- 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/docs/software/power.md b/docs/software/power.md index 84c397519..cd0d2c913 100644 --- a/docs/software/power.md +++ b/docs/software/power.md @@ -36,6 +36,10 @@ From lower to higher power consumption. onEntry: setBluetoothOn(true), screen.setOn(true) onExit: screen.setOn(false) +- serial API usage (SERIAL) - Screen is on, device doesn't sleep, bluetooth off + onEntry: setBluetooth off, screen on + onExit: + ## Behavior ### events that increase CPU activity @@ -51,9 +55,11 @@ From lower to higher power consumption. - While in DARK/ON: If we receive EVENT_BLUETOOTH_PAIR we transition to ON and start our screen_on_secs timeout - While in NB/DARK/ON: If we receive EVENT_NODEDB_UPDATED we transition to ON (so the new screen can be shown) - While in DARK: While the phone talks to us over BLE (EVENT_CONTACT_FROM_PHONE) reset any sleep timers and stay in DARK (needed for bluetooth sw update and nice user experience if the user is reading/replying to texts) +- while in LS/NB/DARK: if SERIAL_CONNECTED, go to serial ### events that decrease cpu activity +- While in SERIAL: if SERIAL_DISCONNECTED, go to NB - While in ON: If PRESS event occurs, reset screen_on_secs timer and tell the screen to handle the pess - While in ON: If it has been more than screen_on_secs since a press, lower to DARK - While in DARK: If time since last contact by our phone exceeds phone_timeout_secs (15 minutes), we transition down into NB mode diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 22b2ccd72..1e9adbec9 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -104,6 +104,12 @@ static void darkEnter() screen.setOn(false); } +static void serialEnter() +{ + setBluetoothEnable(false); + screen.setOn(true); +} + static void onEnter() { screen.setOn(true); @@ -133,6 +139,7 @@ State stateSDS(sdsEnter, NULL, NULL, "SDS"); State stateLS(lsEnter, lsIdle, lsExit, "LS"); State stateNB(nbEnter, NULL, NULL, "NB"); State stateDARK(darkEnter, NULL, NULL, "DARK"); +State stateSERIAL(serialEnter, NULL, NULL, "SERIAL"); State stateBOOT(bootEnter, NULL, NULL, "BOOT"); State stateON(onEnter, NULL, NULL, "ON"); Fsm powerFSM(&stateBOOT); @@ -148,7 +155,7 @@ void PowerFSM_setup() powerFSM.add_transition(&stateNB, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet, resetting win wake"); - // Handle press events + // Handle press events - note: we ignore button presses when in API mode powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL, "Press"); powerFSM.add_transition(&stateNB, &stateON, EVENT_PRESS, NULL, "Press"); powerFSM.add_transition(&stateDARK, &stateON, EVENT_PRESS, NULL, "Press"); @@ -160,6 +167,7 @@ void PowerFSM_setup() 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(&stateSERIAL, &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"); @@ -173,6 +181,13 @@ void PowerFSM_setup() powerFSM.add_transition(&stateDARK, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); powerFSM.add_transition(&stateON, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); // restarts the sleep timer + powerFSM.add_transition(&stateLS, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API"); + powerFSM.add_transition(&stateNB, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API"); + powerFSM.add_transition(&stateDARK, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API"); + powerFSM.add_transition(&stateON, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API"); + + powerFSM.add_transition(&stateSERIAL, &stateNB, EVENT_SERIAL_DISCONNECTED, NULL, "serial disconnect"); + powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone"); powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone"); diff --git a/src/PowerFSM.h b/src/PowerFSM.h index ecaea70ac..c89ad9148 100644 --- a/src/PowerFSM.h +++ b/src/PowerFSM.h @@ -14,6 +14,8 @@ #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 +#define EVENT_SERIAL_CONNECTED 11 +#define EVENT_SERIAL_DISCONNECTED 12 extern Fsm powerFSM; diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 9490e72c9..6fc014a5c 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -1,6 +1,6 @@ #include "SerialConsole.h" +#include "PowerFSM.h" #include "configuration.h" -#include "target_specific.h" #include #define Port Serial @@ -40,5 +40,9 @@ void SerialConsole::handleToRadio(const uint8_t *buf, size_t len) /// Hookable to find out when connection changes void SerialConsole::onConnectionChanged(bool connected) { - setBluetoothEnable(!connected); // To prevent user confusion, turn off bluetooth while using the serial port api + if (connected) { // To prevent user confusion, turn off bluetooth while using the serial port api + powerFSM.trigger(EVENT_SERIAL_CONNECTED); + } else { + powerFSM.trigger(EVENT_SERIAL_DISCONNECTED); + } } \ No newline at end of file diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 9a6595fc8..b4c5538ca 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -18,7 +18,7 @@ void PhoneAPI::init() void PhoneAPI::checkConnectionTimeout() { if (isConnected) { - bool newConnected = (millis() - lastContactMsec < radioConfig.preferences.phone_timeout_secs); + bool newConnected = (millis() - lastContactMsec < radioConfig.preferences.phone_timeout_secs * 1000L); if (!newConnected) { isConnected = false; onConnectionChanged(isConnected); @@ -247,8 +247,9 @@ void PhoneAPI::handleToRadioPacket(MeshPacket *p) {} /// If the mesh service tells us fromNum has changed, tell the phone int PhoneAPI::onNotify(uint32_t newValue) { - checkConnectionTimeout(); // a handy place to check if we've heard from the phone (since the BLE version doesn't call this from idle) - + checkConnectionTimeout(); // a handy place to check if we've heard from the phone (since the BLE version doesn't call this + // from idle) + if (state == STATE_SEND_PACKETS || state == STATE_LEGACY) { DEBUG_MSG("Telling client we have new packets %u\n", newValue); onNowHasData(newValue); From 009f05b61d7c533d9113c0add6ec5925496ea767 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 9 Jun 2020 06:38:09 -0700 Subject: [PATCH 095/131] temp workaround for sleep bug #167 --- src/sleep.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sleep.cpp b/src/sleep.cpp index 270ecc5cc..18462de79 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -129,6 +129,7 @@ static void waitEnterSleep() if (millis() - now > 30 * 1000) { // If we wait too long just report an error and go to sleep recordCriticalError(ErrSleepEnterWait); + ESP.restart(); // FIXME - for now we just restart, need to fix bug #167 break; } } From a8a5e036f52b0f6b2426458d38f16b8d6214815b Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 9 Jun 2020 10:35:06 -0700 Subject: [PATCH 096/131] turn off serial debug output once we are using the protocol on the stream --- src/SerialConsole.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 6fc014a5c..683886ed5 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -27,11 +27,8 @@ void SerialConsole::init() */ void SerialConsole::handleToRadio(const uint8_t *buf, size_t len) { - // Note: for the time being we could _allow_ debug printing to keep going out the console - // I _think_ this is okay because we currently only print debug msgs from loop() and we are only - // dispatching serial protobuf msgs from loop() as well. When things are more threaded in the future this - // will need to change. - // setDestination(&noopPrint); + // Turn off debug serial printing once the API is activated, because other threads could print and corrupt packets + setDestination(&noopPrint); canWrite = true; StreamAPI::handleToRadio(buf, len); From 846fc14b4adb47a35982421a41f2fd9076bb89cc Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 9 Jun 2020 10:35:13 -0700 Subject: [PATCH 097/131] 0.7.4 --- bin/version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/version.sh b/bin/version.sh index 4b3996936..befa884d6 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=0.6.8 \ No newline at end of file +export VERSION=0.7.4 \ No newline at end of file From a05e45f84bd45ef50097213336b0dc91eec801a9 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 9 Jun 2020 15:47:05 -0700 Subject: [PATCH 098/131] make txQueue private --- src/mesh/RadioInterface.cpp | 2 +- src/mesh/RadioInterface.h | 1 - src/mesh/RadioLibInterface.h | 2 ++ 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index c7edf74de..43d9a6696 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -24,7 +24,7 @@ separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts // 1kb was too small #define RADIO_STACK_SIZE 4096 -RadioInterface::RadioInterface() : txQueue(MAX_TX_QUEUE) +RadioInterface::RadioInterface() { assert(sizeof(PacketHeader) == 4 || sizeof(PacketHeader) == 16); // make sure the compiler did what we expected diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 64222a76a..0617592ac 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -59,7 +59,6 @@ class RadioInterface : protected NotifiedWorkerThread protected: MeshPacket *sendingPacket = NULL; // The packet we are currently sending - PointerQueue txQueue; uint32_t lastTxStart = 0L; /** diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 1a0e5f450..6619337ba 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -29,6 +29,8 @@ class RadioLibInterface : public RadioInterface, private PeriodicTask */ uint32_t rxBad = 0, rxGood = 0, txGood = 0; + PointerQueue txQueue = PointerQueue(MAX_TX_QUEUE); + protected: float bw = 125; uint8_t sf = 9; From 00d55c9daa715efc78f1a2aaa42d03d5f6c3b727 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 9 Jun 2020 18:20:06 -0700 Subject: [PATCH 099/131] require min app version 172 --- src/mesh/NodeDB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 86b5bda84..37233a3a0 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -124,7 +124,7 @@ void NodeDB::init() // default to no GPS, until one has been found by probing myNodeInfo.has_gps = false; myNodeInfo.message_timeout_msec = FLOOD_EXPIRE_TIME; - myNodeInfo.min_app_version = 167; + myNodeInfo.min_app_version = 172; generatePacketId(); // FIXME - ugly way to init current_packet_id; // Init our blank owner info to reasonable defaults From 21a90a42e56842a3b185b1dc6552e61460cd3791 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 10 Jun 2020 14:02:53 -0700 Subject: [PATCH 100/131] move flutter ideas into own project --- docs/software/nrf52-TODO.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 500180d03..55b781d23 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -59,8 +59,6 @@ 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 - enable monitor mode debugging (need to use real jlink): https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/monitor-mode-debugging-with-j-link-and-gdbeclipse - Improve efficiency of PeriodicTimer by only checking the next queued timer event, and carefully sorting based on schedule - 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. From ddfdae64bf479d4bf5a5328923ad07298811a5a4 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 10 Jun 2020 14:11:43 -0700 Subject: [PATCH 101/131] Fix #167 while in light sleep, allow loop() to still run occasionally --- src/PowerFSM.cpp | 65 ++++++++++++++++++---------------- src/mesh/RadioLibInterface.cpp | 8 +++-- src/sleep.cpp | 4 +-- src/sleep.h | 3 ++ 4 files changed, 46 insertions(+), 34 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 1e9adbec9..8f67b148c 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -26,60 +26,65 @@ static void sdsEnter() #include "error.h" +static uint32_t secsSlept; + static void lsEnter() { DEBUG_MSG("lsEnter begin, ls_secs=%u\n", radioConfig.preferences.ls_secs); screen.setOn(false); + secsSlept = 0; // How long have we been sleeping this time DEBUG_MSG("lsEnter end\n"); } static void lsIdle() { - DEBUG_MSG("lsIdle begin ls_secs=%u\n", radioConfig.preferences.ls_secs); + // DEBUG_MSG("lsIdle begin ls_secs=%u\n", radioConfig.preferences.ls_secs); #ifndef NO_ESP32 - uint32_t secsSlept = 0; esp_sleep_source_t wakeCause = ESP_SLEEP_WAKEUP_UNDEFINED; - bool reached_ls_secs = false; - while (!reached_ls_secs) { + // Do we have more sleeping to do? + if (secsSlept < radioConfig.preferences.ls_secs) { // Briefly come out of sleep long enough to blink the led once every few seconds - uint32_t sleepTime = 5; + uint32_t sleepTime = 30; - setLed(false); // Never leave led on while in light sleep - wakeCause = doLightSleep(sleepTime * 1000LL); - if (wakeCause != ESP_SLEEP_WAKEUP_TIMER) - break; + // If some other service would stall sleep, don't let sleep happen yet + if (doPreflightSleep()) { + setLed(false); // Never leave led on while in light sleep + wakeCause = doLightSleep(sleepTime * 1000LL); - setLed(true); // briefly turn on led - doLightSleep(1); - if (wakeCause != ESP_SLEEP_WAKEUP_TIMER) - break; + if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) { + // Normal case: timer expired, we should just go back to sleep ASAP - secsSlept += sleepTime; - reached_ls_secs = secsSlept >= radioConfig.preferences.ls_secs; - } - setLed(false); + setLed(true); // briefly turn on led + wakeCause = doLightSleep(1); // leave led on for 1ms - if (reached_ls_secs) { - // stay in LS mode but let loop check whatever it wants - DEBUG_MSG("reached ls_secs, servicing loop()\n"); - } else { - DEBUG_MSG("wakeCause %d\n", wakeCause); + secsSlept += sleepTime; + // DEBUG_MSG("sleeping, flash led!\n"); + } else { + // We woke for some other reason (button press, uart, device interrupt) + DEBUG_MSG("wakeCause %d\n", wakeCause); #ifdef BUTTON_PIN - bool pressed = !digitalRead(BUTTON_PIN); + bool pressed = !digitalRead(BUTTON_PIN); #else - bool pressed = false; + bool pressed = false; #endif - if (pressed) // If we woke because of press, instead generate a PRESS event. - { - powerFSM.trigger(EVENT_PRESS); - } else { - // Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc) - powerFSM.trigger(EVENT_WAKE_TIMER); + if (pressed) // If we woke because of press, instead generate a PRESS event. + { + powerFSM.trigger(EVENT_PRESS); + } else { + // Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc) + powerFSM.trigger(EVENT_WAKE_TIMER); + } + } } + } else { + // Time to stop sleeping! + setLed(false); + DEBUG_MSG("reached ls_secs, servicing loop()\n"); + powerFSM.trigger(EVENT_WAKE_TIMER); } #endif } diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 53f99aae9..5239f8f60 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -134,7 +134,7 @@ bool RadioLibInterface::canSleep() { bool res = txQueue.isEmpty(); if (!res) // only print debug messages if we are vetoing sleep - DEBUG_MSG("radio wait to sleep, txEmpty=%d\n", txQueue.isEmpty()); + DEBUG_MSG("radio wait to sleep, txEmpty=%d\n", res); return res; } @@ -173,11 +173,13 @@ void RadioLibInterface::loop() case ISR_TX: handleTransmitInterrupt(); startReceive(); + // DEBUG_MSG("tx complete - starting timer\n"); startTransmitTimer(); break; case ISR_RX: handleReceiveInterrupt(); startReceive(); + // DEBUG_MSG("rx complete - starting timer\n"); startTransmitTimer(); break; case TRANSMIT_DELAY_COMPLETED: @@ -192,6 +194,8 @@ void RadioLibInterface::loop() assert(txp); startSend(txp); } + } else { + // DEBUG_MSG("done with txqueue\n"); } break; default: @@ -216,7 +220,7 @@ void RadioLibInterface::startTransmitTimer(bool withDelay) uint32_t delay = !withDelay ? 1 : random(MIN_TX_WAIT_MSEC, MAX_TX_WAIT_MSEC); // See documentation for loop() wrt these values // DEBUG_MSG("xmit timer %d\n", delay); - + // DEBUG_MSG("delaying %u\n", delay); setPeriod(delay); } } diff --git a/src/sleep.cpp b/src/sleep.cpp index 18462de79..ceb21404c 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -111,8 +111,8 @@ void initDeepSleep() #endif } -/// return true if sleep is allowed -static bool doPreflightSleep() + +bool doPreflightSleep() { if (preflightSleep.notifyObservers(NULL) != 0) return false; // vetoed diff --git a/src/sleep.h b/src/sleep.h index b3446882a..66eafa611 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -19,6 +19,9 @@ void initDeepSleep(); void setCPUFast(bool on); void setLed(bool ledOn); +/** return true if sleep is allowed right now */ +bool doPreflightSleep(); + extern int bootCount; // is bluetooth sw currently running? From 8ccd59a7d8df2410c991fac1ea16f8f298b94166 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 10 Jun 2020 14:36:11 -0700 Subject: [PATCH 102/131] Fix #115: wake from light sleep if a character arrives on the serial port Note - we do this not by using the uart wake feature, but by the lower power GPIO edge feature. Recommend sending "Z" 0x5A - because that has many edges. Send the character 4 times to make sure the device is awake --- src/PowerFSM.cpp | 5 +++++ src/configuration.h | 3 +++ src/sleep.cpp | 13 ++++++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 8f67b148c..c495cd27f 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -62,8 +62,13 @@ static void lsIdle() secsSlept += sleepTime; // DEBUG_MSG("sleeping, flash led!\n"); + } + if (wakeCause == ESP_SLEEP_WAKEUP_UART) { + // Not currently used (because uart triggers in hw have problems) + powerFSM.trigger(EVENT_SERIAL_CONNECTED); } else { // We woke for some other reason (button press, uart, device interrupt) + //uint64_t status = esp_sleep_get_ext1_wakeup_status(); DEBUG_MSG("wakeCause %d\n", wakeCause); #ifdef BUTTON_PIN diff --git a/src/configuration.h b/src/configuration.h index bc64d3187..4759dfc73 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -258,7 +258,10 @@ along with this program. If not, see . #ifdef NO_ESP32 #define USE_SEGGER +#else +#define SERIAL0_RX_GPIO 3 // Always GPIO3 on ESP32 #endif + #ifdef USE_SEGGER #include "SEGGER_RTT.h" #define DEBUG_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__) diff --git a/src/sleep.cpp b/src/sleep.cpp index ceb21404c..8cb0f6798 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -14,6 +14,7 @@ #include "esp_pm.h" #include "rom/rtc.h" #include +#include #include "BluetoothUtil.h" @@ -111,7 +112,6 @@ void initDeepSleep() #endif } - bool doPreflightSleep() { if (preflightSleep.notifyObservers(NULL) != 0) @@ -257,6 +257,17 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r gpio_pullup_en((gpio_num_t)BUTTON_PIN); #endif +#ifdef SERIAL0_RX_GPIO + // We treat the serial port as a GPIO for a fast/low power way of waking, if we see a rising edge that means + // someone started to send something + + // Alas - doesn't work reliably, instead need to use the uart specific version (which burns a little power) + // FIXME: gpio 3 is RXD for serialport 0 on ESP32 + // Send a few Z characters to wake the port + gpio_wakeup_enable((gpio_num_t)SERIAL0_RX_GPIO, GPIO_INTR_LOW_LEVEL); + // uart_set_wakeup_threshold(UART_NUM_0, 3); + // esp_sleep_enable_uart_wakeup(0); +#endif #ifdef BUTTON_PIN gpio_wakeup_enable((gpio_num_t)BUTTON_PIN, GPIO_INTR_LOW_LEVEL); // when user presses, this button goes low #endif From 1f668046a0f16efdcfd4edd7d9a9c5517dd1753e Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 10 Jun 2020 18:23:20 -0700 Subject: [PATCH 103/131] if we can't sleep, at least have the processor block for 100ms --- src/PowerFSM.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index c495cd27f..aa926abd3 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -68,7 +68,7 @@ static void lsIdle() powerFSM.trigger(EVENT_SERIAL_CONNECTED); } else { // We woke for some other reason (button press, uart, device interrupt) - //uint64_t status = esp_sleep_get_ext1_wakeup_status(); + // uint64_t status = esp_sleep_get_ext1_wakeup_status(); DEBUG_MSG("wakeCause %d\n", wakeCause); #ifdef BUTTON_PIN @@ -84,6 +84,9 @@ static void lsIdle() powerFSM.trigger(EVENT_WAKE_TIMER); } } + } else { + // Someone says we can't sleep now, so just save some power by sleeping the CPU for 100ms or so + delay(100); } } else { // Time to stop sleeping! From 6edaadf5d8d729dda6da4857a1167ba42e373250 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 11 Jun 2020 21:14:53 -0700 Subject: [PATCH 104/131] Update BLE docs --- docs/software/TODO.md | 1 + docs/software/bluetooth-api.md | 25 +++++++++++++++++-------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 93ceb20d2..d00c7acd6 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -58,6 +58,7 @@ During the beta timeframe the following improvements 'would be nice' (and yeah - Items after the first final candidate release. +- add "store and forward" support for messages, or move to the DB sync model. This would allow messages to be eventually delivered even if nodes are out of contact at the moment. - use variable length arduino Strings in protobufs (instead of current fixed buffers) - use BLEDevice::setPower to lower our BLE transmit power - extra range doesn't help us, it costs amps and it increases snoopability - make an install script to let novices install software on their boards diff --git a/docs/software/bluetooth-api.md b/docs/software/bluetooth-api.md index cc0df5a1d..78bcb6539 100644 --- a/docs/software/bluetooth-api.md +++ b/docs/software/bluetooth-api.md @@ -10,19 +10,28 @@ This device will work with any MTU size, but it is highly recommended that you c This is the main bluetooth service for the device and provides the API your app should use to get information about the mesh, send packets or provision the radio. -For a reference implementation of a client that uses this service see [RadioInterfaceService](https://github.com/meshtastic/Meshtastic-Android/blob/master/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt). Typical flow when -a phone connects to the device should be the following: +For a reference implementation of a client that uses this service see [RadioInterfaceService](https://github.com/meshtastic/Meshtastic-Android/blob/master/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt). +Typical flow when a phone connects to the device should be the following (if you want to watch this flow from the python app just run "meshtastic --debug --info" - the flow over BLE is identical): + +- There are only three relevant endpoints (and they have built in BLE documentation - so use a BLE tool of your choice to watch them): FromRadio, FromNum (sends notifies when new data is available in FromRadio) and ToRadio - SetMTU size to 512 +- Write a ToRadio.startConfig protobuf to the "ToRadio" endpoint" - this tells the radio you are a new connection and you need the entire NodeDB sent down. +- Read repeatedly from the "FromRadio" endpoint. Each time you read you will get back a FromRadio protobuf (see Meshtatastic-protobuf). Keep reading from this endpoint until you get back and empty buffer. +- See below for the expected sequence for your initial download. +- After the initial download, you should subscribe for BLE "notify" on the "FromNum" endpoint. If a notification arrives, that means there are now one or more FromRadio packets waiting inside FromRadio. Read from FromRadio until you get back an empty packet. +- Any time you want to send packets to the radio, you should write a ToRadio packet into ToRadio. + +Expected sequence for initial download: + +- After your send startConfig, you will receive a series of FromRadio packets. The sequence of these packets will be as follows (but you are best not counting on this, instead just update your model for whatever packet you receive - based on looking at the type) - Read a RadioConfig from "radio" - used to get the channel and radio settings -- Read (and write if incorrect) a User from "user" - to get the username for this node +- Read a User from "user" - to get the username for this node - Read a MyNodeInfo from "mynode" to get information about this local device - Write an empty record to "nodeinfo" to restart the nodeinfo reading state machine -- Read from "nodeinfo" until it returns empty to build the phone's copy of the current NodeDB for the mesh -- Read from "fromradio" until it returns empty to get any messages that arrived for this node while the phone was away -- Subscribe to notify on "fromnum" to get notified whenever the device has a new received packet -- Read that new packet from "fromradio" -- Whenever the phone has a packet to send write to "toradio" +- Read a series of NodeInfo packets to build the phone's copy of the current NodeDB for the mesh +- Read a endConfig packet that indicates that the entire state you need has been sent. +- Read a series of MeshPackets until it returns empty to get any messages that arrived for this node while the phone was away For definitions (and documentation) on FromRadio, ToRadio, MyNodeInfo, NodeInfo and User protocol buffers see [mesh.proto](https://github.com/meshtastic/Meshtastic-protobufs/blob/master/mesh.proto) From 99f825363710f6cacb772918e6f03cfb16e9ab9d Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 12 Jun 2020 08:59:48 -0700 Subject: [PATCH 105/131] protobuf updates --- proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto b/proto index 3ba76bbe4..b6b1cca5a 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 3ba76bbe4c98ee9c9e422d8dc10844cc9fb5272a +Subproject commit b6b1cca5ada3bc22c6b84761846e889315160db3 From dc169675e2b4768e8ee4f5f5f9e98b70942ca4d0 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 12 Jun 2020 09:01:28 -0700 Subject: [PATCH 106/131] Update TODO list --- docs/software/TODO.md | 201 ++++++------------------------------------ proto | 2 +- 2 files changed, 28 insertions(+), 175 deletions(-) diff --git a/docs/software/TODO.md b/docs/software/TODO.md index d00c7acd6..a2ad8e82f 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -1,206 +1,59 @@ # High priority -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. - # Medium priority 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. -- 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 ? -- check fcc rules on duty cycle. we might not need to freq hop. https://www.sunfiretesting.com/LoRa-FCC-Certification-Guide/ -- use fuse bits to store the board type and region. So one load can be used on all boards -- the BLE stack is leaking about 200 bytes each time we go to light sleep +- show battery level as % full - rx signal measurements -3 marginal, -9 bad, 10 great, -10 means almost unusable. So scale this into % signal strength. preferably as a graph, with an X indicating loss of comms. -- assign every "channel" a random shared 8 bit sync word (per 4.2.13.6 of datasheet) - use that word to filter packets before even checking CRC. This will ensure our CPU will only wake for packets on our "channel" -- Note: we do not do address filtering at the chip level, because we might need to route for the mesh - is in cleartext (so that nodes will route for other radios that are cryptoed with a key we don't know) -- add frequency hopping, dependent on the gps time, make the switch moment far from the time anyone is going to be transmitting -- share channel settings over Signal (or qr code) by embedding an an URL which is handled by the MeshUtil app. -- publish update articles on the web # Pre-beta priority -During the beta timeframe the following improvements 'would be nice' (and yeah - I guess some of these items count as features, but it is a hobby project ;-) ) +During the beta timeframe the following improvements 'would be nice' -- If the phone doesn't read fromradio mailbox within X seconds, assume the phone is gone and we can stop queing location msgs - for it (because it will redownload the nodedb when it comes back) -- Figure out why the RF95 ISR is never seeing RH_RF95_VALID_HEADER, so it is not protecting our rx packets from getting stomped on by sends -- fix the frequency error reading in the RF95 RX code (can't do floating point math in an ISR ;-) -- See CustomRF95::send and fix the problem of dropping partially received packets if we want to start sending -- make sure main cpu is not woken for packets with bad crc or not addressed to this node - do that in the radio hw -- triple check fcc compliance +- finish DSR for unicast +- check fcc rules on duty cycle. we might not need to freq hop. https://www.sunfiretesting.com/LoRa-FCC-Certification-Guide/ . Might need to add enforcement for europe though. - pick channel center frequency based on channel name? "dolphin" would hash to 900Mhz, "cat" to 905MHz etc? allows us to hide the concept of channel # from hte user. -- scan to find channels with low background noise? (Use CAD mode of the RF95 to automatically find low noise channels) - make a no bluetooth configured yet screen - include this screen in the loop if the user hasn't yet paired - if radio params change fundamentally, discard the nodedb -- reneable the bluetooth battery level service on the T-BEAM, because we can read battery level there - -# Spinoff project ideas - -- an open source version of https://www.burnair.ch/skynet/ -- a paragliding app like http://airwhere.co.uk/ -- a version with a solar cell for power, just mounted high to permanently provide routing for nodes in a valley. Someone just pointed me at disaster.radio -- How do avalanche beacons work? Could this do that as well? possibly by using beacon mode feature of the RF95? -- provide generalized (but slow) internet message forwarding servie if one of our nodes has internet connectivity +- re-enable the bluetooth battery level service on the T-BEAM +- implement first cut of router mode: preferentially handle flooding, and change sleep and GPS behaviors +- provide generalized (but slow) internet message forwarding service if one of our nodes has internet connectivity (MQTT) [ Not a requirement but a personal interest ] # Low priority Items after the first final candidate release. -- add "store and forward" support for messages, or move to the DB sync model. This would allow messages to be eventually delivered even if nodes are out of contact at the moment. -- use variable length arduino Strings in protobufs (instead of current fixed buffers) +- scan to find channels with low background noise? (Use CAD mode of the RF95 to automatically find low noise channels) +- If the phone doesn't read fromradio mailbox within X seconds, assume the phone is gone and we can stop queing location msgs + for it (because it will redownload the nodedb when it comes back) +- add frequency hopping, dependent on the gps time, make the switch moment far from the time anyone is going to be transmitting +- assign every "channel" a random shared 8 bit sync word (per 4.2.13.6 of datasheet) - use that word to filter packets before even checking CRC. This will ensure our CPU will only wake for packets on our "channel" +- the BLE stack is leaking about 200 bytes each time we go to light sleep +- use fuse bits to store the board type and region. So one load can be used on all boards +- 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 fixed32 for node IDs, packetIDs and lat/lon - will require all nodes to be updated, but make messages slightly smaller. +- add "store and forward" support for messages, or move to the DB sync model. This would allow messages to be eventually delivered even if nodes are out of contact at the moment. +- use variable length Strings in protobufs (instead of current fixed buffers). This would save lots of RAM - use BLEDevice::setPower to lower our BLE transmit power - extra range doesn't help us, it costs amps and it increases snoopability -- make an install script to let novices install software on their boards -- use std::map in node db -- make a HAM build: yep - that's a great idea. I'll add it to the TODO. should be pretty painless - just a new frequency list, a bool to say 'never do encryption' and use hte callsign as that node's unique id. -from Girts +- make a HAM build: just a new frequency list, a bool to say 'never do encryption' and use hte callsign as that node's unique id. -from Girts - don't forward redundant pings or ping responses to the phone, it just wastes phone battery -- use https://platformio.org/lib/show/1260/OneButton if necessary -- don't send location packets if we haven't moved +- don't send location packets if we haven't moved significantly - scrub default radio config settings for bandwidth/range/speed -- answer to pings (because some other user is looking at our nodeinfo) with our latest location (not a stale location) - show radio and gps signal strength as an image - only BLE advertise for a short time after the screen is on and button pressed - to save power and prevent people for sniffing for our BT app. -- make mesh aware network timing state machine (sync wake windows to gps time) +- make mesh aware network timing state machine (sync wake windows to gps time) - this can save LOTS of battery - split out the software update utility so other projects can use it. Have the appload specify the URL for downloads. - read the PMU battery fault indicators and blink/led/warn user on screen -- the AXP debug output says it is trying to charge at 700mA, but the max I've seen is 180mA, so AXP registers probably need to be set to tell them the circuit can only provide 300mAish max. So that the low charge rate kicks in faster and we don't wear out batteries. -- increase the max charging rate a bit for 18650s, currently it limits to 180mA (at 4V). Work backwards from the 500mA USB limit (at 5V) and let the AXP charge at that rate. - discard very old nodedb records (> 1wk) -- using the genpartitions based table doesn't work on TTGO so for now I stay with my old memory map -- We let anyone BLE scan for us (FIXME, perhaps only allow that until we are paired with a phone and configured) -- use two different buildenv flags for ttgo vs lora32. https://docs.platformio.org/en/latest/ide/vscode.html#key-bindings -- sim gps data for testing nodes that don't have hardware -- do debug serial logging to android over bluetooth -- break out my bluetooth OTA software as a seperate library so others can use it -- Heltec LoRa32 has 8MB flash, use a bigger partition table if needed - TTGO is 4MB but has PSRAM - add a watchdog timer - handle millis() rollover in GPS.getTime - otherwise we will break after 50 days - report esp32 device code bugs back to the mothership via android -# Done +# Spinoff project ideas -- change the partition table to take advantage of the 4MB flash on the wroom: http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables -- wrap in nice MeshRadio class -- add mesh send & rx -- make message send from android go to service, then to mesh radio -- make message receive from radio go through to android -- test loopback tx/rx path code without using radio -- notify phone when rx packets arrive, currently the phone polls at startup only -- figure out if we can use PA_BOOST - yes, it seems to be on both boards -- implement new ble characteristics -- have MeshService keep a node DB by sniffing user messages -- have a state machine return the correct FromRadio packet to the phone, it isn't always going to be a MeshPacket. Do a notify on fromnum to force the radio to read our state machine generated packets -- send my_node_num when phone sends WantsNodes -- have meshservice periodically send location data on mesh (if device has a GPS) -- implement getCurrentTime() - set based off gps but then updated locally -- make default owner record have valid usernames -- message loop between node 0x28 and 0x7c -- check in my radiolib fixes -- figure out what is busted with rx -- send our owner info at boot, reply if we see anyone send theirs -- add manager layers -- confirm second device receives that gps message and updates device db -- send correct hw vendor in the bluetooth info - needed so the android app can update different radio models -- correctly map nodeids to nodenums, currently we just do a proof of concept by always doing a broadcast -- add interrupt detach/sleep mode config to lora radio so we can enable deepsleep without panicing -- make jtag work on second board -- implement regen owner and radio prefs -- use a better font -- make nice screens (boot, about to sleep, debug info (gps signal, #people), latest text, person info - one frame per person on network) -- turn framerate from ui->state.frameState to 1 fps (or less) unless in transition -- switch to my gui layout manager -- make basic gui. different screens: debug, one page for each user in the user db, last received text message -- make button press cycle between screens -- save our node db on entry to sleep -- fix the logo -- sent/received packets (especially if a node was just reset) have variant of zero sometimes - I think there is a bug (race-condtion?) in the radio send/rx path. -- DONE dynamic nodenum assignment tasks -- make jtag debugger id stable: https://askubuntu.com/questions/49910/how-to-distinguish-between-identical-usb-to-serial-adapters -- reported altitude is crap -- good tips on which bands might be more free https://github.com/TheThingsNetwork/ttn/issues/119 -- finish power measurements (GPS on during sleep vs LCD on during sleep vs LORA on during sleep) and est battery life -- make screen sleep behavior work -- make screen advance only when a new node update arrives, a new text arrives or the user presses a button, turn off screen after a while -- after reboot, channel number is getting reset to zero! fix! -- send user and location events much less often -- send location (or if not available user) when the user wakes the device from display sleep (both for testing and to improve user experience) -- make real implementation of getNumOnlineNodes -- very occasionally send our position and user packet based on the schedule in the radio info (if for nothing else so that other nodes update last_seen) -- show real text info on the text screen -- apply radio settings from android land -- cope with nodes that have 0xff or 0x00 as the last byte of their mac -- allow setting full radio params from android -- add receive timestamps to messages, inserted by esp32 when message is received but then shown on the phone -- update build to generate both board types -- have node info screen show real info (including distance and heading) -- blink the power led less often -- have radiohead ISR send messages to RX queue directly, to allow that thread to block until we have something to send -- move lora rx/tx to own thread and block on IO -- keep our pseudo time moving forward even if we enter deep sleep (use esp32 rtc) -- for non GPS equipped devices, set time from phone -- GUI on oled hangs for a few seconds occasionally, but comes back -- update local GPS position (but do not broadcast) at whatever rate the GPS is giving it -- don't send our times to other nodes -- don't trust times from other nodes -- draw compass rose based off local walking track -- add requestResponse optional bool - use for location broadcasts when sending tests -- post sample video to signal forum -- support non US frequencies -- send pr https://github.com/ThingPulse/esp8266-oled-ssd1306 to tell them about this project -- document rules for sleep wrt lora/bluetooth/screen/gps. also: if I have text messages (only) for the phone, then give a few seconds in the hopes BLE can get it across before we have to go back to sleep. -- wake from light sleep as needed for our next scheduled periodic task (needed for gps position broadcasts etc) -- turn bluetooth off based on our sleep policy -- blink LED while in LS sleep mode -- scrolling between screens based on press is busted -- Use Neo-M8M API to put it in sleep mode (on hold until my new boards arrive) -- update the prebuilt bins for different regulatory regions -- don't enter NB state if we've recently talked to the phone (to prevent breaking syncing or bluetooth sw update) -- have sw update prevent BLE sleep -- manually delete characteristics/descs -- leave lora receiver always on -- protobufs are sometimes corrupted after sleep! -- stay awake while charging -- check gps battery voltage -- if a position report includes ground truth time and we don't have time yet, set our clock from that. It is better than nothing. -- retest BLE software update for both board types -- report on wikifactory -- send note to the guy who designed the cases -- turn light sleep on aggressively (while lora is on but BLE off) -- Use the Periodic class for both position and user periodic broadcasts -- don't treat north as up, instead adjust shown bearings for our guess at the users heading (i.e. subtract one from the other) -- sendToMesh can currently block for a long time, instead have it just queue a packet for a radio freertos thread -- don't even power on bluetooth until we have some data to send to the android phone. Most of the time we should be sleeping in a lowpower "listening for lora" only mode. Once we have some packets for the phone, then power on bluetooth - until the phone pulls those packets. Ever so often power on bluetooth just so we can see if the phone wants to send some packets. Possibly might need ULP processor to help with this wake process. -- do hibernation mode to get power draw down to 2.5uA https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/ -- fix GPS.zeroOffset calculation it is wrong -- (needs testing) fixed the following during a plane flight: - Have state machine properly enter deep sleep based on loss of mesh and phone comms. - Default to enter deep sleep if no LORA received for two hours (indicates user has probably left the mesh). -- (fixed I think) text messages are not showing on local screen if screen was on -- add links to todos -- link to the kanban page -- add a getting started page -- finish mesh alg reeval -- ublox gps parsing seems a little buggy (we shouldn't be sending out read solution commands, the device is already broadcasting them) -- turn on gps https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/blob/master/examples/Example18_PowerSaveMode/Example18_PowerSaveMode.ino -- switch gps to 38400 baud https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/blob/master/examples/Example11_ResetModule/Example2_FactoryDefaultsviaSerial/Example2_FactoryDefaultsviaSerial.ino -- Use Neo-M8M API to put it in sleep mode -- use gps sleep mode instead of killing its power (to allow fast position when we wake) -- 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 +- an open source version of https://www.burnair.ch/skynet/ +- a paragliding app like http://airwhere.co.uk/ +- How do avalanche beacons work? Could this do that as well? possibly by using beacon mode feature of the RF95? diff --git a/proto b/proto index b6b1cca5a..e7f181ef6 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit b6b1cca5ada3bc22c6b84761846e889315160db3 +Subproject commit e7f181ef6fd4e38c40e0d0be552149f8bbe75a66 From 88b91de197e50e09e27e56aa8f9c4493927dc39e Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 12 Jun 2020 11:53:59 -0700 Subject: [PATCH 107/131] Prepare to make MemoryDynamic --- src/mesh/MemoryPool.h | 88 ++++++++++++++++++++++++++----------------- src/mesh/MeshTypes.h | 2 +- src/mesh/Router.cpp | 4 +- 3 files changed, 58 insertions(+), 36 deletions(-) diff --git a/src/mesh/MemoryPool.h b/src/mesh/MemoryPool.h index 89c514c90..7f051af13 100644 --- a/src/mesh/MemoryPool.h +++ b/src/mesh/MemoryPool.h @@ -5,12 +5,58 @@ #include "PointerQueue.h" +template class Allocator +{ + + public: + virtual ~Allocator() {} + + /// Return a queable object which has been prefilled with zeros. Panic if no buffer is available + /// Note: this method is safe to call from regular OR ISR code + T *allocZeroed() + { + T *p = allocZeroed(0); + + assert(p); // FIXME panic instead + return p; + } + + /// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you probably + /// don't want this version). + T *allocZeroed(TickType_t maxWait) + { + T *p = alloc(maxWait); + assert(p); + + if (p) + memset(p, 0, sizeof(T)); + return p; + } + + /// Return a queable object which is a copy of some other object + T *allocCopy(const T &src, TickType_t maxWait = portMAX_DELAY) + { + T *p = alloc(maxWait); + assert(p); + + if (p) + *p = src; + return p; + } + + /// Return a buffer for use by others + virtual void release(T *p) = 0; + + protected: + // Alloc some storage + virtual T *alloc(TickType_t maxWait) = 0; +}; + /** * A pool based allocator * - * Eventually this routine will even be safe for ISR use... */ -template class MemoryPool +template class MemoryPool : public Allocator { PointerQueue dead; @@ -30,39 +76,8 @@ template class MemoryPool ~MemoryPool() { delete[] buf; } - /// Return a queable object which has been prefilled with zeros. Panic if no buffer is available - /// Note: this method is safe to call from regular OR ISR code - T *allocZeroed() - { - T *p = allocZeroed(0); - - assert(p); // FIXME panic instead - return p; - } - - /// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you probably - /// don't want this version). - T *allocZeroed(TickType_t maxWait) - { - T *p = dead.dequeuePtr(maxWait); - - if (p) - memset(p, 0, sizeof(T)); - return p; - } - - /// Return a queable object which is a copy of some other object - T *allocCopy(const T &src, TickType_t maxWait = portMAX_DELAY) - { - T *p = dead.dequeuePtr(maxWait); - - if (p) - *p = src; - return p; - } - /// Return a buffer for use by others - void release(T *p) + virtual void release(T *p) { assert(dead.enqueue(p, 0)); assert(p >= buf && @@ -78,4 +93,9 @@ template class MemoryPool (size_t)(p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool } + + protected: + /// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you + /// probably don't want this version). + virtual T *alloc(TickType_t maxWait) { return dead.dequeuePtr(maxWait); } }; diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index 32ec2b08d..7c58b2e3e 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -29,4 +29,4 @@ typedef uint32_t PacketId; // A packet sequence number typedef int ErrorCode; /// Alloc and free packets to our global, ISR safe pool -extern MemoryPool packetPool; \ No newline at end of file +extern Allocator &packetPool; \ No newline at end of file diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 2f8dd5e28..f9b196d60 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -23,7 +23,9 @@ (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + MAX_TX_QUEUE + \ 2) // max number of packets which can be in flight (either queued from reception or queued for sending) -MemoryPool packetPool(MAX_PACKETS); + +static MemoryPool staticPool(MAX_PACKETS); +Allocator &packetPool = staticPool; /** * Constructor From f0b8f10665498566df231a5fa06c729786e66535 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 12 Jun 2020 12:11:18 -0700 Subject: [PATCH 108/131] Fix #149: Use a simple heap allocator for now, after 1.0 we can go to fixed sized pools to protect against fragmentation. --- docs/software/TODO.md | 1 + src/mesh/MemoryPool.h | 20 +++++++++++++++++++- src/mesh/Router.cpp | 3 ++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/software/TODO.md b/docs/software/TODO.md index a2ad8e82f..bbec4546c 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -24,6 +24,7 @@ During the beta timeframe the following improvements 'would be nice' Items after the first final candidate release. +- Change back to using a fixed sized MemoryPool rather than MemoryDynamic (see bug #149) - scan to find channels with low background noise? (Use CAD mode of the RF95 to automatically find low noise channels) - If the phone doesn't read fromradio mailbox within X seconds, assume the phone is gone and we can stop queing location msgs for it (because it will redownload the nodedb when it comes back) diff --git a/src/mesh/MemoryPool.h b/src/mesh/MemoryPool.h index 7f051af13..8c6986e16 100644 --- a/src/mesh/MemoryPool.h +++ b/src/mesh/MemoryPool.h @@ -26,7 +26,6 @@ template class Allocator T *allocZeroed(TickType_t maxWait) { T *p = alloc(maxWait); - assert(p); if (p) memset(p, 0, sizeof(T)); @@ -52,6 +51,25 @@ template class Allocator virtual T *alloc(TickType_t maxWait) = 0; }; +/** + * An allocator that just uses regular free/malloc + */ +template class MemoryDynamic : public Allocator +{ + public: + /// Return a buffer for use by others + virtual void release(T *p) + { + assert(p); + free(p); + } + + protected: + /// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you + /// probably don't want this version). + virtual T *alloc(TickType_t maxWait) { return (T *)malloc(sizeof(T)); } +}; + /** * A pool based allocator * diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index f9b196d60..914e50d09 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -23,8 +23,9 @@ (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + MAX_TX_QUEUE + \ 2) // max number of packets which can be in flight (either queued from reception or queued for sending) +// static MemoryPool staticPool(MAX_PACKETS); +static MemoryDynamic staticPool; -static MemoryPool staticPool(MAX_PACKETS); Allocator &packetPool = staticPool; /** From de37e1bbabba430fd7a3636d2ee8a11f1deddb0e Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 12 Jun 2020 15:40:36 -0700 Subject: [PATCH 109/131] todo notes --- docs/software/TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/software/TODO.md b/docs/software/TODO.md index bbec4546c..9043a1616 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -35,7 +35,7 @@ Items after the first final candidate 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 fixed32 for node IDs, packetIDs and lat/lon - will require all nodes to be updated, but make messages slightly smaller. +- Use fixed32 for node IDs, packetIDs, successid, failid, and lat/lon - will require all nodes to be updated, but make messages slightly smaller. - add "store and forward" support for messages, or move to the DB sync model. This would allow messages to be eventually delivered even if nodes are out of contact at the moment. - use variable length Strings in protobufs (instead of current fixed buffers). This would save lots of RAM - use BLEDevice::setPower to lower our BLE transmit power - extra range doesn't help us, it costs amps and it increases snoopability From a8d4b5479d0889365164df6a36b2915e8a453183 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 12 Jun 2020 15:40:56 -0700 Subject: [PATCH 110/131] don't start the BLE update service for now - the android side isn't ready --- src/esp32/BluetoothUtil.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/esp32/BluetoothUtil.cpp b/src/esp32/BluetoothUtil.cpp index ccaf41639..067dcee1a 100644 --- a/src/esp32/BluetoothUtil.cpp +++ b/src/esp32/BluetoothUtil.cpp @@ -223,11 +223,14 @@ void deinitBLE() pServer->getAdvertising()->stop(); - destroyUpdateService(); + if (pUpdate != NULL) { + destroyUpdateService(); + + pUpdate->stop(); + pUpdate->stop(); // we delete them below + } - pUpdate->stop(); pDevInfo->stop(); - pUpdate->stop(); // we delete them below // First shutdown bluetooth BLEDevice::deinit(false); @@ -235,7 +238,8 @@ void deinitBLE() // do not delete this - it is dynamically allocated, but only once - statically in BLEDevice // delete pServer->getAdvertising(); - delete pUpdate; + if (pUpdate != NULL) + delete pUpdate; delete pDevInfo; delete pServer; @@ -276,15 +280,18 @@ BLEServer *initBLE(StartBluetoothPinScreenCallback startBtPinScreen, StopBluetoo // We now let users create the battery service only if they really want (not all devices have a battery) // BLEService *pBattery = createBatteryService(pServer); +#ifdef BLE_SOFTWARE_UPDATE // Disable for now pUpdate = createUpdateService(pServer, hwVendor, swVersion, hwVersion); // We need to advertise this so our android ble scan operation can see it + pUpdate->start(); +#endif + // It seems only one service can be advertised - so for now don't advertise our updater // pServer->getAdvertising()->addServiceUUID(pUpdate->getUUID()); // start all our services (do this after creating all of them) pDevInfo->start(); - pUpdate->start(); // FIXME turn on this restriction only after the device is paired with a phone // advert->setScanFilter(false, true); // We let anyone scan for us (FIXME, perhaps only allow that until we are paired with a From 03cb3c2145603c3c4795014739bf5f2974bae7c3 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 12 Jun 2020 16:37:03 -0700 Subject: [PATCH 111/131] basic stack debugging - we are okay for now --- src/WorkerThread.cpp | 12 ++++++++++-- src/WorkerThread.h | 2 ++ src/main.cpp | 9 +++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/WorkerThread.cpp b/src/WorkerThread.cpp index f84d83be2..bf38a9266 100644 --- a/src/WorkerThread.cpp +++ b/src/WorkerThread.cpp @@ -1,4 +1,5 @@ #include "WorkerThread.h" +#include "debug.h" #include void Thread::start(const char *name, size_t stackSize, uint32_t priority) @@ -16,6 +17,15 @@ void WorkerThread::doRun() { while (!wantExit) { block(); + +#ifdef DEBUG_STACK + static uint32_t lastPrint = 0; + if (millis() - lastPrint > 10 * 1000L) { + lastPrint = millis(); + meshtastic::printThreadInfo("net"); + } +#endif + loop(); } } @@ -28,8 +38,6 @@ void NotifiedWorkerThread::notify(uint32_t v, eNotifyAction action) xTaskNotify(taskHandle, v, action); } - - void NotifiedWorkerThread::block() { xTaskNotifyWait(0, // don't clear notification on entry diff --git a/src/WorkerThread.h b/src/WorkerThread.h index 86ec08e13..655e316f8 100644 --- a/src/WorkerThread.h +++ b/src/WorkerThread.h @@ -15,6 +15,8 @@ class Thread virtual ~Thread() { vTaskDelete(taskHandle); } + uint32_t getStackHighwaterMark() { return uxTaskGetStackHighWaterMark(taskHandle); } + protected: /** * The method that will be called when start is called. diff --git a/src/main.cpp b/src/main.cpp index d7fb21bd1..0c9fe5ca3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,6 +33,7 @@ #include "power.h" // #include "rom/rtc.h" #include "DSRRouter.h" +#include "debug.h" #include "main.h" #include "screen.h" #include "sleep.h" @@ -314,6 +315,14 @@ void loop() showingBootScreen = false; } +#ifdef DEBUG_STACK + static uint32_t lastPrint = 0; + if (millis() - lastPrint > 10 * 1000L) { + lastPrint = millis(); + meshtastic::printThreadInfo("main"); + } +#endif + // Update the screen last, after we've figured out what to show. screen.debug()->setNodeNumbersStatus(nodeDB.getNumOnlineNodes(), nodeDB.getNumNodes()); screen.debug()->setChannelNameStatus(channelSettings.name); From 47e614c7d63fca402f14f6d43c48a3bd6040d39c Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 13 Jun 2020 08:26:48 -0700 Subject: [PATCH 112/131] fix #172 We need our own branch because we need this fix and associated pullrequest https://github.com/espressif/arduino-esp32/pull/4085 --- docs/software/TODO.md | 6 ++++++ platformio.ini | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 9043a1616..840156e5c 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -1,9 +1,15 @@ # High priority +- why is the net so chatty now? +- E22 bringup +- encryption review findings writeup +- turn on modem-sleep mode + # Medium priority Items to complete before the first beta release. +- turn on watchdog timer (because lib code seems buggy) - show battery level as % full - rx signal measurements -3 marginal, -9 bad, 10 great, -10 means almost unusable. So scale this into % signal strength. preferably as a graph, with an X indicating loss of comms. diff --git a/platformio.ini b/platformio.ini index c51e0954e..791bfed20 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,8 +22,6 @@ default_envs = tbeam ; Note: the github actions CI test build can't yet build NR ; HW_VERSION (default emptystring) [env] -platform = espressif32 -framework = arduino ; customize the partition table ; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables @@ -79,6 +77,8 @@ lib_deps = ; Common settings for ESP targes, mixin with extends = esp32_base [esp32_base] +platform = espressif32 +framework = arduino src_filter = ${env.src_filter} - upload_speed = 921600 @@ -86,6 +86,8 @@ debug_init_break = tbreak setup build_flags = ${env.build_flags} -Wall -Wextra -Isrc/esp32 lib_ignore = segger_rtt +platform_packages = + framework-arduinoespressif32 @ https://github.com/meshtastic/arduino-esp32.git ; The 1.0 release of the TBEAM board [env:tbeam] From db66e4dc008c573957fd0bdee76c3f25eda4e17f Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 13 Jun 2020 08:27:25 -0700 Subject: [PATCH 113/131] ensure we never get null from malloc --- src/mesh/MemoryPool.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/mesh/MemoryPool.h b/src/mesh/MemoryPool.h index 8c6986e16..4fefb4c4d 100644 --- a/src/mesh/MemoryPool.h +++ b/src/mesh/MemoryPool.h @@ -65,9 +65,13 @@ template class MemoryDynamic : public Allocator } protected: - /// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you - /// probably don't want this version). - virtual T *alloc(TickType_t maxWait) { return (T *)malloc(sizeof(T)); } + // Alloc some storage + virtual T *alloc(TickType_t maxWait) + { + T *p = (T *)malloc(sizeof(T)); + assert(p); + return p; + } }; /** From f54b18f7337745dacf8278e347e855e060e4467b Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 13 Jun 2020 08:27:44 -0700 Subject: [PATCH 114/131] each tx packet might have a retransmission/ack copy, make pool bigger --- src/mesh/Router.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 914e50d09..7ecb6e72f 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -19,8 +19,9 @@ 4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big // I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX +// And every TX packet might have a retransmission packet or an ack alive at any moment #define MAX_PACKETS \ - (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + MAX_TX_QUEUE + \ + (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \ 2) // max number of packets which can be in flight (either queued from reception or queued for sending) // static MemoryPool staticPool(MAX_PACKETS); From dc7469c64ba81486540a6f8b5e2458d4df6e4a4f Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 13 Jun 2020 08:28:01 -0700 Subject: [PATCH 115/131] useful bluetooth debugging output --- src/esp32/main-esp32.cpp | 5 +++++ src/main.cpp | 2 ++ src/mesh/PhoneAPI.cpp | 6 +++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index 67256bc7f..2b4711d5e 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -160,6 +160,11 @@ void esp32Setup() DEBUG_MSG("Setting random seed %u\n", seed); randomSeed(seed); // ESP docs say this is fairly random + DEBUG_MSG("Total heap: %d\n", ESP.getHeapSize()); + DEBUG_MSG("Free heap: %d\n", ESP.getFreeHeap()); + DEBUG_MSG("Total PSRAM: %d\n", ESP.getPsramSize()); + DEBUG_MSG("Free PSRAM: %d\n", ESP.getFreePsram()); + #ifdef AXP192_SLAVE_ADDRESS axp192Init(); #endif diff --git a/src/main.cpp b/src/main.cpp index 0c9fe5ca3..ded027f51 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -283,6 +283,8 @@ void loop() DEBUG_PORT.loop(); // Send/receive protobufs over the serial port #endif + // heap_caps_check_integrity_all(true); // FIXME - disable this expensive check + #ifndef NO_ESP32 esp32Loop(); #endif diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index b4c5538ca..ce8b13d22 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -91,8 +91,12 @@ void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) */ size_t PhoneAPI::getFromRadio(uint8_t *buf) { - if (!available()) + if (!available()) { + DEBUG_MSG("getFromRadio, !available\n"); return false; + } else { + DEBUG_MSG("getFromRadio, state=%d\n", state); + } // In case we send a FromRadio packet memset(&fromRadioScratch, 0, sizeof(fromRadioScratch)); From 575a15e1359a4df75c26b8cbda6731bf16af86fd Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 13 Jun 2020 08:29:41 -0700 Subject: [PATCH 116/131] remove more dead rev1 protocol code --- src/esp32/MeshBluetoothService.cpp | 33 ------------------------------ 1 file changed, 33 deletions(-) diff --git a/src/esp32/MeshBluetoothService.cpp b/src/esp32/MeshBluetoothService.cpp index 3e803ad66..2c05b7faa 100644 --- a/src/esp32/MeshBluetoothService.cpp +++ b/src/esp32/MeshBluetoothService.cpp @@ -41,39 +41,6 @@ class BluetoothPhoneAPI : public PhoneAPI BluetoothPhoneAPI *bluetoothPhoneAPI; -class ProtobufCharacteristic : public CallbackCharacteristic -{ - const pb_msgdesc_t *fields; - void *my_struct; - - public: - ProtobufCharacteristic(const char *uuid, uint32_t btprops, const pb_msgdesc_t *_fields, void *_my_struct) - : CallbackCharacteristic(uuid, btprops), fields(_fields), my_struct(_my_struct) - { - setCallbacks(this); - } - - void onRead(BLECharacteristic *c) - { - size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), fields, my_struct); - DEBUG_MSG("pbread from %s returns %d bytes\n", c->getUUID().toString().c_str(), numbytes); - c->setValue(trBytes, numbytes); - } - - void onWrite(BLECharacteristic *c) { writeToDest(c, my_struct); } - - protected: - /// like onWrite, but we provide an different destination to write to, for use by subclasses that - /// want to optionally ignore parts of writes. - /// returns true for success - bool writeToDest(BLECharacteristic *c, void *dest) - { - // dumpCharacteristic(pCharacteristic); - std::string src = c->getValue(); - DEBUG_MSG("pbwrite to %s of %d bytes\n", c->getUUID().toString().c_str(), src.length()); - return pb_decode_from_bytes((const uint8_t *)src.c_str(), src.length(), fields, dest); - } -}; class ToRadioCharacteristic : public CallbackCharacteristic { From d5deb49d20e2d9d0fef9f5f0becdd7b7e5b0f5ac Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 13 Jun 2020 11:05:13 -0700 Subject: [PATCH 117/131] use executeDelete to prevent leaking BLE handles --- src/esp32/BluetoothUtil.cpp | 104 +++++++++++++++-------------- src/esp32/MeshBluetoothService.cpp | 1 + 2 files changed, 55 insertions(+), 50 deletions(-) diff --git a/src/esp32/BluetoothUtil.cpp b/src/esp32/BluetoothUtil.cpp index 067dcee1a..61f6e1592 100644 --- a/src/esp32/BluetoothUtil.cpp +++ b/src/esp32/BluetoothUtil.cpp @@ -8,53 +8,6 @@ SimpleAllocator btPool; -/** - * Create standard device info service - **/ -BLEService *createDeviceInfomationService(BLEServer *server, std::string hwVendor, std::string swVersion, - std::string hwVersion = "") -{ - BLEService *deviceInfoService = server->createService(BLEUUID((uint16_t)ESP_GATT_UUID_DEVICE_INFO_SVC)); - - BLECharacteristic *swC = - new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ); - BLECharacteristic *mfC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_MANU_NAME), BLECharacteristic::PROPERTY_READ); - // BLECharacteristic SerialNumberCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_SERIAL_NUMBER_STR), - // BLECharacteristic::PROPERTY_READ); - - /* - * Mandatory characteristic for device info service? - - BLECharacteristic *m_pnpCharacteristic = m_deviceInfoService->createCharacteristic(ESP_GATT_UUID_PNP_ID, - BLECharacteristic::PROPERTY_READ); - - uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version; - uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> - 8), (uint8_t) version }; m_pnpCharacteristic->setValue(pnp, sizeof(pnp)); - */ - swC->setValue(swVersion); - deviceInfoService->addCharacteristic(addBLECharacteristic(swC)); - mfC->setValue(hwVendor); - deviceInfoService->addCharacteristic(addBLECharacteristic(mfC)); - if (!hwVersion.empty()) { - BLECharacteristic *hwvC = - new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ); - hwvC->setValue(hwVersion); - deviceInfoService->addCharacteristic(addBLECharacteristic(hwvC)); - } - // SerialNumberCharacteristic.setValue("FIXME"); - // deviceInfoService->addCharacteristic(&SerialNumberCharacteristic); - - // m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, - // BLECharacteristic::PROPERTY_READ); m_manufacturerCharacteristic->setValue(name); - - /* add these later? - ESP_GATT_UUID_SYSTEM_ID - */ - - // caller must call service->start(); - return deviceInfoService; -} bool _BLEClientConnected = false; @@ -106,6 +59,54 @@ void addWithDesc(BLEService *service, BLECharacteristic *c, const char *descript addBLEDescriptor(desc); } +/** + * Create standard device info service + **/ +BLEService *createDeviceInfomationService(BLEServer *server, std::string hwVendor, std::string swVersion, + std::string hwVersion = "") +{ + BLEService *deviceInfoService = server->createService(BLEUUID((uint16_t)ESP_GATT_UUID_DEVICE_INFO_SVC)); + + BLECharacteristic *swC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ); + BLECharacteristic *mfC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_MANU_NAME), BLECharacteristic::PROPERTY_READ); + // BLECharacteristic SerialNumberCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_SERIAL_NUMBER_STR), + // BLECharacteristic::PROPERTY_READ); + + /* + * Mandatory characteristic for device info service? + + BLECharacteristic *m_pnpCharacteristic = m_deviceInfoService->createCharacteristic(ESP_GATT_UUID_PNP_ID, + BLECharacteristic::PROPERTY_READ); + + uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version; + uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> + 8), (uint8_t) version }; m_pnpCharacteristic->setValue(pnp, sizeof(pnp)); + */ + swC->setValue(swVersion); + deviceInfoService->addCharacteristic(addBLECharacteristic(swC)); + mfC->setValue(hwVendor); + deviceInfoService->addCharacteristic(addBLECharacteristic(mfC)); + if (!hwVersion.empty()) { + BLECharacteristic *hwvC = + new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ); + hwvC->setValue(hwVersion); + deviceInfoService->addCharacteristic(addBLECharacteristic(hwvC)); + } + // SerialNumberCharacteristic.setValue("FIXME"); + // deviceInfoService->addCharacteristic(&SerialNumberCharacteristic); + + // m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, + // BLECharacteristic::PROPERTY_READ); m_manufacturerCharacteristic->setValue(name); + + /* add these later? + ESP_GATT_UUID_SYSTEM_ID + */ + + // caller must call service->start(); + return deviceInfoService; +} + + static BLECharacteristic *batteryLevelC; /** @@ -226,11 +227,12 @@ void deinitBLE() if (pUpdate != NULL) { destroyUpdateService(); - pUpdate->stop(); pUpdate->stop(); // we delete them below + pUpdate->executeDelete(); } pDevInfo->stop(); + pDevInfo->executeDelete(); // First shutdown bluetooth BLEDevice::deinit(false); @@ -245,8 +247,9 @@ void deinitBLE() batteryLevelC = NULL; // Don't let anyone generate bogus notifies - for (int i = 0; i < numChars; i++) + for (int i = 0; i < numChars; i++) { delete chars[i]; + } numChars = 0; for (int i = 0; i < numDescs; i++) @@ -280,7 +283,8 @@ BLEServer *initBLE(StartBluetoothPinScreenCallback startBtPinScreen, StopBluetoo // We now let users create the battery service only if they really want (not all devices have a battery) // BLEService *pBattery = createBatteryService(pServer); -#ifdef BLE_SOFTWARE_UPDATE // Disable for now +#define BLE_SOFTWARE_UPDATE +#ifdef BLE_SOFTWARE_UPDATE pUpdate = createUpdateService(pServer, hwVendor, swVersion, hwVersion); // We need to advertise this so our android ble scan operation can see it diff --git a/src/esp32/MeshBluetoothService.cpp b/src/esp32/MeshBluetoothService.cpp index 2c05b7faa..0f3b512e2 100644 --- a/src/esp32/MeshBluetoothService.cpp +++ b/src/esp32/MeshBluetoothService.cpp @@ -133,6 +133,7 @@ void stopMeshBluetoothService() { assert(meshService); meshService->stop(); + meshService->executeDelete(); } void destroyMeshBluetoothService() From 8caa075bc60fda49e149c936457a797ea848de9d Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 13 Jun 2020 11:05:36 -0700 Subject: [PATCH 118/131] used fixed pool allocator for now - since that's how we've been testing --- src/mesh/Router.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 7ecb6e72f..bbf03944f 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -24,8 +24,8 @@ (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \ 2) // max number of packets which can be in flight (either queued from reception or queued for sending) -// static MemoryPool staticPool(MAX_PACKETS); -static MemoryDynamic staticPool; +static MemoryPool staticPool(MAX_PACKETS); +// static MemoryDynamic staticPool; Allocator &packetPool = staticPool; From 8a1754efe8366e102075c2888248c4bd760b9651 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 13 Jun 2020 11:36:45 -0700 Subject: [PATCH 119/131] leave the software update service off for now - no one is using ityet --- docs/software/TODO.md | 3 ++- src/esp32/BluetoothUtil.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 840156e5c..e0dcb2459 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -1,9 +1,10 @@ # High priority - why is the net so chatty now? +- do a release - E22 bringup - encryption review findings writeup -- turn on modem-sleep mode +- turn on modem-sleep mode - https://github.com/espressif/arduino-esp32/issues/1142#issuecomment-512428852 # Medium priority diff --git a/src/esp32/BluetoothUtil.cpp b/src/esp32/BluetoothUtil.cpp index 61f6e1592..2833a7ede 100644 --- a/src/esp32/BluetoothUtil.cpp +++ b/src/esp32/BluetoothUtil.cpp @@ -283,7 +283,7 @@ BLEServer *initBLE(StartBluetoothPinScreenCallback startBtPinScreen, StopBluetoo // We now let users create the battery service only if they really want (not all devices have a battery) // BLEService *pBattery = createBatteryService(pServer); -#define BLE_SOFTWARE_UPDATE +// #define BLE_SOFTWARE_UPDATE #ifdef BLE_SOFTWARE_UPDATE pUpdate = createUpdateService(pServer, hwVendor, swVersion, hwVersion); // We need to advertise this so our android ble scan operation can see it From 13307c502f6e1d0bcb78fdac09d286717d4062b8 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 13 Jun 2020 16:29:53 -0700 Subject: [PATCH 120/131] misc debug output --- docs/software/TODO.md | 2 ++ src/esp32/MeshBluetoothService.cpp | 2 -- src/mesh/NodeDB.cpp | 4 +++- src/mesh/PhoneAPI.cpp | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/software/TODO.md b/docs/software/TODO.md index e0dcb2459..d646b589f 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -1,7 +1,9 @@ # High priority - why is the net so chatty now? +- three bakc to back sends are getting overritten - shows up as three separate writes with the same payload - might be a bug on the android side or the device side - probably android - do a release +- device wakes, turns BLE on and phone doesn't notice (while phone was sitting in auto-connect) - E22 bringup - encryption review findings writeup - turn on modem-sleep mode - https://github.com/espressif/arduino-esp32/issues/1142#issuecomment-512428852 diff --git a/src/esp32/MeshBluetoothService.cpp b/src/esp32/MeshBluetoothService.cpp index 0f3b512e2..9bc41459a 100644 --- a/src/esp32/MeshBluetoothService.cpp +++ b/src/esp32/MeshBluetoothService.cpp @@ -49,8 +49,6 @@ class ToRadioCharacteristic : public CallbackCharacteristic void onWrite(BLECharacteristic *c) { - DEBUG_MSG("Got on write\n"); - bluetoothPhoneAPI->handleToRadio(c->getData(), c->getValue().length()); } }; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 37233a3a0..e1ea45dd6 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -101,10 +101,12 @@ void NodeDB::resetRadioConfig() crypto->setKey(channelSettings.psk.size, channelSettings.psk.bytes); // temp hack for quicker testing + /* radioConfig.preferences.screen_on_secs = 30; radioConfig.preferences.wait_bluetooth_secs = 30; - radioConfig.preferences.position_broadcast_secs = 15; + radioConfig.preferences.position_broadcast_secs = 6 * 60; + radioConfig.preferences.ls_secs = 60; */ } diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index ce8b13d22..ec071fa5f 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -42,8 +42,9 @@ void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) if (pb_decode_from_bytes(buf, bufLength, ToRadio_fields, &toRadioScratch)) { switch (toRadioScratch.which_variant) { case ToRadio_packet_tag: { - // If our phone is sending a position, see if we can use it to set our RTC MeshPacket &p = toRadioScratch.variant.packet; + DEBUG_MSG("PACKET FROM PHONE: id=%d, to=%x, want_ack=%d, which1=%d, which2=%d, typ=%d, buflen=%d\n", p.id, p.to, p.want_ack, p.which_payload, + p.decoded.which_payload, p.decoded.data.typ, bufLength); service.handleToRadio(p); break; } From 112a94e572e9ec6d640e5576845485bc3ebef325 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 13 Jun 2020 16:48:34 -0700 Subject: [PATCH 121/131] 0.7.5 --- bin/version.sh | 2 +- docs/software/TODO.md | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/version.sh b/bin/version.sh index befa884d6..b918e13a1 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=0.7.4 \ No newline at end of file +export VERSION=0.7.5 \ No newline at end of file diff --git a/docs/software/TODO.md b/docs/software/TODO.md index d646b589f..0c742665d 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -1,7 +1,6 @@ # High priority - why is the net so chatty now? -- three bakc to back sends are getting overritten - shows up as three separate writes with the same payload - might be a bug on the android side or the device side - probably android - do a release - device wakes, turns BLE on and phone doesn't notice (while phone was sitting in auto-connect) - E22 bringup From 37c598833c49f00a518e5362fa32ab4bde952e73 Mon Sep 17 00:00:00 2001 From: Marcel van der Boom Date: Sun, 14 Jun 2020 10:28:23 +0200 Subject: [PATCH 122/131] Add support for SH1106 controller The SH1106 is almost indistinguisable from a SSD1306. - the nr of columns in the sh1106 is 132 vs 128 - use the proper includes/library functions when in use --- src/configuration.h | 4 ++++ src/screen.cpp | 4 ++++ src/screen.h | 9 +++++++++ 3 files changed, 17 insertions(+) diff --git a/src/configuration.h b/src/configuration.h index 4759dfc73..3a6626cf3 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -104,6 +104,10 @@ along with this program. If not, see . #define SSD1306_ADDRESS 0x3C +// The SH1106 controller is almost, but not quite, the same as SSD1306 +// Define this if you know you have that controller or your "SSD1306" misbehaves. +//#define USE_SH1106 + // 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 diff --git a/src/screen.cpp b/src/screen.cpp index 47f4f1c53..9198bc202 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -34,7 +34,11 @@ along with this program. If not, see . #define FONT_HEIGHT 14 // actually 13 for "ariel 10" but want a little extra space #define FONT_HEIGHT_16 (ArialMT_Plain_16[1] + 1) +#ifdef USE_SH1106 +#define SCREEN_WIDTH 132 +#else #define SCREEN_WIDTH 128 +#endif #define SCREEN_HEIGHT 64 #define TRANSITION_FRAMERATE 30 // fps #define IDLE_FRAMERATE 10 // in fps diff --git a/src/screen.h b/src/screen.h index be5444c1e..302c8e339 100644 --- a/src/screen.h +++ b/src/screen.h @@ -3,7 +3,12 @@ #include #include + +#ifdef USE_SH1106 +#include +#else #include +#endif #include "PeriodicTask.h" #include "TypedQueue.h" @@ -211,7 +216,11 @@ class Screen : public PeriodicTask /// Holds state for debug information DebugInfo debugInfo; /// Display device +#ifdef USE_SH1106 + SH1106Wire dispdev; +#else SSD1306Wire dispdev; +#endif /// UI helper for rendering to frames and switching between them OLEDDisplayUi ui; }; From 2c8d152885f3da07cfd72969a48236abddc86964 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 14 Jun 2020 15:30:21 -0700 Subject: [PATCH 123/131] Use old style (pre BLE 4.2) pairing, it seems more reliable --- src/esp32/BluetoothUtil.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/esp32/BluetoothUtil.cpp b/src/esp32/BluetoothUtil.cpp index 2833a7ede..3ad0e80d9 100644 --- a/src/esp32/BluetoothUtil.cpp +++ b/src/esp32/BluetoothUtil.cpp @@ -8,7 +8,6 @@ SimpleAllocator btPool; - bool _BLEClientConnected = false; class MyServerCallbacks : public BLEServerCallbacks @@ -67,7 +66,8 @@ BLEService *createDeviceInfomationService(BLEServer *server, std::string hwVendo { BLEService *deviceInfoService = server->createService(BLEUUID((uint16_t)ESP_GATT_UUID_DEVICE_INFO_SVC)); - 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); BLECharacteristic *mfC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_MANU_NAME), BLECharacteristic::PROPERTY_READ); // BLECharacteristic SerialNumberCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_SERIAL_NUMBER_STR), // BLECharacteristic::PROPERTY_READ); @@ -106,7 +106,6 @@ BLEService *createDeviceInfomationService(BLEServer *server, std::string hwVendo return deviceInfoService; } - static BLECharacteristic *batteryLevelC; /** @@ -304,7 +303,11 @@ BLEServer *initBLE(StartBluetoothPinScreenCallback startBtPinScreen, StopBluetoo static BLESecurity security; // static to avoid allocs BLESecurity *pSecurity = &security; pSecurity->setCapability(ESP_IO_CAP_OUT); - pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND); + + // FIXME - really should be ESP_LE_AUTH_REQ_SC_BOND but it seems there is a bug right now causing that bonding info to be lost + // occasionally + pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); return pServer; From aadba1f6949682e069834af7fc6ec2a8e9fd721a Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 14 Jun 2020 15:30:42 -0700 Subject: [PATCH 124/131] add printPacket for debug printing packets --- docs/software/TODO.md | 3 ++- src/esp32/main-esp32.cpp | 12 +++++++++ src/mesh/MeshService.cpp | 2 +- src/mesh/NodeDB.cpp | 2 +- src/mesh/PacketHistory.cpp | 4 +-- src/mesh/PhoneAPI.cpp | 4 +-- src/mesh/RadioInterface.cpp | 46 ++++++++++++++++++++++++++++++++++ src/mesh/RadioInterface.h | 3 +++ src/mesh/RadioLibInterface.cpp | 10 ++++---- src/mesh/ReliableRouter.cpp | 2 +- src/mesh/Router.cpp | 6 ++--- 11 files changed, 77 insertions(+), 17 deletions(-) diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 0c742665d..e26948017 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -1,7 +1,7 @@ # High priority - why is the net so chatty now? -- do a release +- CONFIG_CLASSIC_BT_ENABLED=n - device wakes, turns BLE on and phone doesn't notice (while phone was sitting in auto-connect) - E22 bringup - encryption review findings writeup @@ -60,6 +60,7 @@ Items after the first final candidate release. - add a watchdog timer - handle millis() rollover in GPS.getTime - otherwise we will break after 50 days - report esp32 device code bugs back to the mothership via android +- change BLE bonding to something more secure. see comment by pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND) # Spinoff project ideas diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index 2b4711d5e..cf02ebebf 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -154,6 +154,18 @@ void axp192Init() } #endif +/* +static void printBLEinfo() { + int dev_num = esp_ble_get_bond_device_num(); + + esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num); + esp_ble_get_bond_device_list(&dev_num, dev_list); + for (int i = 0; i < dev_num; i++) { + // esp_ble_remove_bond_device(dev_list[i].bd_addr); + } + +} */ + void esp32Setup() { uint32_t seed = esp_random(); diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 49cd0fe79..eebfb227e 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -160,7 +160,7 @@ int MeshService::handleFromRadio(const MeshPacket *mp) // If we veto a received User packet, we don't put it into the DB or forward it to the phone (to prevent confusing it) if (mp) { - DEBUG_MSG("Forwarding to phone, from=0x%x, rx_time=%u\n", mp->from, mp->rx_time); + printPacket("Forwarding to phone", mp); nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio fromNum++; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index e1ea45dd6..4e87ac773 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -30,7 +30,7 @@ DeviceState versions used to be defined in the .proto file but really only this #define here. */ -#define DEVICESTATE_CUR_VER 9 +#define DEVICESTATE_CUR_VER 10 #define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER #ifndef NO_ESP32 diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index 75005d408..3d1884ace 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -2,8 +2,6 @@ #include "configuration.h" #include "mesh-pb-constants.h" - - PacketHistory::PacketHistory() { recentPackets.reserve(MAX_NUM_NODES); // Prealloc the worst case # of records - to prevent heap fragmentation @@ -48,7 +46,7 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p, bool withUpdate) r.sender = p->from; r.rxTimeMsec = now; recentPackets.push_back(r); - DEBUG_MSG("Adding packet record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + printPacket("Adding packet record", p); } return false; diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index ec071fa5f..59750d468 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -2,6 +2,7 @@ #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" +#include "RadioInterface.h" #include PhoneAPI::PhoneAPI() @@ -43,8 +44,7 @@ void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) switch (toRadioScratch.which_variant) { case ToRadio_packet_tag: { MeshPacket &p = toRadioScratch.variant.packet; - DEBUG_MSG("PACKET FROM PHONE: id=%d, to=%x, want_ack=%d, which1=%d, which2=%d, typ=%d, buflen=%d\n", p.id, p.to, p.want_ack, p.which_payload, - p.decoded.which_payload, p.decoded.data.typ, bufLength); + printPacket("PACKET FROM PHONE", &p); service.handleToRadio(p); break; } diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 43d9a6696..4307b0a7d 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -24,6 +24,52 @@ separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts // 1kb was too small #define RADIO_STACK_SIZE 4096 +void printPacket(const char *prefix, const MeshPacket *p) +{ + DEBUG_MSG("%s (id=0x%08x Fr0x%02x To0x%02x, WantAck%d, HopLim%d", prefix, p->id, p->from & 0xff, p->to & 0xff, p->want_ack, + p->hop_limit); + if (p->which_payload == MeshPacket_decoded_tag) { + auto &s = p->decoded; + switch (s.which_payload) { + case SubPacket_data_tag: + DEBUG_MSG(" Payload:Data"); + break; + case SubPacket_position_tag: + DEBUG_MSG(" Payload:Position"); + break; + case SubPacket_user_tag: + DEBUG_MSG(" Payload:User"); + break; + case 0: + DEBUG_MSG(" Payload:None"); + break; + default: + DEBUG_MSG(" Payload:%d", s.which_payload); + break; + } + if (s.want_response) + DEBUG_MSG(" WANTRESP"); + + if (s.source != 0) + DEBUG_MSG(" source=%08x", s.source); + + if (s.dest != 0) + DEBUG_MSG(" dest=%08x", s.dest); + + if (s.which_ack == SubPacket_success_id_tag) + DEBUG_MSG(" successId=%08x", s.ack.success_id); + else if (s.which_ack == SubPacket_fail_id_tag) + DEBUG_MSG(" failId=%08x", s.ack.fail_id); + } else { + DEBUG_MSG(" encrypted"); + } + + if (p->rx_time != 0) { + DEBUG_MSG(" rxtime=%u", p->rx_time); + } + DEBUG_MSG(")\n"); +} + RadioInterface::RadioInterface() { assert(sizeof(PacketHeader) == 4 || sizeof(PacketHeader) == 16); // make sure the compiler did what we expected diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 0617592ac..988034dbb 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -162,3 +162,6 @@ class SimRadio : public RadioInterface /// \return true if initialisation succeeded. virtual bool init() { return true; } }; + +/// Debug printing for packets +void printPacket(const char *prefix, const MeshPacket *p); \ No newline at end of file diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 5239f8f60..8fb1ce781 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -114,8 +114,8 @@ bool RadioLibInterface::canSendImmediately() /// bluetooth comms code. If the txmit queue is empty it might return an error ErrorCode RadioLibInterface::send(MeshPacket *p) { - DEBUG_MSG("enqueuing for send on mesh fr=0x%x,to=0x%x,id=%d (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, txGood, - rxGood, rxBad); + printPacket("enqueuing for send", p); + DEBUG_MSG("txGood=%d,rxGood=%d,rxBad=%d\n", txGood, rxGood, rxBad); ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN; if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks @@ -237,7 +237,7 @@ void RadioLibInterface::completeSending() { if (sendingPacket) { txGood++; - DEBUG_MSG("Completed sending to=0x%x, id=%u\n", sendingPacket->to, sendingPacket->id); + printPacket("Completed sending", sendingPacket); // We are done sending that packet, release it packetPool.release(sendingPacket); @@ -291,7 +291,7 @@ void RadioLibInterface::handleReceiveInterrupt() memcpy(mp->encrypted.bytes, payload, payloadLen); mp->encrypted.size = payloadLen; - DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id); + printPacket("Lora RX", mp); deliverToReceiver(mp); } @@ -301,7 +301,7 @@ void RadioLibInterface::handleReceiveInterrupt() /** start an immediate transmit */ void RadioLibInterface::startSend(MeshPacket *txp) { - DEBUG_MSG("Starting low level send from=0x%x, to=0x%x, id=%u, want_ack=%d\n", txp->from, txp->to, txp->id, txp->want_ack); + printPacket("Starting low level send", txp); 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 3499c51b6..acec6b7f9 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -27,7 +27,7 @@ ErrorCode ReliableRouter::send(MeshPacket *p) bool ReliableRouter::shouldFilterReceived(const MeshPacket *p) { if (p->to == NODENUM_BROADCAST && p->from == getNodeNum()) { - DEBUG_MSG("Received someone rebroadcasting for us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + printPacket("Rx someone rebroadcasting for us", p); // We are seeing someone rebroadcast one of our broadcast attempts. // If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index bbf03944f..444ccc7ad 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -150,8 +150,8 @@ ErrorCode Router::send(MeshPacket *p) */ void Router::sniffReceived(const MeshPacket *p) { - DEBUG_MSG("FIXME-update-db Sniffing packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - // FIXME, update nodedb + DEBUG_MSG("FIXME-update-db Sniffing packet\n"); + // FIXME, update nodedb here for any packet that passes through us } bool Router::perhapsDecode(MeshPacket *p) @@ -202,7 +202,7 @@ void Router::handleReceived(MeshPacket *p) sniffReceived(p); 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); + printPacket("Delivering rx packet", p); notifyPacketReceived.notifyObservers(p); } } From fda98bbf58c0d557071d5f597dad6043698d90c3 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 14 Jun 2020 15:52:06 -0700 Subject: [PATCH 125/131] oops BLE auth should not change --- src/esp32/BluetoothUtil.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/esp32/BluetoothUtil.cpp b/src/esp32/BluetoothUtil.cpp index 3ad0e80d9..8d6b5d7d3 100644 --- a/src/esp32/BluetoothUtil.cpp +++ b/src/esp32/BluetoothUtil.cpp @@ -305,8 +305,8 @@ BLEServer *initBLE(StartBluetoothPinScreenCallback startBtPinScreen, StopBluetoo pSecurity->setCapability(ESP_IO_CAP_OUT); // FIXME - really should be ESP_LE_AUTH_REQ_SC_BOND but it seems there is a bug right now causing that bonding info to be lost - // occasionally - pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND); + // occasionally? + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND); pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); From d8db4449bebd7484f254c65498d57602ab657953 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 15 Jun 2020 07:04:03 -0700 Subject: [PATCH 126/131] 0.7.6 --- bin/version.sh | 2 +- docs/software/TODO.md | 10 +++++++++- src/esp32/main-esp32.cpp | 3 +++ src/sleep.cpp | 6 +++--- src/sleep.h | 4 +++- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/bin/version.sh b/bin/version.sh index b918e13a1..1ff41ed0f 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=0.7.5 \ No newline at end of file +export VERSION=0.7.6 \ No newline at end of file diff --git a/docs/software/TODO.md b/docs/software/TODO.md index e26948017..e01d525b4 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -1,11 +1,19 @@ # High priority - why is the net so chatty now? -- CONFIG_CLASSIC_BT_ENABLED=n +- modem sleep should work if we lower serial rate to 115kb? - device wakes, turns BLE on and phone doesn't notice (while phone was sitting in auto-connect) - E22 bringup - encryption review findings writeup + - turn on modem-sleep mode - https://github.com/espressif/arduino-esp32/issues/1142#issuecomment-512428852 +last EDF release in arduino is: https://github.com/espressif/arduino-esp32/commit/1977370e6fc069e93ffd8818798fbfda27ae7d99 +IDF release/v3.3 46b12a560 +IDF release/v3.3 367c3c09c +https://docs.espressif.com/projects/esp-idf/en/release-v3.3/get-started/linux-setup.html +kevinh@kevin-server:~/development/meshtastic/esp32-arduino-lib-builder$ python /home/kevinh/development/meshtastic/esp32-arduino-lib-builder/esp-idf/components/esptool_py/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dout --flash_freq 40m --flash_size detect 0x1000 /home/kevinh/development/meshtastic/esp32-arduino-lib-builder/build/bootloader/bootloader.bin +cp -a out/tools/sdk/* components/arduino/tools/sdk +cp -ar components/arduino/* ~/.platformio/packages/framework-arduinoespressif32@src-fba9d33740f719f712e9f8b07da6ea13/ # Medium priority diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index cf02ebebf..45e2a1234 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -4,6 +4,7 @@ #include "configuration.h" #include "main.h" #include "power.h" +#include "sleep.h" #include "target_specific.h" bool bluetoothOn; @@ -177,6 +178,8 @@ void esp32Setup() DEBUG_MSG("Total PSRAM: %d\n", ESP.getPsramSize()); DEBUG_MSG("Free PSRAM: %d\n", ESP.getFreePsram()); + // enableModemSleep(); + #ifdef AXP192_SLAVE_ADDRESS axp192Init(); #endif diff --git a/src/sleep.cpp b/src/sleep.cpp index 8cb0f6798..2dfee04fb 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -291,7 +291,7 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r } #endif -#if 0 + // not legal on the stock android ESP build /** @@ -306,8 +306,8 @@ void enableModemSleep() static esp_pm_config_esp32_t config; // filled with zeros because bss config.max_freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ; - config.min_freq_mhz = 10; // 10Mhz is minimum recommended + config.min_freq_mhz = 20; // 10Mhz is minimum recommended config.light_sleep_enable = false; DEBUG_MSG("Sleep request result %x\n", esp_pm_configure(&config)); } -#endif + diff --git a/src/sleep.h b/src/sleep.h index 66eafa611..800100119 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -34,4 +34,6 @@ extern Observable preflightSleep; extern Observable notifySleep; /// Called to tell observers we are now entering (deep) sleep and you should prepare. Must return 0 -extern Observable notifyDeepSleep; \ No newline at end of file +extern Observable notifyDeepSleep; + +void enableModemSleep(); \ No newline at end of file From 1c6092c43001d2c12a93698c67f7f480df7e84d8 Mon Sep 17 00:00:00 2001 From: Zombodotcom Date: Mon, 15 Jun 2020 11:38:15 -0600 Subject: [PATCH 127/131] Fixed GPS pin Definitions --- src/configuration.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/configuration.h b/src/configuration.h index 4759dfc73..6b7ba2fd5 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -168,6 +168,13 @@ along with this program. If not, see . // This string must exactly match the case used in release file names or the android updater won't work #define HW_VENDOR "heltec" +// the default ESP32 Pin of 15 is the Oled SCL, set to 36 and 37 and works fine. +//Tested on Neo6m module. +#undef GPS_RX_PIN +#undef GPS_TX_PIN +#define GPS_RX_PIN 36 +#define GPS_TX_PIN 37 + #ifndef USE_JTAG // gpio15 is TDO for JTAG, so no I2C on this board while doing jtag #define I2C_SDA 4 // I2C pins for this board #define I2C_SCL 15 From 362d5452d55b81dbd370b0c51633a65539e37385 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 15 Jun 2020 13:31:53 -0700 Subject: [PATCH 128/131] remove unused chip --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index c6a34795f..e7e864b86 100644 --- a/platformio.ini +++ b/platformio.ini @@ -164,7 +164,7 @@ board = ppr lib_deps = ${env.lib_deps} UC1701 - https://github.com/meshtastic/BQ25703A.git + From 82169d4115dad3a53fc2f8d9bb027968628327e0 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 15 Jun 2020 13:32:06 -0700 Subject: [PATCH 129/131] make recent changes work on non ESP hardware --- src/sleep.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/sleep.cpp b/src/sleep.cpp index 2dfee04fb..2d3ad4d31 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -129,7 +129,7 @@ static void waitEnterSleep() if (millis() - now > 30 * 1000) { // If we wait too long just report an error and go to sleep recordCriticalError(ErrSleepEnterWait); - ESP.restart(); // FIXME - for now we just restart, need to fix bug #167 + assert(0); // FIXME - for now we just restart, need to fix bug #167 break; } } @@ -289,8 +289,6 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r return cause; } -#endif - // not legal on the stock android ESP build @@ -310,4 +308,4 @@ void enableModemSleep() config.light_sleep_enable = false; DEBUG_MSG("Sleep request result %x\n", esp_pm_configure(&config)); } - +#endif From 477c62082db33181f37142bb10002f6ea3520f0b Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 15 Jun 2020 14:38:09 -0700 Subject: [PATCH 130/131] E22 SX1262 module now works! Thanks mostly to an old github comment by @beegee-tokyo the fix was easy (comment here https://github.com/jgromes/RadioLib/issues/12#issuecomment-520450429) We now set DIO3 to 2.4 volts to power the oscillator inside the E22 module (undocumented in the E22 docs) --- src/mesh/RadioLibInterface.h | 10 ++++---- src/mesh/SX1262Interface.cpp | 44 +++++++++++++++++++++++++++++++++++- src/mesh/SX1262Interface.h | 6 +++++ variants/ppr/variant.h | 1 + 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 6619337ba..5f70d027d 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -91,9 +91,6 @@ class RadioLibInterface : public RadioInterface, private PeriodicTask virtual void startReceive() = 0; private: - /** start an immediate transmit */ - void startSend(MeshPacket *txp); - /** if we have something waiting to send, start a short random timer so we can come check for collision before actually doing * the transmit * @@ -113,7 +110,12 @@ class RadioLibInterface : public RadioInterface, private PeriodicTask /// Make sure the Driver is properly configured before calling init(). /// \return true if initialisation succeeded. virtual bool init(); - + + /** start an immediate transmit + * This method is virtual so subclasses can hook as needed, subclasses should not call directly + */ + virtual void startSend(MeshPacket *txp); + /** * Convert our modemConfig enum into wf, sf, etc... * diff --git a/src/mesh/SX1262Interface.cpp b/src/mesh/SX1262Interface.cpp index 69e7f8ee7..628ec995f 100644 --- a/src/mesh/SX1262Interface.cpp +++ b/src/mesh/SX1262Interface.cpp @@ -14,7 +14,19 @@ bool SX1262Interface::init() { RadioLibInterface::init(); - float tcxoVoltage = 0; // None - we use an XTAL +#ifdef SX1262_RXEN // set not rx or tx mode + pinMode(SX1262_RXEN, OUTPUT); + pinMode(SX1262_TXEN, OUTPUT); + digitalWrite(SX1262_RXEN, LOW); + digitalWrite(SX1262_TXEN, LOW); +#endif + +#ifndef SX1262_E22 + float tcxoVoltage = 0; // None - we use an XTAL +#else + float tcxoVoltage = + 2.4; // E22 uses DIO3 to power tcxo per https://github.com/jgromes/RadioLib/issues/12#issuecomment-520695575 +#endif bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC? applyModemConfig(); @@ -23,6 +35,12 @@ bool SX1262Interface::init() int res = lora.begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO); DEBUG_MSG("LORA init result %d\n", res); +#ifdef SX1262_RXEN + // lora.begin assumes Dio2 is RF switch control, which is not true if we are manually controlling RX and TX + if (res == ERR_NONE) + res = lora.setDio2AsRfSwitch(false); +#endif + if (res == ERR_NONE) res = lora.setCRC(SX126X_LORA_CRC_ON); @@ -81,6 +99,11 @@ void SX1262Interface::setStandby() int err = lora.standby(); assert(err == ERR_NONE); +#ifdef SX1262_RXEN // we have RXEN/TXEN control - turn off RX and TX power + digitalWrite(SX1262_RXEN, LOW); + digitalWrite(SX1262_TXEN, LOW); +#endif + isReceiving = false; // If we were receiving, not any more disableInterrupt(); completeSending(); // If we were sending, not anymore @@ -94,6 +117,19 @@ void SX1262Interface::addReceiveMetadata(MeshPacket *mp) mp->rx_snr = lora.getSNR(); } +/** start an immediate transmit + * We override to turn on transmitter power as needed. + */ +void SX1262Interface::startSend(MeshPacket *txp) +{ +#ifdef SX1262_RXEN // we have RXEN/TXEN control - turn on TX power / off RX power + digitalWrite(SX1262_RXEN, LOW); + digitalWrite(SX1262_TXEN, HIGH); +#endif + + RadioLibInterface::startSend(txp); +} + // For power draw measurements, helpful to force radio to stay sleeping // #define SLEEP_ONLY @@ -102,6 +138,12 @@ void SX1262Interface::startReceive() #ifdef SLEEP_ONLY sleep(); #else + +#ifdef SX1262_RXEN // we have RXEN/TXEN control - turn on RX power / off TX power + digitalWrite(SX1262_RXEN, HIGH); + digitalWrite(SX1262_TXEN, LOW); +#endif + setStandby(); // int err = lora.startReceive(); int err = lora.startReceiveDutyCycleAuto(); // We use a 32 bit preamble so this should save some power by letting radio sit in diff --git a/src/mesh/SX1262Interface.h b/src/mesh/SX1262Interface.h index 92b301bfc..1eee86a66 100644 --- a/src/mesh/SX1262Interface.h +++ b/src/mesh/SX1262Interface.h @@ -43,6 +43,12 @@ class SX1262Interface : public RadioLibInterface * Start waiting to receive a message */ virtual void startReceive(); + + /** start an immediate transmit + * We override to turn on transmitter power as needed. + */ + virtual void startSend(MeshPacket *txp); + /** * Add SNR data to received messages */ diff --git a/variants/ppr/variant.h b/variants/ppr/variant.h index 95b2803bd..bbdda3065 100644 --- a/variants/ppr/variant.h +++ b/variants/ppr/variant.h @@ -137,6 +137,7 @@ static const uint8_t SCK = PIN_SPI_SCK; // #define SX1262_ANT_SW (32 + 10) #define SX1262_RXEN (22) #define SX1262_TXEN (24) +#define SX1262_E22 // Indicates this SX1262 is inside of an ebyte E22 module and special config should be done for that // ERC12864-10 LCD #define ERC12864_CS (32 + 4) From 9ad14ad98b01a2c398c54aed9a14d547c3d70e5b Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 15 Jun 2020 14:43:16 -0700 Subject: [PATCH 131/131] Cleanup to merge NR52 support back into mainline --- docs/software/TODO.md | 16 +++++++--------- platformio.ini | 2 +- src/main.cpp | 7 +++---- src/nrf52/main-nrf52.cpp | 6 ------ 4 files changed, 11 insertions(+), 20 deletions(-) diff --git a/docs/software/TODO.md b/docs/software/TODO.md index e01d525b4..07bfad429 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -1,19 +1,17 @@ # High priority -- why is the net so chatty now? - modem sleep should work if we lower serial rate to 115kb? - device wakes, turns BLE on and phone doesn't notice (while phone was sitting in auto-connect) -- E22 bringup - encryption review findings writeup - turn on modem-sleep mode - https://github.com/espressif/arduino-esp32/issues/1142#issuecomment-512428852 -last EDF release in arduino is: https://github.com/espressif/arduino-esp32/commit/1977370e6fc069e93ffd8818798fbfda27ae7d99 -IDF release/v3.3 46b12a560 -IDF release/v3.3 367c3c09c -https://docs.espressif.com/projects/esp-idf/en/release-v3.3/get-started/linux-setup.html -kevinh@kevin-server:~/development/meshtastic/esp32-arduino-lib-builder$ python /home/kevinh/development/meshtastic/esp32-arduino-lib-builder/esp-idf/components/esptool_py/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dout --flash_freq 40m --flash_size detect 0x1000 /home/kevinh/development/meshtastic/esp32-arduino-lib-builder/build/bootloader/bootloader.bin -cp -a out/tools/sdk/* components/arduino/tools/sdk -cp -ar components/arduino/* ~/.platformio/packages/framework-arduinoespressif32@src-fba9d33740f719f712e9f8b07da6ea13/ + last EDF release in arduino is: https://github.com/espressif/arduino-esp32/commit/1977370e6fc069e93ffd8818798fbfda27ae7d99 + IDF release/v3.3 46b12a560 + IDF release/v3.3 367c3c09c + https://docs.espressif.com/projects/esp-idf/en/release-v3.3/get-started/linux-setup.html + kevinh@kevin-server:~/development/meshtastic/esp32-arduino-lib-builder\$ python /home/kevinh/development/meshtastic/esp32-arduino-lib-builder/esp-idf/components/esptool_py/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dout --flash_freq 40m --flash_size detect 0x1000 /home/kevinh/development/meshtastic/esp32-arduino-lib-builder/build/bootloader/bootloader.bin + cp -a out/tools/sdk/_ components/arduino/tools/sdk + cp -ar components/arduino/_ ~/.platformio/packages/framework-arduinoespressif32@src-fba9d33740f719f712e9f8b07da6ea13/ # Medium priority diff --git a/platformio.ini b/platformio.ini index e7e864b86..f0f63bc31 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = ppr ; Note: the github actions CI test build can't yet build NRF52 targets +default_envs = tbeam ; Note: the github actions CI test build can't yet build NRF52 targets [common] ; common is not currently used diff --git a/src/main.cpp b/src/main.cpp index cfca10a0b..e6820e841 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -153,7 +153,9 @@ void setup() Wire.begin(); #endif // i2c still busted on new board - // scanI2Cdevice(); +#ifndef ARDUINO_NRF52840_PPR + scanI2Cdevice(); +#endif // Buttons & LED #ifdef BUTTON_PIN @@ -182,9 +184,6 @@ void setup() nrf52Setup(); #endif - extern void testLCD(); - // testLCD(); - // Initialize the screen first so we can show the logo while we start up everything else. if (ssd1306_found) screen.setup(); diff --git a/src/nrf52/main-nrf52.cpp b/src/nrf52/main-nrf52.cpp index ce3fe7e31..93798462a 100644 --- a/src/nrf52/main-nrf52.cpp +++ b/src/nrf52/main-nrf52.cpp @@ -59,12 +59,6 @@ void setBluetoothEnable(bool on) } } -#ifdef ARDUINO_NRF52840_PPR -#include "PmuBQ25703A.h" - -PmuBQ25703A pmu; -#endif - void nrf52Setup() {