diff --git a/bin/build-all.sh b/bin/build-all.sh index edea28cd64..55d72fa873 100755 --- a/bin/build-all.sh +++ b/bin/build-all.sh @@ -39,6 +39,9 @@ function do_build { cp $SRCELF $OUTDIR/elfs/firmware-$ENV_NAME-$COUNTRY-$VERSION.elf } +# Make sure our submodules are current +git submodule update + # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale platformio lib update diff --git a/boards/nrf52840_dk_modified.json b/boards/nrf52840_dk_modified.json new file mode 100644 index 0000000000..ce86e09f54 --- /dev/null +++ b/boards/nrf52840_dk_modified.json @@ -0,0 +1,46 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_PCA10056 -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [["0x239A", "0x4404"]], + "usb_product": "SimPPR", + "mcu": "nrf52840", + "variant": "pca10056-rc-clock", + "variants_dir": "variants", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": ["jlink"], + "svd_path": "nrf52840.svd" + }, + "frameworks": ["arduino"], + "name": "A modified NRF52840-DK devboard (Adafruit BSP)", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "require_upload_port": true, + "speed": 115200, + "protocol": "jlink", + "protocols": ["jlink", "nrfjprog", "stlink"] + }, + "url": "https://meshtastic.org/", + "vendor": "Nordic Semi" +} diff --git a/boards/ppr.json b/boards/ppr.json index 5050758f7d..5283fdc4e8 100644 --- a/boards/ppr.json +++ b/boards/ppr.json @@ -10,7 +10,7 @@ "hwids": [["0x239A", "0x4403"]], "usb_product": "PPR", "mcu": "nrf52840", - "variant": "pca10056-rc-clock", + "variant": "ppr", "variants_dir": "variants", "bsp": { "name": "adafruit" diff --git a/docs/README.md b/docs/README.md index a10ff9f6aa..47281dbebf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,7 +1,7 @@ # What is Meshtastic? Meshtastic is a project that lets you use -inexpensive (\$30 ish) GPS radios as an extensible, super long battery life mesh GPS communicator. These radios are great for hiking, skiing, paragliding - essentially any hobby where you don't have reliable internet access. Each member of your private mesh can always see the location and distance of all other members and any text messages sent to your group chat. +inexpensive (\$30 ish) GPS radios as an extensible, long battery life, secure, mesh GPS communicator. These radios are great for hiking, skiing, paragliding - essentially any hobby where you don't have reliable internet access. Each member of your private mesh can always see the location and distance of all other members and any text messages sent to your group chat. The radios automatically create a mesh to forward packets as needed, so everyone in the group can receive messages from even the furthest member. The radios will optionally work with your phone, but no phone is required. diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 23efd6ee35..93ceb20d24 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -5,21 +5,16 @@ 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 +- 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 ? @@ -204,3 +199,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/docs/software/cypto.md b/docs/software/crypto.md similarity index 90% rename from docs/software/cypto.md rename to docs/software/crypto.md index f5f7d04a6c..7f5709c485 100644 --- a/docs/software/cypto.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/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index d90d85c9ca..78f5eba86e 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -1,19 +1,80 @@ # 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): + +- 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. +- 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) +- DONE test one hop ack/nak with the python framework +- Do stress test with acks + +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 + +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 + +- 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 +- 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) - 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) 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 +138,6 @@ look into the literature for this idea specifically. FIXME, merge into the above: - good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 4f4b6c4092..ae89750ddc 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -1,23 +1,18 @@ # NRF52 TODO +## Misc work items + ## 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 "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 - 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 @@ -45,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 @@ -62,6 +56,11 @@ 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 - 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 @@ -72,11 +71,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 @@ -102,6 +104,17 @@ 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 +- 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 +- remove the MeshRadio wrapper - we don't need it anymore, just do everything in RadioInterface subclasses. ``` diff --git a/platformio.ini b/platformio.ini index 61b61d6778..3706fa20c4 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 @@ -83,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 @@ -92,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 @@ -122,11 +123,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 +135,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 +145,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/proto b/proto index 5e2df6c998..e095ea92e6 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 5e2df6c9986cd75f0af4eab1ba0d2aacf258aaab +Subproject commit e095ea92e62edc3f5dd6864c3d08d113fd8842e2 diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index ed6eaf4fff..b60204fd5b 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -153,6 +153,13 @@ void PowerFSM_setup() powerFSM.add_transition(&stateDARK, &stateON, EVENT_PRESS, NULL, "Press"); powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, screenPress, "Press"); // reenter On to restart our timers + // Handle critically low power battery by forcing deep sleep + powerFSM.add_transition(&stateBOOT, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateLS, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateNB, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateDARK, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateON, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing"); powerFSM.add_transition(&stateON, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing"); diff --git a/src/PowerFSM.h b/src/PowerFSM.h index c94ffabf9a..ecaea70ac7 100644 --- a/src/PowerFSM.h +++ b/src/PowerFSM.h @@ -13,6 +13,7 @@ #define EVENT_BLUETOOTH_PAIR 7 #define EVENT_NODEDB_UPDATED 8 // NodeDB has a big enough change that we think you should turn on the screen #define EVENT_CONTACT_FROM_PHONE 9 // the phone just talked to us over bluetooth +#define EVENT_LOW_BATTERY 10 // Battery is critically low, go to sleep extern Fsm powerFSM; diff --git a/src/WorkerThread.cpp b/src/WorkerThread.cpp index ed11039119..f84d83be2a 100644 --- a/src/WorkerThread.cpp +++ b/src/WorkerThread.cpp @@ -28,13 +28,7 @@ void NotifiedWorkerThread::notify(uint32_t v, eNotifyAction action) xTaskNotify(taskHandle, v, action); } -/** - * Notify from an ISR - */ -void NotifiedWorkerThread::notifyFromISR(BaseType_t *highPriWoken, uint32_t v, eNotifyAction action) -{ - xTaskNotifyFromISR(taskHandle, v, action, highPriWoken); -} + void NotifiedWorkerThread::block() { diff --git a/src/WorkerThread.h b/src/WorkerThread.h index f951da32cb..86ec08e133 100644 --- a/src/WorkerThread.h +++ b/src/WorkerThread.h @@ -61,8 +61,13 @@ class NotifiedWorkerThread : public WorkerThread /** * Notify from an ISR + * + * This must be inline or IRAM_ATTR on ESP32 */ - void notifyFromISR(BaseType_t *highPriWoken, uint32_t v = 0, eNotifyAction action = eNoAction); + inline void notifyFromISR(BaseType_t *highPriWoken, uint32_t v = 0, eNotifyAction action = eNoAction) + { + xTaskNotifyFromISR(taskHandle, v, action, highPriWoken); + } protected: /** diff --git a/src/configuration.h b/src/configuration.h index b02a1a00f2..04f9d26a13 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -48,30 +48,38 @@ 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 -// ----------------------------------------------------------------------------- -// OLED -// ----------------------------------------------------------------------------- +/// 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") -#define SSD1306_ADDRESS 0x3C +#ifdef NRF52840_XXAA // All of the NRF52 targets are configured using variant.h, so this section shouldn't need to be + // board specific -// 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 +// +// Standard definitions for NRF52 targets +// -// DEBUG LED +#define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth) -#define LED_INVERTED 0 // define as 1 if LED is active low (on) +// We bind to the GPS using variant.h instead for this platform (Serial1) -// ----------------------------------------------------------------------------- -// GPS -// ----------------------------------------------------------------------------- +// FIXME, not yet ready for NRF52 +#define RTC_DATA_ATTR -#define GPS_SERIAL_NUM 1 -#define GPS_BAUDRATE 9600 +#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_RX_PIN 34 #ifdef USE_JTAG #define GPS_TX_PIN -1 @@ -88,6 +96,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 +217,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 +240,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/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp b/src/esp32/BluetoothSoftwareUpdate.cpp similarity index 56% rename from lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp rename to src/esp32/BluetoothSoftwareUpdate.cpp index 62bdd499da..0f56cecaab 100644 --- a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp +++ b/src/esp32/BluetoothSoftwareUpdate.cpp @@ -1,21 +1,30 @@ -#include "BluetoothUtil.h" #include "BluetoothSoftwareUpdate.h" +#include "BluetoothUtil.h" +#include "CallbackCharacteristic.h" +#include "RadioLibInterface.h" #include "configuration.h" -#include -#include +#include "lock.h" #include -#include +#include #include -#include "CallbackCharacteristic.h" +#include +#include + +using namespace meshtastic; CRC32 crc; uint32_t rebootAtMsec = 0; // If not zero we will reboot at this time (used to reboot shortly after the update completes) +uint32_t updateExpectedSize, updateActualSize; + +Lock *updateLock; + class TotalSizeCharacteristic : public CallbackCharacteristic { -public: + public: TotalSizeCharacteristic() - : CallbackCharacteristic("e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ) + : CallbackCharacteristic("e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e", + BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ) { } @@ -23,43 +32,54 @@ class TotalSizeCharacteristic : public CallbackCharacteristic { BLEKeepAliveCallbacks::onWrite(c); + LockGuard g(updateLock); // Check if there is enough to OTA Update uint32_t len = getValue32(c, 0); + updateExpectedSize = len; + updateActualSize = 0; crc.reset(); bool canBegin = Update.begin(len); DEBUG_MSG("Setting update size %u, result %d\n", len, canBegin); - 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."); - //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,49 +87,46 @@ 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()); } result = Update.getError(); } + + if (RadioLibInterface::instance) + RadioLibInterface::instance->startReceive(); // Resume radio + assert(resultC); resultC->setValue(&result, 1); resultC->notify(); } }; - - void bluetoothRebootCheck() { if (rebootAtMsec && millis() > rebootAtMsec) @@ -122,11 +139,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 +156,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 +165,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/src/esp32/BluetoothSoftwareUpdate.h similarity index 53% rename from lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h rename to src/esp32/BluetoothSoftwareUpdate.h index 60b1f66962..60517a7f2b 100644 --- a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h +++ b/src/esp32/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/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 diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index 48af9b998b..7f9780862c 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 @@ -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,10 @@ void esp32Loop() readPowerStatus(); axp.clearIRQ(); } + + 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 diff --git a/src/gps/UBloxGPS.cpp b/src/gps/UBloxGPS.cpp index 560c52fa89..ca8f955c57 100644 --- a/src/gps/UBloxGPS.cpp +++ b/src/gps/UBloxGPS.cpp @@ -87,7 +87,7 @@ void UBloxGPS::doTask() // Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions // turn off for now // fixtype = ublox.getFixType(); - DEBUG_MSG("fix type %d\n", fixtype); + // DEBUG_MSG("fix type %d\n", fixtype); // DEBUG_MSG("sec %d\n", ublox.getSecond()); // DEBUG_MSG("lat %d\n", ublox.getLatitude()); diff --git a/src/main.cpp b/src/main.cpp index b6c03846fd..3103335b5c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,7 +33,7 @@ #include "error.h" #include "power.h" // #include "rom/rtc.h" -#include "FloodingRouter.h" +#include "ReliableRouter.h" #include "main.h" #include "screen.h" #include "sleep.h" @@ -53,7 +53,7 @@ meshtastic::PowerStatus powerStatus; bool ssd1306_found; bool axp192_found; -FloodingRouter realRouter; +ReliableRouter realRouter; Router &router = realRouter; // Users of router don't care what sort of subclass implements that API // ----------------------------------------------------------------------------- @@ -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 @@ -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,11 +201,13 @@ void setup() gps = new NEMAGPS(); gps->setup(); } +#else + gps = new NEMAGPS(); + gps->setup(); +#endif 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 da7070172a..f16405e46e 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -2,16 +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) - -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 -} +FloodingRouter::FloodingRouter() {} /** * Send a packet on a suitable interface. This routine will @@ -20,25 +11,12 @@ 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); + // Add any messages _we_ send to the seen message list + wasSeenRecently(p); // FIXME, move this to a sniffSent method 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. @@ -48,100 +26,30 @@ bool needDelay = false; */ 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 (needDelay) { - uint32_t delay = getRandomDelay(); - - DEBUG_MSG("Rebroadcasting received floodmsg to neighbors in %u msec, fr=0x%x,to=0x%x,id=%d\n", delay, - p->from, p->to, p->id); - - 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"); - } + 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); } - } 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()); + // handle the packet as normal + Router::handleReceived(p); } } - -/** - * 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 (int i = 0; i < recentBroadcasts.size();) { - BroadcastRecord &r = recentBroadcasts[i]; - - if ((now - r.rxTimeMsec) >= FLOOD_EXPIRE_TIME) { - // DEBUG_MSG("Deleting old broadcast record %d\n", i); - recentBroadcasts.erase(recentBroadcasts.begin() + i); // delete old record - } else { - if (r.id == p->id && r.sender == p->from) { - DEBUG_MSG("Found existing broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - - // Update the time on this record to now - r.rxTimeMsec = now; - return true; - } - - i++; - } - } - - // Didn't find an existing record, make one - BroadcastRecord r; - r.id = p->id; - r.sender = p->from; - r.rxTimeMsec = now; - recentBroadcasts.push_back(r); - DEBUG_MSG("Adding broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - - return false; -} \ No newline at end of file diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index 4ca759eccb..48a8f0bc76 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -1,17 +1,8 @@ #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,19 +27,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, protected 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 - */ - PointerQueue toResend; - public: /** * Constructor @@ -72,12 +53,4 @@ class FloodingRouter : public Router, public PeriodicTask * Note: this method will free the provided packet */ 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/MeshService.cpp b/src/mesh/MeshService.cpp index 992144b82c..ee2905ad4a 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -46,8 +46,6 @@ MeshService service; #include "Router.h" -#define NUM_PACKET_ID 255 // 0 is consider invalid - static uint32_t sendOwnerCb() { service.sendOurOwner(); @@ -57,23 +55,6 @@ static uint32_t sendOwnerCb() static Periodic sendOwnerPeriod(sendOwnerCb); -/// Generate a unique packet id -// FIXME, move this someplace better -PacketId generatePacketId() -{ - static uint32_t i; // Note: trying to keep this in noinit didn't help for working across reboots - static bool didInit = false; - - if (!didInit) { - didInit = true; - i = random(0, NUM_PACKET_ID + - 1); // pick a random initial sequence number at boot (to prevent repeated reboots always starting at 0) - } - - i++; - return (i % NUM_PACKET_ID) + 1; // return number between 1 and 255 -} - MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE) { // assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro @@ -90,10 +71,10 @@ 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.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 +124,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 +152,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 +238,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; @@ -265,32 +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->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()); @@ -310,9 +272,9 @@ 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.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. @@ -324,8 +286,8 @@ 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(); - p->decoded.has_position = true; + MeshPacket *p = router.allocForSending(); + p->decoded.which_payload = SubPacket_position_tag; Position &pos = p->decoded.position; // !zero or !zero lat/long means valid diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index f3328225be..f6e688e194 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -67,9 +67,6 @@ class MeshService /// The owner User record just got updated, update our node DB and broadcast the info into the mesh void reloadOwner() { sendOurOwner(); } - /// Allocate and return a meshpacket which defaults as send to broadcast from the current node. - MeshPacket *allocForSending(); - /// Called when the user wakes up our GUI, normally sends our latest location to the mesh (if we have it), otherwise at least /// sends our owner void sendNetworkPing(NodeNum dest, bool wantReplies = false); diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index 0d9783e146..f491ce508e 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -14,6 +14,18 @@ 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 7 + +/// We normally just use max 3 hops for sending reliable messages +#define HOP_RELIABLE 3 + typedef int ErrorCode; /// Alloc and free packets to our global, ISR safe pool diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 8fe82b8229..cc83f2f472 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -109,10 +109,6 @@ void NodeDB::init() // default to no GPS, until one has been found by probing myNodeInfo.has_gps = false; - strncpy(myNodeInfo.region, xstr(HW_VERSION), sizeof(myNodeInfo.region)); - strncpy(myNodeInfo.firmware_version, xstr(APP_VERSION), sizeof(myNodeInfo.firmware_version)); - strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model)); - // Init our blank owner info to reasonable defaults getMacAddr(ourMacAddr); sprintf(owner.id, "!%02x%02x%02x%02x%02x%02x", ourMacAddr[0], ourMacAddr[1], ourMacAddr[2], ourMacAddr[3], ourMacAddr[4], @@ -135,6 +131,13 @@ void NodeDB::init() // saveToDisk(); loadFromDisk(); + + // We set these _after_ loading from disk - because they come from the build and are more trusted than + // what is stored in flash + strncpy(myNodeInfo.region, optstr(HW_VERSION), sizeof(myNodeInfo.region)); + strncpy(myNodeInfo.firmware_version, optstr(APP_VERSION), sizeof(myNodeInfo.firmware_version)); + strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model)); + resetRadioConfig(); // If bogus settings got saved, then fix them DEBUG_MSG("NODENUM=0x%x, dbsize=%d\n", myNodeInfo.my_node_num, *numNodes); @@ -296,16 +299,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 +323,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 +344,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.cpp b/src/mesh/PacketHistory.cpp new file mode 100644 index 0000000000..30a448f975 --- /dev/null +++ b/src/mesh/PacketHistory.cpp @@ -0,0 +1,55 @@ +#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, bool withUpdate) +{ + 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 + if (withUpdate) + r.rxTimeMsec = now; + return true; + } + + i++; + } + } + + // Didn't find an existing record, make one + 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 new file mode 100644 index 0000000000..38edf7d779 --- /dev/null +++ b/src/mesh/PacketHistory.h @@ -0,0 +1,68 @@ +#pragma once + +#include "Router.h" +#include +#include + +using namespace std; + +/** + * 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 + + 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; + } +}; + +/** + * 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. + */ + vector recentPackets; + // priority_queue, PacketRecordOrderFunction> arrivalTimes; + // unordered_set recentPackets; + + public: + 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 withUpdate = true); +}; diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 3ad60005ca..45ecc42a84 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 | (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 6c7dbd79b0..4197637502 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -11,12 +11,22 @@ #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. */ 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 dee630e0ce..9d27107166 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 @@ -197,6 +196,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) @@ -281,28 +283,31 @@ 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("ignoring packet not sent to us\n"); - } else { - MeshPacket *mp = packetPool.allocZeroed(); - mp->from = h->from; - mp->to = h->to; - mp->id = h->id; - addReceiveMetadata(mp); + // 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(); - 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; + mp->from = h->from; + mp->to = h->to; + mp->id = h->id; + 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); - DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id); + addReceiveMetadata(mp); - deliverToReceiver(mp); - } + 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; + + DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id); + + deliverToReceiver(mp); } } } @@ -310,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/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index a090d132aa..5f9149d717 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -19,10 +19,6 @@ class RadioLibInterface : public RadioInterface volatile PendingISR pending = ISR_NONE; volatile bool timerRunning = false; - /** Our ISR code currently needs this to find our active instance - */ - static RadioLibInterface *instance; - /** * Raw ISR handler that just calls our polymorphic method */ @@ -57,6 +53,11 @@ class RadioLibInterface : public RadioInterface /// are _trying_ to receive a packet currently (note - we might just be waiting for one) bool isReceiving; + public: + /** Our ISR code currently needs this to find our active instance + */ + static RadioLibInterface *instance; + /** * Glue functions called from ISR land */ @@ -80,6 +81,13 @@ class RadioLibInterface : public RadioInterface */ virtual bool canSleep(); + /** + * Start waiting to receive a message + * + * External functions can call this method to wake the device from sleep. + */ + virtual void startReceive() = 0; + private: /** start an immediate transmit */ void startSend(MeshPacket *txp); @@ -110,11 +118,6 @@ class RadioLibInterface : public RadioInterface /** are we actively receiving a packet (only called during receiving state) */ virtual bool isActivelyReceiving() = 0; - /** - * Start waiting to receive a message - */ - virtual void startReceive() = 0; - /** * Raw ISR handler that just calls our polymorphic method */ diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp new file mode 100644 index 0000000000..eb6fc248ae --- /dev/null +++ b/src/mesh/ReliableRouter.cpp @@ -0,0 +1,154 @@ +#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, 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); +} + +/** + * 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); +} + +#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) +{ + 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) +{ + 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() +{ + 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 new file mode 100644 index 0000000000..3798d9d68b --- /dev/null +++ b/src/mesh/ReliableRouter.h @@ -0,0 +1,114 @@ +#pragma once + +#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. + */ +class ReliableRouter : public FloodingRouter +{ + private: + unordered_map pending; + + 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); + void 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) + */ + void doRetransmissions(); +}; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 3752a2fbde..a7d5707814 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 @@ -43,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. @@ -50,36 +84,83 @@ 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 + // If this packet was destined only to apps on our node, don't send it out into the network + if (p->to == nodeDB.getNodeNum()) { + DEBUG_MSG("Dropping locally processed message\n"); + packetPool.release(p); + return ERRNO_OK; + } else { + // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) + + assert(p->which_payload == MeshPacket_encrypted_tag || + p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now + + // 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; + } + } +} - // 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 +/** + * 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); +} - size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded); +bool Router::perhapsDecode(MeshPacket *p) +{ + if (p->which_payload == MeshPacket_decoded_tag) + return true; // If packet was already decoded just return - assert(numbytes <= MAX_RHPACKETLEN); - crypto->encrypt(p->from, p->id, numbytes, bytes); + assert(p->which_payload == MeshPacket_encrypted_tag); - // Copy back into the packet and set the variant type - memcpy(p->encrypted.bytes, bytes, numbytes); - p->encrypted.size = numbytes; - p->which_payload = MeshPacket_encrypted_tag; - } + // 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, + p->encrypted.size); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf + crypto->decrypt(p->from, p->id, p->encrypted.size, bytes); - 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); + // 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!\n"); + return false; } 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; + // 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. @@ -90,24 +171,16 @@ void Router::handleReceived(MeshPacket *p) // 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 - assert(p->which_payload == - MeshPacket_encrypted_tag); // I _think_ the only thing that pushes to us is raw devices that just received packets - - // Try to decrypt the packet if we can - static uint8_t bytes[MAX_RHPACKETLEN]; - memcpy(bytes, p->encrypted.bytes, - p->encrypted.size); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf - crypto->decrypt(p->from, p->id, p->encrypted.size, bytes); - // 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"); - } else { + if (perhapsDecode(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); + + 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); + } } packetPool.release(p); diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 20378371dc..d0a8e029c4 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -44,7 +44,7 @@ class Router * do idle processing * Mostly looking in our incoming rxPacket queue and calling handleReceived. */ - void loop(); + virtual void loop(); /** * Send a packet on a suitable interface. This routine will @@ -53,15 +53,40 @@ 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() * 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); + + /** + * 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 diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 00434f90b6..49ccb3d6b6 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -31,7 +31,7 @@ void StreamAPI::readStream() if (c != START2) rxPtr = 0; // failed to find framing } else if (ptr >= HEADER_LEN) { // we have at least read our 4 byte framing - uint16_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing + uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing if (ptr == HEADER_LEN) { // we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid diff --git a/src/mesh/mesh.pb.c b/src/mesh/mesh.pb.c index 0b2c5b8ce1..321576556b 100644 --- a/src/mesh/mesh.pb.c +++ b/src/mesh/mesh.pb.c @@ -51,6 +51,9 @@ PB_BIND(FromRadio, FromRadio, 2) PB_BIND(ToRadio, ToRadio, 2) +PB_BIND(ManufacturingData, ManufacturingData, AUTO) + + diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index 9442bd25bf..ce9bc4e36d 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; @@ -54,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; @@ -69,9 +72,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 { @@ -90,6 +93,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 +106,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; @@ -114,27 +123,37 @@ typedef struct _RadioConfig { } RadioConfig; typedef struct _SubPacket { - bool has_position; - Position position; - bool has_data; - Data data; - bool has_user; - User user; + pb_size_t which_payload; + union { + Position position; + Data data; + User user; + RouteDiscovery request; + RouteDiscovery reply; + }; bool want_response; + 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; 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; + uint32_t hop_limit; + bool want_ack; } MeshPacket; typedef struct _DeviceState { @@ -196,36 +215,37 @@ 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 {0, {Position_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} -#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 {""} #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 {{{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 {0, {Position_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} -#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 {""} #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 RouteDiscovery_route_tag 2 #define ChannelSettings_tx_power_tag 1 #define ChannelSettings_modem_config_tag 3 #define ChannelSettings_psk_tag 4 @@ -233,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 @@ -246,7 +270,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 @@ -260,6 +284,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 +293,27 @@ 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_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 #define MeshPacket_encrypted_tag 8 #define MeshPacket_from_tag 1 #define MeshPacket_to_tag 2 -#define MeshPacket_rx_time_tag 4 #define MeshPacket_id_tag 6 +#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 @@ -305,9 +338,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 @@ -326,29 +359,38 @@ 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, 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, 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_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, 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) \ +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 @@ -387,9 +429,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 @@ -459,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; @@ -474,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 @@ -491,23 +543,25 @@ 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 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 RouteDiscovery_size 88 +#define SubPacket_size 273 +#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 18552 +#define DeviceState_size 15021 #define DebugString_size 258 -#define FromRadio_size 428 -#define ToRadio_size 422 +#define FromRadio_size 321 +#define ToRadio_size 315 +/* ManufacturingData_size depends on runtime parameters */ #ifdef __cplusplus } /* extern "C" */ diff --git a/src/nrf52/NRF52CryptoEngine.cpp b/src/nrf52/NRF52CryptoEngine.cpp new file mode 100644 index 0000000000..ee1650ea2a --- /dev/null +++ b/src/nrf52/NRF52CryptoEngine.cpp @@ -0,0 +1,5 @@ + +#include "CryptoEngine.h" + +// FIXME, do a NRF52 version +CryptoEngine *crypto = new CryptoEngine(); \ No newline at end of file diff --git a/src/screen.cpp b/src/screen.cpp index c60907aa83..dd2f99c07b 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -94,7 +94,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state // the max length of this buffer is much longer than we can possibly print static char tempBuf[96]; - assert(mp.decoded.has_data); + assert(mp.decoded.which_payload == SubPacket_data_tag); snprintf(tempBuf, sizeof(tempBuf), " %s", mp.decoded.data.payload.bytes); display->drawStringMaxWidth(4 + x, 10 + y, 128, tempBuf); diff --git a/src/screen.h b/src/screen.h index 634cdd10c8..be5444c1e1 100644 --- a/src/screen.h +++ b/src/screen.h @@ -100,7 +100,14 @@ class Screen : public PeriodicTask void setup(); /// Turns the screen on/off. - void setOn(bool on) { enqueueCmd(CmdItem{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF}); } + void setOn(bool on) + { + if (!on) + handleSetOn( + false); // We handle off commands immediately, because they might be called because the CPU is shutting down + else + enqueueCmd(CmdItem{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF}); + } /// Handles a button press. void onPress() { enqueueCmd(CmdItem{.cmd = Cmd::ON_PRESS}); } diff --git a/src/sleep.cpp b/src/sleep.cpp index 4f5fa2fdc4..4b8db06b08 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -35,9 +35,6 @@ Observable notifySleep, notifyDeepSleep; // deep sleep support RTC_DATA_ATTR int bootCount = 0; -#define xstr(s) str(s) -#define str(s) #s - // ----------------------------------------------------------------------------- // Application // ----------------------------------------------------------------------------- diff --git a/variants/ppr/variant.cpp b/variants/ppr/variant.cpp index bd85e97133..f5f219e9b0 100644 --- a/variants/ppr/variant.cpp +++ b/variants/ppr/variant.cpp @@ -19,31 +19,24 @@ */ #include "variant.h" +#include "nrf.h" #include "wiring_constants.h" #include "wiring_digital.h" -#include "nrf.h" -const uint32_t g_ADigitalPinMap[] = -{ - // P0 - 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , - 8 , 9 , 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, 26, 27, 28, 29, 30, 31, - - // P1 - 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 42, 43, 44, 45, 46, 47 -}; +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0xff, 12, 13, 0xff, 15, 0xff, 17, 18, 0xff, 20, 0xff, 22, 0xff, 24, 0xff, 26, 0xff, 28, 29, + 30, 31, + // P1 + 32, 0xff, 34, 0xff, 36, 0xff, 38, 0xff, 0xff, 41, 42, 43, 0xff, 45}; void initVariant() { - // LED1 & LED2 - pinMode(PIN_LED1, OUTPUT); - ledOff(PIN_LED1); + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2);; + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); } - diff --git a/variants/ppr/variant.h b/variants/ppr/variant.h index 5033a4ecc4..d59e175038 100644 --- a/variants/ppr/variant.h +++ b/variants/ppr/variant.h @@ -16,15 +16,12 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef _VARIANT_PCA10056_ -#define _VARIANT_PCA10056_ +#pragma once /** Master clock frequency */ #define VARIANT_MCK (64000000ul) -// This file is the same as the standard pac10056 variant, except that @geeksville broke the xtal on his devboard so -// he has to use a RC clock. - +// This board does not have a 32khz crystal // #define USE_LFXO // Board uses 32khz crystal for LF #define USE_LFRC // Board uses RC for LF @@ -39,40 +36,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 + +// FIXME, bluefruit automatically blinks this led while connected. call AdafruitBluefruit::autoConnLed to change this. +#define LED_BLUE LED_GREEN -#define LED_STATE_ON 0 // State when LED is litted +#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 +88,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 +98,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 +125,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 } @@ -134,5 +154,3 @@ static const uint8_t SCK = PIN_SPI_SCK; /*---------------------------------------------------------------------------- * Arduino objects - C++ only *----------------------------------------------------------------------------*/ - -#endif