mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-21 10:12:50 +00:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1897d4703c | ||
|
|
a25cead7d7 | ||
|
|
3d919b21f6 | ||
|
|
d2de04d5b2 | ||
|
|
a753c942b9 | ||
|
|
9dd88281af | ||
|
|
e75561016b | ||
|
|
e2cbccb133 | ||
|
|
0271df0657 | ||
|
|
e05e324a7c | ||
|
|
71041e8674 | ||
|
|
c65b518432 | ||
|
|
6ba960ce47 | ||
|
|
8bf4919576 | ||
|
|
cca4867987 | ||
|
|
7aa47cf93b | ||
|
|
3089de79ee | ||
|
|
6b020149f3 | ||
|
|
34fae52e98 | ||
|
|
976bdad067 | ||
|
|
26d3ef529e | ||
|
|
53c3d9baa2 | ||
|
|
2a6858fa34 | ||
|
|
5440cbec6a | ||
|
|
19f5a5ef79 | ||
|
|
ef831a0b4d | ||
|
|
efc239533c | ||
|
|
ef1463a6a9 | ||
|
|
95e952b896 | ||
|
|
db72faca5e | ||
|
|
6961853ed7 | ||
|
|
5ec5248fe4 | ||
|
|
14fdd33972 | ||
|
|
140e29840a | ||
|
|
d1d318fad5 | ||
|
|
dfb6c0bb20 | ||
|
|
7339abbab5 | ||
|
|
27db0e27e8 | ||
|
|
a0b43b9a95 | ||
|
|
b6a202d68e | ||
|
|
125035d546 | ||
|
|
9f05ad2927 | ||
|
|
86ae69d360 | ||
|
|
c12fb69ca2 | ||
|
|
8b911aba7f | ||
|
|
2fa595523f | ||
|
|
190a3c2d6b | ||
|
|
1bf9d052fc | ||
|
|
e7b99b0d78 | ||
|
|
96313ee1c4 | ||
|
|
3e356e5866 | ||
|
|
1cc24de787 | ||
|
|
e6875d559c | ||
|
|
b73dd5b23b | ||
|
|
28d21ecdcc | ||
|
|
42298abfad | ||
|
|
c4a1fe0f36 | ||
|
|
0396f8f968 | ||
|
|
f1b840dede | ||
|
|
4da5d79e88 | ||
|
|
fc0c9bcfe3 | ||
|
|
32d0368f59 | ||
|
|
8bfe9fa8fc |
28
README.md
28
README.md
@@ -20,9 +20,25 @@ This software is 100% open source and developed by a group of hobbyist experimen
|
||||
|
||||
## Supported hardware
|
||||
|
||||
We currently support three models of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4000119152086.html), [TTGO LORA32](https://www.banggood.com/LILYGO-TTGO-LORA32-868Mhz-SX1276-ESP32-Oled-Display-bluetooth-WIFI-Lora-Development-Module-Board-p-1248652.html?cur_warehouse=UK) and the [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/). Most users should buy the T-Beam and an 18650 battery (total cost less than \$35). Make sure to buy the frequency range which is legal for your country (915MHz for US/JP/AU/NZ, 470MHz for CN, 433MHz and 870MHz for EU). Getting a version that includes a screen is optional, but highly recommended.
|
||||
We currently support three models of radios.
|
||||
- TTGO T-Beam
|
||||
- [T-Beam V1.0 w/ NEO-M8N](https://www.aliexpress.com/item/33047631119.html) (Recommended)
|
||||
- [T-Beam V1.0 w/ NEO-6M](https://www.aliexpress.com/item/33050391850.html)
|
||||
- 3D printable cases
|
||||
- [T-Beam V0](https://www.thingiverse.com/thing:3773717)
|
||||
- [T-Beam V1](https://www.thingiverse.com/thing:3830711)
|
||||
|
||||
See (meshtastic.org) for 3D printable cases.
|
||||
- [TTGO LORA32](https://www.aliexpress.com/item/4000211331316.html) - No GPS
|
||||
|
||||
- [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/) - No GPS
|
||||
- [3D Printable case](https://www.thingiverse.com/thing:3125854)
|
||||
|
||||
**Make sure to get the frequency for your country**
|
||||
- US/JP/AU/NZ - 915MHz
|
||||
- CN - 470MHz
|
||||
- EU - 870MHz
|
||||
|
||||
Getting a version that includes a screen is optional, but highly recommended.
|
||||
|
||||
## Firmware Installation
|
||||
|
||||
@@ -40,7 +56,8 @@ Please post comments on our [group chat](https://meshtastic.discourse.group/) if
|
||||
6. In ESPHome Flasher, refresh the serial ports and select your board.
|
||||
7. Browse to the previously downloaded firmware and select the correct firmware based on the board type, country and frequency.
|
||||
8. Select Flash ESP.
|
||||
9. Once finished, the terminal should start displaying debug messages including the Bluetooth passphrase when you try connect from your phone (handy if you don’t have a screen).
|
||||
9. Once complete, “Done! Flashing is complete!” will be shown.
|
||||
10. Debug messages sent from the Meshtastic device can be viewed with a terminal program such as [PuTTY](https://www.putty.org/) (Windows only). Within PuTTY, click “Serial”, enter the “Serial line” com port (can be found at step 4), enter “Speed” as 921600, then click “Open”.
|
||||
|
||||
### Installing from a commandline
|
||||
|
||||
@@ -70,7 +87,10 @@ Hard resetting via RTS pin...
|
||||
```
|
||||
|
||||
5. cd into the directory where the release zip file was expanded.
|
||||
6. Install the correct firmware for your board with "device-install.sh firmware-_board_-_country_.bin". For instance "./device-install.sh firmware-HELTEC-US-0.0.3.bin".
|
||||
6. Install the correct firmware for your board with `device-install.sh firmware-_board_-_country_.bin`.
|
||||
- Example: `./device-install.sh firmware-HELTEC-US-0.0.3.bin`.
|
||||
7. To update run `device-update.sh firmware-_board_-_country_.bin`
|
||||
- Example: `./device-update.sh firmware-HELTEC-US-0.0.3.bin`.
|
||||
|
||||
Note: If you have previously installed meshtastic, you don't need to run this full script instead just run "esptool.py --baud 921600 write*flash 0x10000 firmware-\_board*-_country_.bin". This will be faster, also all of your current preferences will be preserved.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -64,6 +67,6 @@ Generated by bin/buildall.sh -->
|
||||
XML
|
||||
|
||||
rm -f $ARCHIVEDIR/firmware-$VERSION.zip
|
||||
zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/bins/firmware-*-$VERSION.* images/system-info.bin bin/device-install.sh
|
||||
zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/bins/firmware-*-$VERSION.* images/system-info.bin bin/device-install.sh bin/device-update.sh
|
||||
|
||||
echo BUILT ALL
|
||||
8
bin/device-update.sh
Executable file
8
bin/device-update.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
FILENAME=$1
|
||||
|
||||
echo "Trying to update $FILENAME"
|
||||
esptool.py --baud 921600 writeflash 0x10000 $FILENAME
|
||||
@@ -1,3 +1,3 @@
|
||||
|
||||
|
||||
export VERSION=0.6.2
|
||||
export VERSION=0.6.4
|
||||
46
boards/nrf52840_dk_modified.json
Normal file
46
boards/nrf52840_dk_modified.json
Normal file
@@ -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"
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
"hwids": [["0x239A", "0x4403"]],
|
||||
"usb_product": "PPR",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "pca10056-rc-clock",
|
||||
"variant": "ppr",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ This project is still pretty young but moving at a pretty good pace. Not all fea
|
||||
Most of these problems should be solved by the beta release (within three months):
|
||||
|
||||
- We don't make these devices and they haven't been tested by UL or the FCC. If you use them you are experimenting and we can't promise they won't burn your house down ;-)
|
||||
- Encryption is turned off for now
|
||||
- The encryption [implementation](software/crypto.md) has not been reviewed by an expert. (Are you an expert? Please help us)
|
||||
- A number of (straightforward) software work items have to be completed before battery life matches our measurements, currently battery life is about three days. Join us on chat if you want the spreadsheet of power measurements/calculations.
|
||||
- The Android API needs to be documented better
|
||||
- No one has written an iOS app yet. But some good souls [are talking about it](https://github.com/meshtastic/Meshtastic-esp32/issues/14) ;-)
|
||||
|
||||
@@ -5,20 +5,15 @@ Items to complete soon (next couple of alpha releases).
|
||||
- lower wait_bluetooth_secs to 30 seconds once we have the GPS power on (but GPS in sleep mode) across light sleep. For the time
|
||||
being I have it set at 2 minutes to ensure enough time for a GPS lock from scratch.
|
||||
|
||||
- remeasure wake time power draws now that we run CPU down at 80MHz
|
||||
|
||||
# AXP192 tasks
|
||||
|
||||
- figure out why this fixme is needed: "FIXME, disable wake due to PMU because it seems to fire all the time?"
|
||||
- "AXP192 interrupt is not firing, remove this temporary polling of battery state"
|
||||
- make debug info screen show real data (including battery level & charging) - close corresponding github issue
|
||||
|
||||
# Medium priority
|
||||
|
||||
Items to complete before the first beta release.
|
||||
|
||||
- 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.
|
||||
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/
|
||||
@@ -29,7 +24,6 @@ fetches the fresh nodedb.
|
||||
- rx signal measurements -3 marginal, -9 bad, 10 great, -10 means almost unusable. So scale this into % signal strength. preferably as a graph, with an X indicating loss of comms.
|
||||
- assign every "channel" a random shared 8 bit sync word (per 4.2.13.6 of datasheet) - use that word to filter packets before even checking CRC. This will ensure our CPU will only wake for packets on our "channel"
|
||||
- Note: we do not do address filtering at the chip level, because we might need to route for the mesh
|
||||
- add basic crypto - https://github.com/chegewara/esp32-mbedtls-aes-test/blob/master/main/main.c https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation - use ECB at first (though it is shit) because it doesn't require us to send 16 bytes of IV with each packet. Then OFB per example. Possibly do this crypto at the data payload level only, so that all of the packet routing metadata
|
||||
is in cleartext (so that nodes will route for other radios that are cryptoed with a key we don't know)
|
||||
- add frequency hopping, dependent on the gps time, make the switch moment far from the time anyone is going to be transmitting
|
||||
- share channel settings over Signal (or qr code) by embedding an an URL which is handled by the MeshUtil app.
|
||||
@@ -205,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
|
||||
|
||||
40
docs/software/crypto.md
Normal file
40
docs/software/crypto.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Encryption in Meshtastic
|
||||
|
||||
Cryptography is tricky, so we've tried to 'simply' apply standard crypto solutions to our implementation. However,
|
||||
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 ;-).
|
||||
- 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
|
||||
|
||||
- We do all crypto at the SubPacket (payload) level only, so that all meshtastic nodes will route for others - even those channels which are encrypted with a different key.
|
||||
- Mostly based on reading [Wikipedia](<https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR)>) and using the modes the ESP32 provides support for in hardware.
|
||||
- We use AES256-CTR as a stream cypher (with zero padding on the last BLOCK) because it is well supported with hardware acceleration.
|
||||
|
||||
Parameters for our CTR implementation:
|
||||
|
||||
- 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.
|
||||
- Each BLOCK for a packet has an incrementing COUNTER. COUNTER starts at zero for the first block of each packet.
|
||||
- The IV for each block is constructed by concatenating the NONCE as the upper 96 bits of the IV and the COUNTER as the bottom 32 bits. Note: since our packets are small counter will really never be higher than 32 (five bits).
|
||||
|
||||
```
|
||||
You can encrypt separate messages by dividing the nonce_counter buffer in two areas: the first one used for a per-message nonce, handled by yourself, and the second one updated by this function internally.
|
||||
For example, you might reserve the first 12 bytes for the per-message nonce, and the last 4 bytes for internal use. In that case, before calling this function on a new message you need to set the first 12 bytes of nonce_counter to your chosen nonce value, the last 4 to 0, and nc_off to 0 (which will cause stream_block to be ignored). That way, you can encrypt at most 2**96 messages of up to 2**32 blocks each with the same key.
|
||||
|
||||
The per-message nonce (or information sufficient to reconstruct it) needs to be communicated with the ciphertext and must be unique. The recommended way to ensure uniqueness is to use a message counter. An alternative is to generate random nonces, but this limits the number of messages that can be securely encrypted: for example, with 96-bit random nonces, you should not encrypt more than 2**32 messages with the same key.
|
||||
|
||||
Note that for both stategies, sizes are measured in blocks and that an AES block is 16 bytes.
|
||||
```
|
||||
|
||||
## 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)
|
||||
@@ -1,19 +1,87 @@
|
||||
# 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/
|
||||
|
||||
flood routing improvements
|
||||
|
||||
- DONE if we don't see anyone rebroadcast our want_ack=true broadcasts, retry as needed.
|
||||
|
||||
reliable messaging tasks (stage one for DSR):
|
||||
|
||||
- DONE generalize naive flooding
|
||||
- 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
|
||||
|
||||
- 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 send a packet
|
||||
|
||||
- do "hop by hop" routing
|
||||
- when sending, if destnodeinfo.next_hop is zero (and no message is already waiting for an arp for that node), startRouteDiscovery() for that node. Queue the message in the 'waiting for arp queue' so we can send it later when then the arp completes.
|
||||
- otherwise, use next_hop and start sending a message (with ack request) towards that node (starting with next_hop).
|
||||
|
||||
when we receive any packet
|
||||
|
||||
- sniff and update tables (especially useful to find adjacent nodes). Update user, network and position info.
|
||||
- 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
|
||||
- NOT DOING FOR NOW -if max_hops is zero and they weren't looking for us, drop (FIXME, send back error - I think not though?)
|
||||
- if we receive a discovery packet, and we don't have next_hop set in our nodedb, we use it to populate next_hop (if needed) towards the requester (after decrementing max_hops)
|
||||
- if we receive a discovery packet, and we have a next_hop in our nodedb for that destination we send a (reliable) we send a route reply towards the requester
|
||||
|
||||
when sending any reliable packet
|
||||
|
||||
- if timeout doing retries, send a routeError (nak) message back towards the original requester. all nodes eavesdrop on that packet and update their route caches.
|
||||
|
||||
when we receive a routereply packet
|
||||
|
||||
- update next_hop on the node, if the new reply needs fewer hops than the existing one (we prefer shorter paths). fixme, someday use a better heuristic
|
||||
|
||||
when we receive a routeError packet
|
||||
|
||||
- delete the route for that failed recipient, restartRouteDiscovery()
|
||||
- if we receive routeerror in response to a discovery,
|
||||
- fixme, eventually keep caches of possible other routes.
|
||||
|
||||
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 +145,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
|
||||
|
||||
@@ -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,12 @@ 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
|
||||
- Currently we use Nordic's vendor ID, which is apparently okay: https://devzone.nordicsemi.com/f/nordic-q-a/44014/using-nordic-vid-and-pid-for-nrf52840 and I just picked a PID of 0x4403
|
||||
@@ -71,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
|
||||
|
||||
@@ -101,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.
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -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} -<esp32/>
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
2
proto
2
proto
Submodule proto updated: b35e7fb17e...bfae47bdc0
@@ -113,8 +113,9 @@ static void onEnter()
|
||||
|
||||
uint32_t now = millis();
|
||||
|
||||
if (now - lastPingMs > 60 * 1000) { // if more than a minute since our last press, ask other nodes to update their state
|
||||
service.sendNetworkPing(NODENUM_BROADCAST, true);
|
||||
if (now - lastPingMs > 30 * 1000) { // if more than a minute since our last press, ask other nodes to update their state
|
||||
if (displayedNodeNum)
|
||||
service.sendNetworkPing(displayedNodeNum, true); // Refresh the currently displayed node
|
||||
lastPingMs = now;
|
||||
}
|
||||
}
|
||||
@@ -153,6 +154,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");
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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:
|
||||
/**
|
||||
|
||||
@@ -48,30 +48,38 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#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 LED_PIN PIN_LED1 // LED1 on nrf52840-DK
|
||||
#define BUTTON_PIN PIN_BUTTON1
|
||||
|
||||
// FIXME, use variant.h defs for all of this!!! (even on the ESP32 targets)
|
||||
#else
|
||||
|
||||
//
|
||||
// Standard definitions for ESP32 targets
|
||||
//
|
||||
|
||||
#define GPS_SERIAL_NUM 1
|
||||
#define GPS_BAUDRATE 9600
|
||||
|
||||
#define GPS_RX_PIN 34
|
||||
#ifdef USE_JTAG
|
||||
#define GPS_TX_PIN -1
|
||||
@@ -88,6 +96,27 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#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"
|
||||
@@ -192,31 +221,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#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
|
||||
#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 <http://www.gnu.org/licenses/>.
|
||||
#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
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
#include "BluetoothUtil.h"
|
||||
#include "BluetoothSoftwareUpdate.h"
|
||||
#include "configuration.h"
|
||||
#include <esp_gatt_defs.h>
|
||||
#include <BLE2902.h>
|
||||
#include <Arduino.h>
|
||||
#include <Update.h>
|
||||
#include <CRC32.h>
|
||||
#include "BluetoothUtil.h"
|
||||
#include "CallbackCharacteristic.h"
|
||||
#include "RadioLibInterface.h"
|
||||
#include "configuration.h"
|
||||
#include "lock.h"
|
||||
#include <Arduino.h>
|
||||
#include <BLE2902.h>
|
||||
#include <CRC32.h>
|
||||
#include <Update.h>
|
||||
#include <esp_gatt_defs.h>
|
||||
|
||||
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 @@ public:
|
||||
{
|
||||
BLEKeepAliveCallbacks::onWrite(c);
|
||||
|
||||
LockGuard g(updateLock);
|
||||
// Check if there is enough to OTA Update
|
||||
uint32_t len = getValue32(c, 0);
|
||||
updateExpectedSize = len;
|
||||
updateActualSize = 0;
|
||||
crc.reset();
|
||||
bool canBegin = Update.begin(len);
|
||||
DEBUG_MSG("Setting update size %u, result %d\n", len, canBegin);
|
||||
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));
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <BLEDevice.h>
|
||||
#include <BLEServer.h>
|
||||
#include <BLEUtils.h>
|
||||
|
||||
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();
|
||||
@@ -184,7 +184,7 @@ class MySecurity : public BLESecurityCallbacks
|
||||
|
||||
void onPassKeyNotify(uint32_t pass_key)
|
||||
{
|
||||
Serial.printf("onPassKeyNotify %u\n", pass_key);
|
||||
Serial.printf("onPassKeyNotify %06u\n", pass_key);
|
||||
startCb(pass_key);
|
||||
}
|
||||
|
||||
83
src/esp32/ESP32CryptoEngine.cpp
Normal file
83
src/esp32/ESP32CryptoEngine.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include "CryptoEngine.h"
|
||||
#include "configuration.h"
|
||||
|
||||
#include "crypto/includes.h"
|
||||
|
||||
#include "crypto/common.h"
|
||||
|
||||
// #include "esp_system.h"
|
||||
|
||||
#include "crypto/aes.h"
|
||||
#include "crypto/aes_wrap.h"
|
||||
#include "mbedtls/aes.h"
|
||||
|
||||
#define MAX_BLOCKSIZE 256
|
||||
|
||||
class ESP32CryptoEngine : public CryptoEngine
|
||||
{
|
||||
|
||||
mbedtls_aes_context aes;
|
||||
|
||||
/// How many bytes in our key
|
||||
uint8_t keySize = 0;
|
||||
|
||||
public:
|
||||
ESP32CryptoEngine() { mbedtls_aes_init(&aes); }
|
||||
|
||||
~ESP32CryptoEngine() { mbedtls_aes_free(&aes); }
|
||||
|
||||
/**
|
||||
* Set the key used for encrypt, decrypt.
|
||||
*
|
||||
* As a special case: If all bytes are zero, we assume _no encryption_ and send all data in cleartext.
|
||||
*
|
||||
* @param numBytes must be 16 (AES128), 32 (AES256) or 0 (no crypt)
|
||||
* @param bytes a _static_ buffer that will remain valid for the life of this crypto instance (i.e. this class will cache the
|
||||
* provided pointer)
|
||||
*/
|
||||
virtual void setKey(size_t numBytes, uint8_t *bytes)
|
||||
{
|
||||
keySize = numBytes;
|
||||
DEBUG_MSG("Installing AES%d key!\n", numBytes * 8);
|
||||
if (numBytes != 0) {
|
||||
auto res = mbedtls_aes_setkey_enc(&aes, bytes, numBytes * 8);
|
||||
assert(!res);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a packet
|
||||
*
|
||||
* @param bytes is updated in place
|
||||
*/
|
||||
virtual void encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes)
|
||||
{
|
||||
if (keySize != 0) {
|
||||
uint8_t stream_block[16];
|
||||
static uint8_t scratch[MAX_BLOCKSIZE];
|
||||
size_t nc_off = 0;
|
||||
|
||||
// DEBUG_MSG("ESP32 encrypt!\n");
|
||||
initNonce(fromNode, packetNum);
|
||||
assert(numBytes <= MAX_BLOCKSIZE);
|
||||
memcpy(scratch, bytes, numBytes);
|
||||
memset(scratch + numBytes, 0,
|
||||
sizeof(scratch) - numBytes); // Fill rest of buffer with zero (in case cypher looks at it)
|
||||
|
||||
auto res = mbedtls_aes_crypt_ctr(&aes, numBytes, &nc_off, nonce, stream_block, scratch, bytes);
|
||||
assert(!res);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes)
|
||||
{
|
||||
// DEBUG_MSG("ESP32 decrypt!\n");
|
||||
|
||||
// For CTR, the implementation is the same
|
||||
encrypt(fromNode, packetNum, numBytes, bytes);
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
CryptoEngine *crypto = new ESP32CryptoEngine();
|
||||
@@ -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
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
22
src/main.cpp
22
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
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -104,8 +104,6 @@ const char *getDeviceName()
|
||||
return name;
|
||||
}
|
||||
|
||||
static MeshRadio *radio = NULL;
|
||||
|
||||
static uint32_t ledBlinker()
|
||||
{
|
||||
static bool ledOn;
|
||||
@@ -169,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
|
||||
@@ -191,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()) {
|
||||
@@ -201,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);
|
||||
@@ -231,10 +233,10 @@ void setup()
|
||||
#else
|
||||
new SimRadio();
|
||||
#endif
|
||||
radio = new MeshRadio(rIf);
|
||||
router.addInterface(&radio->radioIf);
|
||||
|
||||
if (radio && !radio->init())
|
||||
router.addInterface(rIf);
|
||||
|
||||
if (!rIf->init())
|
||||
recordCriticalError(ErrNoRadio);
|
||||
|
||||
// This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values
|
||||
|
||||
32
src/mesh/CryptoEngine.cpp
Normal file
32
src/mesh/CryptoEngine.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "CryptoEngine.h"
|
||||
#include "configuration.h"
|
||||
|
||||
void CryptoEngine::setKey(size_t numBytes, uint8_t *bytes)
|
||||
{
|
||||
DEBUG_MSG("WARNING: Using stub crypto - all crypto is sent in plaintext!\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a packet
|
||||
*
|
||||
* @param bytes is updated in place
|
||||
*/
|
||||
void CryptoEngine::encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes)
|
||||
{
|
||||
DEBUG_MSG("WARNING: noop encryption!\n");
|
||||
}
|
||||
|
||||
void CryptoEngine::decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes)
|
||||
{
|
||||
DEBUG_MSG("WARNING: noop decryption!\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Init our 128 bit nonce for a new packet
|
||||
*/
|
||||
void CryptoEngine::initNonce(uint32_t fromNode, uint64_t packetNum)
|
||||
{
|
||||
memset(nonce, 0, sizeof(nonce));
|
||||
*((uint64_t *)&nonce[0]) = packetNum;
|
||||
*((uint32_t *)&nonce[8]) = fromNode;
|
||||
}
|
||||
50
src/mesh/CryptoEngine.h
Normal file
50
src/mesh/CryptoEngine.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
/**
|
||||
* see docs/software/crypto.md for details.
|
||||
*
|
||||
*/
|
||||
|
||||
class CryptoEngine
|
||||
{
|
||||
protected:
|
||||
/** Our per packet nonce */
|
||||
uint8_t nonce[16];
|
||||
|
||||
public:
|
||||
virtual ~CryptoEngine() {}
|
||||
|
||||
/**
|
||||
* Set the key used for encrypt, decrypt.
|
||||
*
|
||||
* As a special case: If all bytes are zero, we assume _no encryption_ and send all data in cleartext.
|
||||
*
|
||||
* @param numBytes must be 16 (AES128), 32 (AES256) or 0 (no crypt)
|
||||
* @param bytes a _static_ buffer that will remain valid for the life of this crypto instance (i.e. this class will cache the
|
||||
* provided pointer)
|
||||
*/
|
||||
virtual void setKey(size_t numBytes, uint8_t *bytes);
|
||||
|
||||
/**
|
||||
* Encrypt a packet
|
||||
*
|
||||
* @param bytes is updated in place
|
||||
*/
|
||||
virtual void encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes);
|
||||
virtual void decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Init our 128 bit nonce for a new packet
|
||||
*
|
||||
* The NONCE is constructed by concatenating (from MSB to LSB):
|
||||
* a 64 bit packet number (stored in little endian order)
|
||||
* a 32 bit sending node number (stored in little endian order)
|
||||
* a 32 bit block counter (starts at zero)
|
||||
*/
|
||||
void initNonce(uint32_t fromNode, uint64_t packetNum);
|
||||
};
|
||||
|
||||
extern CryptoEngine *crypto;
|
||||
80
src/mesh/DSRRouter.cpp
Normal file
80
src/mesh/DSRRouter.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include "DSRRouter.h"
|
||||
#include "configuration.h"
|
||||
|
||||
/* when we receive any packet
|
||||
|
||||
- sniff and update tables (especially useful to find adjacent nodes). Update user, network and position info.
|
||||
- if we need to route() that packet, resend it to the next_hop based on our nodedb.
|
||||
- if it is broadcast or destined for our node, deliver locally
|
||||
- handle routereply/routeerror/routediscovery messages as described below
|
||||
- then free it
|
||||
|
||||
routeDiscovery
|
||||
|
||||
- if we've already passed through us (or is from us), then it ignore it
|
||||
- use the nodes already mentioned in the request to update our routing table
|
||||
- if they were looking for us, send back a routereply
|
||||
- if max_hops is zero and they weren't looking for us, drop (FIXME, send back error - I think not though?)
|
||||
- if we receive a discovery packet, we use it to populate next_hop (if needed) towards the requester (after decrementing max_hops)
|
||||
- if we receive a discovery packet, and we have a next_hop in our nodedb for that destination we send a (reliable) we send a route
|
||||
reply towards the requester
|
||||
|
||||
when sending any reliable packet
|
||||
|
||||
- if timeout doing retries, send a routeError (nak) message back towards the original requester. all nodes eavesdrop on that
|
||||
packet and update their route caches.
|
||||
|
||||
when we receive a routereply packet
|
||||
|
||||
- update next_hop on the node, if the new reply needs fewer hops than the existing one (we prefer shorter paths). fixme, someday
|
||||
use a better heuristic
|
||||
|
||||
when we receive a routeError packet
|
||||
|
||||
- delete the route for that failed recipient, restartRouteDiscovery()
|
||||
- if we receive routeerror in response to a discovery,
|
||||
- fixme, eventually keep caches of possible other routes.
|
||||
*/
|
||||
|
||||
void DSRRouter::sniffReceived(const MeshPacket *p)
|
||||
{
|
||||
|
||||
// FIXME, update nodedb
|
||||
|
||||
// Handle route discovery packets (will be a broadcast message)
|
||||
if (p->decoded.which_payload == SubPacket_request_tag) {
|
||||
// FIXME - always start request with the senders nodenum
|
||||
|
||||
if (weAreInRoute(p->decoded.request)) {
|
||||
DEBUG_MSG("Ignoring a route request that contains us\n");
|
||||
} else {
|
||||
updateRoutes(p->decoded.request, false); // Update our routing tables based on the route that came in so far on this request
|
||||
|
||||
if (p->decoded.dest == getNodeNum()) {
|
||||
// They were looking for us, send back a route reply (the sender address will be first in the list)
|
||||
sendRouteReply(p->decoded.request);
|
||||
} else {
|
||||
// They were looking for someone else, forward it along (as a zero hop broadcast)
|
||||
NodeNum nextHop = getNextHop(p->decoded.dest);
|
||||
if (nextHop) {
|
||||
// in our route cache, reply to the requester (the sender address will be first in the list)
|
||||
sendRouteReply(p->decoded.request, nextHop);
|
||||
} else {
|
||||
// Not in our route cache, rebroadcast on their behalf (after adding ourselves to the request route)
|
||||
resendRouteRequest(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle regular packets
|
||||
if (p->to == getNodeNum()) { // Destined for us (at least for this hop)
|
||||
|
||||
// We need to route this packet
|
||||
if (p->decoded.dest != p->to) {
|
||||
// FIXME
|
||||
}
|
||||
}
|
||||
|
||||
return ReliableRouter::sniffReceived(p);
|
||||
}
|
||||
39
src/mesh/DSRRouter.h
Normal file
39
src/mesh/DSRRouter.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "ReliableRouter.h"
|
||||
|
||||
class DSRRouter : public ReliableRouter
|
||||
{
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to
|
||||
* update routing tables etc... based on what we overhear (even for messages not destined to our node)
|
||||
*/
|
||||
virtual void sniffReceived(const MeshPacket *p);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Does our node appear in the specified route
|
||||
*/
|
||||
bool weAreInRoute(const RouteDiscovery &route);
|
||||
|
||||
/**
|
||||
* Given a DSR route, use that route to update our DB of possible routes
|
||||
**/
|
||||
void updateRoutes(const RouteDiscovery &route, bool reverse);
|
||||
|
||||
/**
|
||||
* send back a route reply (the sender address will be first in the list)
|
||||
*/
|
||||
void sendRouteReply(const RouteDiscovery &route, NodeNum toAppend = 0);
|
||||
|
||||
/**
|
||||
* Given a nodenum return the next node we should forward to if we want to reach that node.
|
||||
*
|
||||
* @return 0 if no route found
|
||||
*/
|
||||
NodeNum getNextHop(NodeNum dest);
|
||||
|
||||
/** Not in our route cache, rebroadcast on their behalf (after adding ourselves to the request route)
|
||||
*/
|
||||
void resendRouteRequest(const MeshPacket *p);
|
||||
};
|
||||
@@ -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 (so we will ignore all retransmissions we see)
|
||||
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");
|
||||
DEBUG_MSG("Ignoring incoming msg, because we've already seen it\n");
|
||||
packetPool.release(p);
|
||||
} else {
|
||||
if (p->to == NODENUM_BROADCAST) {
|
||||
// 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
|
||||
|
||||
if (needDelay) {
|
||||
uint32_t delay = getRandomDelay();
|
||||
tosend->hop_limit--; // bump down the hop count
|
||||
|
||||
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);
|
||||
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 hop) broadcast\n");
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -1,17 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "PacketHistory.h"
|
||||
#include "PeriodicTask.h"
|
||||
#include "Router.h"
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* 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<BroadcastRecord> recentBroadcasts;
|
||||
|
||||
/**
|
||||
* Packets we've received that we need to resend after a short delay
|
||||
*/
|
||||
PointerQueue<MeshPacket> 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);
|
||||
};
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
#include "error.h"
|
||||
#include <SPI.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "MeshRadio.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "configuration.h"
|
||||
#include "sleep.h"
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
|
||||
/**
|
||||
* ## LoRaWAN for North America
|
||||
|
||||
LoRaWAN defines 64, 125 kHz channels from 902.3 to 914.9 MHz increments.
|
||||
|
||||
The maximum output power for North America is +30 dBM.
|
||||
|
||||
The band is from 902 to 928 MHz. It mentions channel number and its respective channel frequency. All the 13 channels are
|
||||
separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency.
|
||||
*/
|
||||
|
||||
/// Sometimes while debugging it is useful to set this false, to disable rf95 accesses
|
||||
bool useHardware = true;
|
||||
|
||||
MeshRadio::MeshRadio(RadioInterface *rIf) : radioIf(*rIf) // , manager(radioIf)
|
||||
{
|
||||
myNodeInfo.num_channels = NUM_CHANNELS;
|
||||
|
||||
// Can't print strings this early - serial not setup yet
|
||||
// DEBUG_MSG("Set meshradio defaults name=%s\n", channelSettings.name);
|
||||
}
|
||||
|
||||
bool MeshRadio::init()
|
||||
{
|
||||
if (!useHardware)
|
||||
return true;
|
||||
|
||||
DEBUG_MSG("Starting meshradio init...\n");
|
||||
|
||||
configChangedObserver.observe(&service.configChanged);
|
||||
preflightSleepObserver.observe(&preflightSleep);
|
||||
notifyDeepSleepObserver.observe(¬ifyDeepSleep);
|
||||
|
||||
#ifdef RESET_GPIO
|
||||
pinMode(RESET_GPIO, OUTPUT); // Deassert reset
|
||||
digitalWrite(RESET_GPIO, HIGH);
|
||||
|
||||
// pulse reset
|
||||
digitalWrite(RESET_GPIO, LOW);
|
||||
delay(10);
|
||||
digitalWrite(RESET_GPIO, HIGH);
|
||||
delay(10);
|
||||
#endif
|
||||
|
||||
// we now expect interfaces to operate in promiscous mode
|
||||
// radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor
|
||||
// time.
|
||||
|
||||
applySettings();
|
||||
|
||||
if (!radioIf.init()) {
|
||||
DEBUG_MSG("LoRa radio init failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// No need to call this now, init is supposed to do same. reloadConfig();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** hash a string into an integer
|
||||
*
|
||||
* djb2 by Dan Bernstein.
|
||||
* http://www.cse.yorku.ca/~oz/hash.html
|
||||
*/
|
||||
unsigned long hash(char *str)
|
||||
{
|
||||
unsigned long hash = 5381;
|
||||
int c;
|
||||
|
||||
while ((c = *str++) != 0)
|
||||
hash = ((hash << 5) + hash) + (unsigned char)c; /* hash * 33 + c */
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull our channel settings etc... from protobufs to the dumb interface settings
|
||||
*/
|
||||
void MeshRadio::applySettings()
|
||||
{
|
||||
// Set up default configuration
|
||||
// No Sync Words in LORA mode.
|
||||
radioIf.modemConfig = (ModemConfigChoice)channelSettings.modem_config;
|
||||
|
||||
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
|
||||
int channel_num = hash(channelSettings.name) % NUM_CHANNELS;
|
||||
radioIf.freq = CH0 + CH_SPACING * channel_num;
|
||||
radioIf.power = channelSettings.tx_power;
|
||||
|
||||
DEBUG_MSG("Set radio: name=%s, config=%u, ch=%d, txpower=%d\n", channelSettings.name, channelSettings.modem_config,
|
||||
channel_num, channelSettings.tx_power);
|
||||
}
|
||||
|
||||
int MeshRadio::reloadConfig(void *unused)
|
||||
{
|
||||
applySettings();
|
||||
radioIf.reconfigure();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
#include "MemoryPool.h"
|
||||
#include "MeshTypes.h"
|
||||
#include "Observer.h"
|
||||
#include "PointerQueue.h"
|
||||
#include "RadioInterface.h"
|
||||
#include "configuration.h"
|
||||
#include "mesh.pb.h"
|
||||
|
||||
@@ -61,48 +59,3 @@
|
||||
#define NUM_CHANNELS NUM_CHANNELS_US
|
||||
#endif
|
||||
|
||||
/**
|
||||
* A raw low level interface to our mesh. Only understands nodenums and bytes (not protobufs or node ids)
|
||||
* FIXME - REMOVE THIS CLASS
|
||||
*/
|
||||
class MeshRadio
|
||||
{
|
||||
public:
|
||||
// Kinda ugly way of selecting different radio implementations, but soon this MeshRadio class will be going away
|
||||
// entirely. At that point we can make things pretty.
|
||||
RadioInterface &radioIf;
|
||||
|
||||
/** pool is the pool we will alloc our rx packets from
|
||||
* rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool
|
||||
*/
|
||||
MeshRadio(RadioInterface *rIf);
|
||||
|
||||
bool init();
|
||||
|
||||
private:
|
||||
CallbackObserver<MeshRadio, void *> configChangedObserver =
|
||||
CallbackObserver<MeshRadio, void *>(this, &MeshRadio::reloadConfig);
|
||||
|
||||
CallbackObserver<MeshRadio, void *> preflightSleepObserver =
|
||||
CallbackObserver<MeshRadio, void *>(this, &MeshRadio::preflightSleepCb);
|
||||
|
||||
CallbackObserver<MeshRadio, void *> notifyDeepSleepObserver =
|
||||
CallbackObserver<MeshRadio, void *>(this, &MeshRadio::notifyDeepSleepDb);
|
||||
|
||||
/// The radioConfig object just changed, call this to force the hw to change to the new settings
|
||||
int reloadConfig(void *unused = NULL);
|
||||
|
||||
/// Return 0 if sleep is okay
|
||||
int preflightSleepCb(void *unused = NULL) { return radioIf.canSleep() ? 0 : 1; }
|
||||
|
||||
int notifyDeepSleepDb(void *unused = NULL)
|
||||
{
|
||||
radioIf.sleep();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull our channel settings etc... from protobufs to the dumb interface settings
|
||||
*/
|
||||
void applySettings();
|
||||
};
|
||||
|
||||
@@ -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,11 +71,11 @@ void MeshService::init()
|
||||
|
||||
void MeshService::sendOurOwner(NodeNum dest, bool wantReplies)
|
||||
{
|
||||
MeshPacket *p = allocForSending();
|
||||
MeshPacket *p = router.allocForSending();
|
||||
p->to = dest;
|
||||
p->payload.want_response = wantReplies;
|
||||
p->payload.has_user = true;
|
||||
User &u = p->payload.user;
|
||||
p->decoded.want_response = wantReplies;
|
||||
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);
|
||||
|
||||
@@ -108,7 +89,7 @@ const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp)
|
||||
bool isCollision = mp->from == myNodeInfo.my_node_num;
|
||||
|
||||
// we win if we have a lower macaddr
|
||||
bool weWin = memcmp(&owner.macaddr, &mp->payload.user.macaddr, sizeof(owner.macaddr)) < 0;
|
||||
bool weWin = memcmp(&owner.macaddr, &mp->decoded.user.macaddr, sizeof(owner.macaddr)) < 0;
|
||||
|
||||
if (isCollision) {
|
||||
if (weWin) {
|
||||
@@ -134,7 +115,7 @@ const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp)
|
||||
|
||||
sendOurOwner(mp->from);
|
||||
|
||||
String lcd = String("Joined: ") + mp->payload.user.long_name + "\n";
|
||||
String lcd = String("Joined: ") + mp->decoded.user.long_name + "\n";
|
||||
screen.print(lcd.c_str());
|
||||
}
|
||||
|
||||
@@ -143,12 +124,12 @@ const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp)
|
||||
|
||||
void MeshService::handleIncomingPosition(const MeshPacket *mp)
|
||||
{
|
||||
if (mp->has_payload && mp->payload.has_position) {
|
||||
DEBUG_MSG("handled incoming position time=%u\n", mp->payload.position.time);
|
||||
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->payload.position.time) {
|
||||
if (mp->decoded.position.time) {
|
||||
struct timeval tv;
|
||||
uint32_t secs = mp->payload.position.time;
|
||||
uint32_t secs = mp->decoded.position.time;
|
||||
|
||||
tv.tv_sec = secs;
|
||||
tv.tv_usec = 0;
|
||||
@@ -171,7 +152,7 @@ int MeshService::handleFromRadio(const MeshPacket *mp)
|
||||
DEBUG_MSG("Ignoring incoming time, because we have a GPS\n");
|
||||
}
|
||||
|
||||
if (mp->has_payload && mp->payload.has_user) {
|
||||
if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_user_tag) {
|
||||
mp = handleFromRadioUser(mp);
|
||||
}
|
||||
|
||||
@@ -192,7 +173,7 @@ int MeshService::handleFromRadio(const MeshPacket *mp)
|
||||
MeshPacket *copied = packetPool.allocCopy(*mp);
|
||||
assert(toPhoneQueue.enqueue(copied, 0)); // FIXME, instead of failing for full queue, delete the oldest mssages
|
||||
|
||||
if (mp->payload.want_response)
|
||||
if (mp->decoded.want_response)
|
||||
sendNetworkPing(mp->from);
|
||||
} else {
|
||||
DEBUG_MSG("Not delivering vetoed User message\n");
|
||||
@@ -257,38 +238,19 @@ 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->has_payload && p->payload.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->payload.position.time);
|
||||
p->payload.position.time = 0;
|
||||
DEBUG_MSG("Stripping time %u from position send\n", p->decoded.position.time);
|
||||
p->decoded.position.time = 0;
|
||||
} else
|
||||
DEBUG_MSG("Providing time to mesh %u\n", p->payload.position.time);
|
||||
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");
|
||||
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) {
|
||||
if (router.sendLocal(p) != ERRNO_OK) {
|
||||
DEBUG_MSG("No radio was able to send packet, discarding...\n");
|
||||
releaseToPool(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MeshPacket *MeshService::allocForSending()
|
||||
{
|
||||
MeshPacket *p = packetPool.allocZeroed();
|
||||
|
||||
p->has_payload = true;
|
||||
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)
|
||||
@@ -310,12 +272,12 @@ 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->payload.has_position = true;
|
||||
p->payload.position = node->position;
|
||||
p->payload.want_response = wantReplies;
|
||||
p->payload.position.time = getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid.
|
||||
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.
|
||||
sendToMesh(p);
|
||||
}
|
||||
|
||||
@@ -324,10 +286,10 @@ 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->payload.has_position = true;
|
||||
MeshPacket *p = router.allocForSending();
|
||||
p->decoded.which_payload = SubPacket_position_tag;
|
||||
|
||||
Position &pos = p->payload.position;
|
||||
Position &pos = p->decoded.position;
|
||||
// !zero or !zero lat/long means valid
|
||||
if (gps->latitude != 0 || gps->longitude != 0) {
|
||||
if (gps->altitude != 0)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "FS.h"
|
||||
#include "SPIFFS.h"
|
||||
|
||||
#include "CryptoEngine.h"
|
||||
#include "GPS.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
@@ -47,11 +48,17 @@ User &owner = devicestate.owner;
|
||||
|
||||
static uint8_t ourMacAddr[6];
|
||||
|
||||
/**
|
||||
* The node number the user is currently looking at
|
||||
* 0 if none
|
||||
*/
|
||||
NodeNum displayedNodeNum;
|
||||
|
||||
NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_count) {}
|
||||
|
||||
void NodeDB::resetRadioConfig()
|
||||
{
|
||||
/// 16 bytes of random PSK for our _public_ default channel that all devices power up on
|
||||
/// 16 bytes of random PSK for our _public_ default channel that all devices power up on (AES128)
|
||||
static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59,
|
||||
0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf};
|
||||
|
||||
@@ -75,10 +82,14 @@ void NodeDB::resetRadioConfig()
|
||||
channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range
|
||||
|
||||
channelSettings.tx_power = 23;
|
||||
memcpy(&channelSettings.psk, &defaultpsk, sizeof(channelSettings.psk));
|
||||
memcpy(&channelSettings.psk.bytes, &defaultpsk, sizeof(channelSettings.psk));
|
||||
channelSettings.psk.size = sizeof(defaultpsk);
|
||||
strcpy(channelSettings.name, "Default");
|
||||
}
|
||||
|
||||
// Tell our crypto engine about the psk
|
||||
crypto->setKey(channelSettings.psk.size, channelSettings.psk.bytes);
|
||||
|
||||
// temp hack for quicker testing
|
||||
/*
|
||||
radioConfig.preferences.screen_on_secs = 30;
|
||||
@@ -104,10 +115,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],
|
||||
@@ -130,6 +137,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);
|
||||
@@ -274,8 +288,8 @@ size_t NodeDB::getNumOnlineNodes()
|
||||
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
|
||||
void NodeDB::updateFrom(const MeshPacket &mp)
|
||||
{
|
||||
if (mp.has_payload) {
|
||||
const SubPacket &p = mp.payload;
|
||||
if (mp.which_payload == MeshPacket_decoded_tag) {
|
||||
const SubPacket &p = mp.decoded;
|
||||
DEBUG_MSG("Update DB node 0x%x, rx_time=%u\n", mp.from, mp.rx_time);
|
||||
|
||||
int oldNumNodes = *numNodes;
|
||||
@@ -291,16 +305,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,
|
||||
@@ -313,9 +329,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,
|
||||
@@ -333,6 +350,8 @@ void NodeDB::updateFrom(const MeshPacket &mp)
|
||||
// We just changed something important about the user, store our DB
|
||||
// saveToDisk();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,4 +95,10 @@ class NodeDB
|
||||
void loadFromDisk();
|
||||
};
|
||||
|
||||
/**
|
||||
* The node number the user is currently looking at
|
||||
* 0 if none
|
||||
*/
|
||||
extern NodeNum displayedNodeNum;
|
||||
|
||||
extern NodeDB nodeDB;
|
||||
|
||||
55
src/mesh/PacketHistory.cpp
Normal file
55
src/mesh/PacketHistory.cpp
Normal file
@@ -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 packet record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
|
||||
|
||||
// Update the time on this record to now
|
||||
if (withUpdate)
|
||||
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 packet record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
68
src/mesh/PacketHistory.h
Normal file
68
src/mesh/PacketHistory.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include "Router.h"
|
||||
#include <queue>
|
||||
#include <unordered_set>
|
||||
|
||||
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<NodeNum>()(p.sender)) ^ (hash<PacketId>()(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<PacketRecord> recentPackets;
|
||||
// priority_queue<PacketRecord, vector<PacketRecord>, PacketRecordOrderFunction> arrivalTimes;
|
||||
// unordered_set<PacketRecord, PacketRecordHashFunction> 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);
|
||||
};
|
||||
@@ -88,7 +88,7 @@ class PhoneAPI
|
||||
/**
|
||||
* Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies)
|
||||
*/
|
||||
void onNowHasData(uint32_t fromRadioNum) {}
|
||||
virtual void onNowHasData(uint32_t fromRadioNum) {}
|
||||
|
||||
private:
|
||||
/**
|
||||
|
||||
@@ -1,28 +1,91 @@
|
||||
|
||||
#include "RadioInterface.h"
|
||||
#include "MeshRadio.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "assert.h"
|
||||
#include "configuration.h"
|
||||
#include "sleep.h"
|
||||
#include <assert.h>
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
|
||||
/**
|
||||
* ## LoRaWAN for North America
|
||||
|
||||
LoRaWAN defines 64, 125 kHz channels from 902.3 to 914.9 MHz increments.
|
||||
|
||||
The maximum output power for North America is +30 dBM.
|
||||
|
||||
The band is from 902 to 928 MHz. It mentions channel number and its respective channel frequency. All the 13 channels are
|
||||
separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency.
|
||||
*/
|
||||
|
||||
// 1kb was too small
|
||||
#define RADIO_STACK_SIZE 4096
|
||||
|
||||
RadioInterface::RadioInterface() : txQueue(MAX_TX_QUEUE)
|
||||
{
|
||||
assert(sizeof(PacketHeader) == 4); // make sure the compiler did what we expected
|
||||
|
||||
myNodeInfo.num_channels = NUM_CHANNELS;
|
||||
|
||||
// Can't print strings this early - serial not setup yet
|
||||
// DEBUG_MSG("Set meshradio defaults name=%s\n", channelSettings.name);
|
||||
}
|
||||
|
||||
bool RadioInterface::init()
|
||||
{
|
||||
DEBUG_MSG("Starting meshradio init...\n");
|
||||
|
||||
configChangedObserver.observe(&service.configChanged);
|
||||
preflightSleepObserver.observe(&preflightSleep);
|
||||
notifyDeepSleepObserver.observe(¬ifyDeepSleep);
|
||||
|
||||
// we now expect interfaces to operate in promiscous mode
|
||||
// radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor
|
||||
// time.
|
||||
|
||||
// we want this thread to run at very high priority, because it is effectively running as a user space ISR
|
||||
start("radio", RADIO_STACK_SIZE, configMAX_PRIORITIES - 1); // Start our worker thread
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** hash a string into an integer
|
||||
*
|
||||
* djb2 by Dan Bernstein.
|
||||
* http://www.cse.yorku.ca/~oz/hash.html
|
||||
*/
|
||||
unsigned long hash(char *str)
|
||||
{
|
||||
unsigned long hash = 5381;
|
||||
int c;
|
||||
|
||||
while ((c = *str++) != 0)
|
||||
hash = ((hash << 5) + hash) + (unsigned char)c; /* hash * 33 + c */
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull our channel settings etc... from protobufs to the dumb interface settings
|
||||
*/
|
||||
void RadioInterface::applyModemConfig()
|
||||
{
|
||||
// Set up default configuration
|
||||
// No Sync Words in LORA mode.
|
||||
modemConfig = (ModemConfigChoice)channelSettings.modem_config;
|
||||
|
||||
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
|
||||
int channel_num = hash(channelSettings.name) % NUM_CHANNELS;
|
||||
freq = CH0 + CH_SPACING * channel_num;
|
||||
power = channelSettings.tx_power;
|
||||
|
||||
DEBUG_MSG("Set radio: name=%s, config=%u, ch=%d, power=%d\n", channelSettings.name, channelSettings.modem_config, channel_num,
|
||||
channelSettings.tx_power);
|
||||
}
|
||||
|
||||
ErrorCode SimRadio::send(MeshPacket *p)
|
||||
{
|
||||
DEBUG_MSG("SimRadio.send\n");
|
||||
@@ -44,7 +107,7 @@ size_t RadioInterface::beginSending(MeshPacket *p)
|
||||
assert(!sendingPacket);
|
||||
|
||||
// DEBUG_MSG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", rf95.txGood(), rf95.rxGood(), rf95.rxBad());
|
||||
assert(p->has_payload);
|
||||
assert(p->which_payload == MeshPacket_encrypted_tag); // It should have already been encoded by now
|
||||
|
||||
lastTxStart = millis();
|
||||
|
||||
@@ -52,17 +115,15 @@ 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);
|
||||
|
||||
size_t numbytes = pb_encode_to_bytes(radiobuf + sizeof(PacketHeader), sizeof(radiobuf), SubPacket_fields, &p->payload) +
|
||||
sizeof(PacketHeader);
|
||||
|
||||
assert(numbytes <= MAX_RHPACKETLEN);
|
||||
memcpy(radiobuf + sizeof(PacketHeader), p->encrypted.bytes, p->encrypted.size);
|
||||
|
||||
sendingPacket = p;
|
||||
return numbytes;
|
||||
return p->encrypted.size + sizeof(PacketHeader);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "MemoryPool.h"
|
||||
#include "MeshTypes.h"
|
||||
#include "Observer.h"
|
||||
#include "PointerQueue.h"
|
||||
#include "WorkerThread.h"
|
||||
#include "mesh.pb.h"
|
||||
@@ -10,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 {
|
||||
@@ -35,6 +46,15 @@ class RadioInterface : protected NotifiedWorkerThread
|
||||
friend class MeshRadio; // for debugging we let that class touch pool
|
||||
PointerQueue<MeshPacket> *rxDest = NULL;
|
||||
|
||||
CallbackObserver<RadioInterface, void *> configChangedObserver =
|
||||
CallbackObserver<RadioInterface, void *>(this, &RadioInterface::reloadConfig);
|
||||
|
||||
CallbackObserver<RadioInterface, void *> preflightSleepObserver =
|
||||
CallbackObserver<RadioInterface, void *>(this, &RadioInterface::preflightSleepCb);
|
||||
|
||||
CallbackObserver<RadioInterface, void *> notifyDeepSleepObserver =
|
||||
CallbackObserver<RadioInterface, void *>(this, &RadioInterface::notifyDeepSleepDb);
|
||||
|
||||
protected:
|
||||
MeshPacket *sendingPacket = NULL; // The packet we are currently sending
|
||||
PointerQueue<MeshPacket> txQueue;
|
||||
@@ -104,6 +124,29 @@ class RadioInterface : protected NotifiedWorkerThread
|
||||
size_t beginSending(MeshPacket *p);
|
||||
|
||||
virtual void loop() {} // Idle processing
|
||||
|
||||
/**
|
||||
* Convert our modemConfig enum into wf, sf, etc...
|
||||
*
|
||||
* These paramaters will be pull from the channelSettings global
|
||||
*/
|
||||
virtual void applyModemConfig();
|
||||
|
||||
private:
|
||||
/// Return 0 if sleep is okay
|
||||
int preflightSleepCb(void *unused = NULL) { return canSleep() ? 0 : 1; }
|
||||
|
||||
int notifyDeepSleepDb(void *unused = NULL)
|
||||
{
|
||||
sleep();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int reloadConfig(void *unused)
|
||||
{
|
||||
reconfigure();
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
class SimRadio : public RadioInterface
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "MeshTypes.h"
|
||||
#include "OSTimer.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include <NodeDB.h> // FIXME, this class shouldn't need to look into nodedb
|
||||
#include <configuration.h>
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
@@ -58,6 +57,8 @@ RadioLibInterface *RadioLibInterface::instance;
|
||||
*/
|
||||
void RadioLibInterface::applyModemConfig()
|
||||
{
|
||||
RadioInterface::applyModemConfig();
|
||||
|
||||
switch (modemConfig) {
|
||||
case Bw125Cr45Sf128: ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range
|
||||
bw = 125;
|
||||
@@ -195,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)
|
||||
@@ -279,41 +283,39 @@ 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();
|
||||
|
||||
SubPacket *p = &mp->payload;
|
||||
// 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->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);
|
||||
|
||||
addReceiveMetadata(mp);
|
||||
|
||||
if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) {
|
||||
DEBUG_MSG("Invalid protobufs in received mesh packet, discarding.\n");
|
||||
packetPool.release(mp);
|
||||
// rxBad++; not really a hw error
|
||||
} else {
|
||||
// parsing was successful, queue for our recipient
|
||||
mp->has_payload = true;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 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, to=0x%x, id=%u, want_ack=%d\n", txp->from, txp->to, txp->id, txp->want_ack);
|
||||
setStandby(); // Cancel any already in process receives
|
||||
|
||||
size_t numbytes = beginSending(txp);
|
||||
|
||||
@@ -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);
|
||||
@@ -99,8 +107,10 @@ class RadioLibInterface : public RadioInterface
|
||||
protected:
|
||||
/**
|
||||
* Convert our modemConfig enum into wf, sf, etc...
|
||||
*
|
||||
* These paramaters will be pull from the channelSettings global
|
||||
*/
|
||||
void applyModemConfig();
|
||||
virtual void applyModemConfig();
|
||||
|
||||
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
|
||||
virtual bool canSendImmediately();
|
||||
@@ -108,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
|
||||
*/
|
||||
|
||||
178
src/mesh/ReliableRouter.cpp
Normal file
178
src/mesh/ReliableRouter.cpp
Normal file
@@ -0,0 +1,178 @@
|
||||
#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) {
|
||||
// If someone asks for acks on broadcast, we need the hop limit to be at least one, so that first node that receives our
|
||||
// message will rebroadcast
|
||||
if (p->to == NODENUM_BROADCAST && p->hop_limit == 0)
|
||||
p->hop_limit = 1;
|
||||
|
||||
auto copy = packetPool.allocCopy(*p);
|
||||
startRetransmission(copy);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
NodeNum ourNode = getNodeNum();
|
||||
|
||||
if (p->from == ourNode && p->to == NODENUM_BROADCAST) {
|
||||
DEBUG_MSG("Received someone rebroadcasting for us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
|
||||
|
||||
// We are seeing someone rebroadcast one of our broadcast attempts.
|
||||
// If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for
|
||||
// the original sending process.
|
||||
if (stopRetransmission(p->from, p->id)) {
|
||||
DEBUG_MSG("Someone is retransmitting for us, generate implicit ack\n");
|
||||
sendAckNak(true, p->from, p->id);
|
||||
}
|
||||
} else if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (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)
|
||||
{
|
||||
auto p = allocForSending();
|
||||
p->hop_limit = 0; // Assume just immediate neighbors for now
|
||||
p->to = to;
|
||||
DEBUG_MSG("Sending an ack=0x%x,to=0x%x,idFrom=%d,id=%d\n", isAck, to, idFrom, p->id);
|
||||
|
||||
if (isAck) {
|
||||
p->decoded.ack.success_id = idFrom;
|
||||
p->decoded.which_ack = SubPacket_success_id_tag;
|
||||
} else {
|
||||
p->decoded.ack.fail_id = idFrom;
|
||||
p->decoded.which_ack = SubPacket_fail_id_tag;
|
||||
}
|
||||
|
||||
sendLocal(p); // we sometimes send directly to the local node
|
||||
}
|
||||
|
||||
#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
|
||||
*/
|
||||
bool ReliableRouter::stopRetransmission(NodeNum from, PacketId id)
|
||||
{
|
||||
auto key = GlobalPacketId(from, id);
|
||||
return stopRetransmission(key);
|
||||
}
|
||||
|
||||
bool ReliableRouter::stopRetransmission(GlobalPacketId key)
|
||||
{
|
||||
auto old = pending.find(key); // If we have an old record, someone messed up because id got reused
|
||||
if (old != pending.end()) {
|
||||
auto numErased = pending.erase(key);
|
||||
assert(numErased == 1);
|
||||
packetPool.release(old->second.packet);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
|
||||
*/
|
||||
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 fr=0x%x,to=0x%x,id=%d\n", p.packet->from, p.packet->to,
|
||||
p.packet->id);
|
||||
sendAckNak(false, p.packet->from, p.packet->id);
|
||||
stopRetransmission(it->first);
|
||||
} else {
|
||||
DEBUG_MSG("Sending reliable retransmission fr=0x%x,to=0x%x,id=%d, tries left=%d\n", p.packet->from, p.packet->to,
|
||||
p.packet->id, p.numRetransmissions);
|
||||
|
||||
// Note: we call the superclass version because we don't want to have our version of send() add a new
|
||||
// retransmission record
|
||||
FloodingRouter::send(packetPool.allocCopy(*p.packet));
|
||||
|
||||
// Queue again
|
||||
--p.numRetransmissions;
|
||||
p.setNextTx();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
122
src/mesh/ReliableRouter.h
Normal file
122
src/mesh/ReliableRouter.h
Normal file
@@ -0,0 +1,122 @@
|
||||
#pragma once
|
||||
|
||||
#include "FloodingRouter.h"
|
||||
#include "PeriodicTask.h"
|
||||
#include <unordered_map>
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/** True if we have started trying to find a route - for DSR usage
|
||||
* While trying to find a route we don't actually send the data packet. We just leave it here pending until
|
||||
* we have a route or we've failed to find one.
|
||||
*/
|
||||
bool wantRoute = false;
|
||||
|
||||
PendingPacket() {}
|
||||
PendingPacket(MeshPacket *p);
|
||||
|
||||
void setNextTx() { nextTxMsec = millis() + random(20 * 1000, 22 * 1000); }
|
||||
};
|
||||
|
||||
class GlobalPacketIdHashFunction
|
||||
{
|
||||
public:
|
||||
size_t operator()(const GlobalPacketId &p) const { return (hash<NodeNum>()(p.node)) ^ (hash<PacketId>()(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<GlobalPacketId, PendingPacket, GlobalPacketIdHashFunction> 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
|
||||
*
|
||||
* @return true if we found and removed a transmission with this ID
|
||||
*/
|
||||
bool stopRetransmission(NodeNum from, PacketId id);
|
||||
bool stopRetransmission(GlobalPacketId p);
|
||||
|
||||
/**
|
||||
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
|
||||
*/
|
||||
void startRetransmission(MeshPacket *p);
|
||||
|
||||
/**
|
||||
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
|
||||
*/
|
||||
void doRetransmissions();
|
||||
};
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "Router.h"
|
||||
#include "CryptoEngine.h"
|
||||
#include "GPS.h"
|
||||
#include "configuration.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include <NodeDB.h>
|
||||
|
||||
/**
|
||||
* Router todo
|
||||
@@ -41,6 +44,49 @@ 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;
|
||||
}
|
||||
|
||||
ErrorCode Router::sendLocal(MeshPacket *p)
|
||||
{
|
||||
if (p->to == nodeDB.getNodeNum()) {
|
||||
DEBUG_MSG("Enqueuing internal message for the receive queue\n");
|
||||
fromRadioQueue.enqueue(p);
|
||||
return ERRNO_OK;
|
||||
} else
|
||||
return send(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a packet on a suitable interface. This routine will
|
||||
* later free() the packet to pool. This routine is not allowed to stall.
|
||||
@@ -48,6 +94,32 @@ void Router::loop()
|
||||
*/
|
||||
ErrorCode Router::send(MeshPacket *p)
|
||||
{
|
||||
assert(p->to != nodeDB.getNodeNum()); // should have already been handled by sendLocal
|
||||
|
||||
// Never set the want_ack flag on broadcast packets sent over the air.
|
||||
if (p->to == NODENUM_BROADCAST)
|
||||
p->want_ack = false;
|
||||
|
||||
// If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it)
|
||||
|
||||
assert(p->which_payload == MeshPacket_encrypted_tag ||
|
||||
p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now
|
||||
|
||||
// First convert from protobufs to raw bytes
|
||||
if (p->which_payload == MeshPacket_decoded_tag) {
|
||||
static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union
|
||||
|
||||
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded);
|
||||
|
||||
assert(numbytes <= MAX_RHPACKETLEN);
|
||||
crypto->encrypt(p->from, p->id, numbytes, bytes);
|
||||
|
||||
// Copy back into the packet and set the variant type
|
||||
memcpy(p->encrypted.bytes, bytes, numbytes);
|
||||
p->encrypted.size = numbytes;
|
||||
p->which_payload = MeshPacket_encrypted_tag;
|
||||
}
|
||||
|
||||
if (iface) {
|
||||
// DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
|
||||
return iface->send(p);
|
||||
@@ -58,7 +130,45 @@ ErrorCode Router::send(MeshPacket *p)
|
||||
}
|
||||
}
|
||||
|
||||
#include "GPS.h"
|
||||
/**
|
||||
* 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(const MeshPacket *p)
|
||||
{
|
||||
DEBUG_MSG("FIXME-update-db Sniffing packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
|
||||
}
|
||||
|
||||
bool Router::perhapsDecode(MeshPacket *p)
|
||||
{
|
||||
if (p->which_payload == MeshPacket_decoded_tag)
|
||||
return true; // If packet was already decoded just return
|
||||
|
||||
assert(p->which_payload == MeshPacket_encrypted_tag);
|
||||
|
||||
// FIXME - someday don't send routing packets encrypted. That would allow us to route for other channels without
|
||||
// being able to decrypt their data.
|
||||
// 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!\n");
|
||||
return false;
|
||||
} else {
|
||||
// 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.
|
||||
@@ -70,7 +180,17 @@ 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
|
||||
|
||||
// Take those raw bytes and convert them back into a well structured protobuf we can understand
|
||||
if (perhapsDecode(p)) {
|
||||
// parsing was successful, queue for our recipient
|
||||
|
||||
sniffReceived(p);
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -44,8 +44,21 @@ class Router
|
||||
* do idle processing
|
||||
* Mostly looking in our incoming rxPacket queue and calling handleReceived.
|
||||
*/
|
||||
void loop();
|
||||
virtual void loop();
|
||||
|
||||
/**
|
||||
* Works like send, but if we are sending to the local node, we directly put the message in the receive queue
|
||||
*/
|
||||
ErrorCode sendLocal(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:
|
||||
/**
|
||||
* Send a packet on a suitable interface. This routine will
|
||||
* later free() the packet to pool. This routine is not allowed to stall.
|
||||
@@ -53,15 +66,32 @@ class Router
|
||||
*/
|
||||
virtual ErrorCode send(MeshPacket *p);
|
||||
|
||||
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(const 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;
|
||||
|
||||
/// Generate a unique packet id
|
||||
// FIXME, move this someplace better
|
||||
PacketId generatePacketId();
|
||||
@@ -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
|
||||
|
||||
@@ -51,6 +51,9 @@ PB_BIND(FromRadio, FromRadio, 2)
|
||||
PB_BIND(ToRadio, ToRadio, 2)
|
||||
|
||||
|
||||
PB_BIND(ManufacturingData, ManufacturingData, AUTO)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -32,14 +32,11 @@ 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;
|
||||
ChannelSettings_ModemConfig modem_config;
|
||||
pb_byte_t psk[16];
|
||||
ChannelSettings_psk_t psk;
|
||||
char name[12];
|
||||
} ChannelSettings;
|
||||
|
||||
@@ -53,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;
|
||||
@@ -68,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 {
|
||||
@@ -89,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];
|
||||
@@ -97,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;
|
||||
|
||||
@@ -113,23 +123,37 @@ typedef struct _RadioConfig {
|
||||
} RadioConfig;
|
||||
|
||||
typedef struct _SubPacket {
|
||||
bool has_position;
|
||||
pb_size_t which_payload;
|
||||
union {
|
||||
Position position;
|
||||
bool has_data;
|
||||
Data data;
|
||||
bool has_user;
|
||||
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;
|
||||
bool has_payload;
|
||||
SubPacket payload;
|
||||
uint32_t rx_time;
|
||||
uint32_t from;
|
||||
uint32_t to;
|
||||
pb_size_t which_payload;
|
||||
union {
|
||||
SubPacket decoded;
|
||||
MeshPacket_encrypted_t encrypted;
|
||||
};
|
||||
uint32_t id;
|
||||
float rx_snr;
|
||||
uint32_t rx_time;
|
||||
uint32_t hop_limit;
|
||||
bool want_ack;
|
||||
} MeshPacket;
|
||||
|
||||
typedef struct _DeviceState {
|
||||
@@ -191,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, false, SubPacket_init_default, 0, 0, 0}
|
||||
#define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {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, false, SubPacket_init_zero, 0, 0, 0}
|
||||
#define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {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
|
||||
@@ -228,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
|
||||
@@ -241,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
|
||||
@@ -255,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
|
||||
@@ -263,18 +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_payload_tag 3
|
||||
#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
|
||||
@@ -299,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
|
||||
|
||||
@@ -320,36 +359,46 @@ 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, OPTIONAL, MESSAGE, payload, 3) \
|
||||
X(a, STATIC, SINGULAR, UINT32, rx_time, 4) \
|
||||
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, 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_MSGTYPE SubPacket
|
||||
#define MeshPacket_payload_decoded_MSGTYPE SubPacket
|
||||
|
||||
#define ChannelSettings_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, INT32, tx_power, 1) \
|
||||
X(a, STATIC, SINGULAR, UENUM, modem_config, 3) \
|
||||
X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, psk, 4) \
|
||||
X(a, STATIC, SINGULAR, BYTES, psk, 4) \
|
||||
X(a, STATIC, SINGULAR, STRING, name, 5)
|
||||
#define ChannelSettings_CALLBACK NULL
|
||||
#define ChannelSettings_DEFAULT NULL
|
||||
@@ -380,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
|
||||
@@ -452,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;
|
||||
@@ -467,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
|
||||
@@ -484,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 ChannelSettings_size 44
|
||||
#define RadioConfig_size 120
|
||||
#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 18535
|
||||
#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" */
|
||||
|
||||
5
src/nrf52/NRF52CryptoEngine.cpp
Normal file
5
src/nrf52/NRF52CryptoEngine.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
#include "CryptoEngine.h"
|
||||
|
||||
// FIXME, do a NRF52 version
|
||||
CryptoEngine *crypto = new CryptoEngine();
|
||||
@@ -23,6 +23,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include <OLEDDisplay.h>
|
||||
|
||||
#include "GPS.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "configuration.h"
|
||||
#include "fonts.h"
|
||||
@@ -78,10 +79,12 @@ static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state,
|
||||
/// Draw the last text message we received
|
||||
static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
displayedNodeNum = 0; // Not currently showing a node pane
|
||||
|
||||
MeshPacket &mp = devicestate.rx_text_message;
|
||||
NodeInfo *node = nodeDB.getNode(mp.from);
|
||||
// DEBUG_MSG("drawing text message from 0x%x: %s\n", mp.from,
|
||||
// mp.payload.variant.data.payload.bytes);
|
||||
// mp.decoded.variant.data.decoded.bytes);
|
||||
|
||||
// Demo for drawStringMaxWidth:
|
||||
// with the third parameter you can define the width after which words will
|
||||
@@ -94,8 +97,8 @@ 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.payload.has_data);
|
||||
snprintf(tempBuf, sizeof(tempBuf), " %s", mp.payload.data.payload.bytes);
|
||||
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);
|
||||
}
|
||||
@@ -303,7 +306,12 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
if (n->num == nodeDB.getNodeNum()) {
|
||||
// Don't show our node, just skip to next
|
||||
nodeIndex = (nodeIndex + 1) % nodeDB.getNumNodes();
|
||||
n = nodeDB.getNodeByIndex(nodeIndex);
|
||||
}
|
||||
|
||||
// We just changed to a new node screen, ask that node for updated state
|
||||
displayedNodeNum = n->num;
|
||||
service.sendNetworkPing(displayedNodeNum, true);
|
||||
}
|
||||
|
||||
NodeInfo *node = nodeDB.getNodeByIndex(nodeIndex);
|
||||
@@ -629,6 +637,8 @@ void Screen::handleOnPress()
|
||||
|
||||
void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
displayedNodeNum = 0; // Not currently showing a node pane
|
||||
|
||||
display->setFont(ArialMT_Plain_10);
|
||||
|
||||
// The coordinates define the left starting point of the text
|
||||
|
||||
@@ -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}); }
|
||||
|
||||
@@ -35,9 +35,6 @@ Observable<void *> notifySleep, notifyDeepSleep;
|
||||
// deep sleep support
|
||||
RTC_DATA_ATTR int bootCount = 0;
|
||||
|
||||
#define xstr(s) str(s)
|
||||
#define str(s) #s
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Application
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -19,23 +19,17 @@
|
||||
*/
|
||||
|
||||
#include "variant.h"
|
||||
#include "nrf.h"
|
||||
#include "wiring_constants.h"
|
||||
#include "wiring_digital.h"
|
||||
#include "nrf.h"
|
||||
|
||||
const uint32_t g_ADigitalPinMap[] =
|
||||
{
|
||||
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,
|
||||
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, 33, 34, 35, 36, 37, 38, 39,
|
||||
40, 41, 42, 43, 44, 45, 46, 47
|
||||
};
|
||||
|
||||
32, 0xff, 34, 0xff, 36, 0xff, 38, 0xff, 0xff, 41, 42, 43, 0xff, 45};
|
||||
|
||||
void initVariant()
|
||||
{
|
||||
@@ -44,6 +38,5 @@ void initVariant()
|
||||
ledOff(PIN_LED1);
|
||||
|
||||
pinMode(PIN_LED2, OUTPUT);
|
||||
ledOff(PIN_LED2);;
|
||||
ledOff(PIN_LED2);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
#define LED_STATE_ON 0 // State when LED is litted
|
||||
// FIXME, bluefruit automatically blinks this led while connected. call AdafruitBluefruit::autoConnLed to change this.
|
||||
#define LED_BLUE LED_GREEN
|
||||
|
||||
#define LED_STATE_ON 1 // State when LED is litted
|
||||
|
||||
/*
|
||||
* Buttons
|
||||
*/
|
||||
#define PIN_BUTTON1 11
|
||||
#define PIN_BUTTON2 12
|
||||
#define PIN_BUTTON3 24
|
||||
#define PIN_BUTTON4 25
|
||||
#define PIN_BUTTON1 4 // center
|
||||
#define PIN_BUTTON2 2
|
||||
#define PIN_BUTTON3 3
|
||||
#define PIN_BUTTON4 5
|
||||
#define PIN_BUTTON5 6
|
||||
|
||||
/*
|
||||
* Analog pins
|
||||
*/
|
||||
#define PIN_A0 (3)
|
||||
#define PIN_A1 (4)
|
||||
#define PIN_A2 (28)
|
||||
#define PIN_A3 (29)
|
||||
#define PIN_A4 (30)
|
||||
#define PIN_A5 (31)
|
||||
#define PIN_A0 (0xff)
|
||||
#define PIN_A1 (0xff)
|
||||
#define PIN_A2 (0xff)
|
||||
#define PIN_A3 (0xff)
|
||||
#define PIN_A4 (0xff)
|
||||
#define PIN_A5 (0xff)
|
||||
#define PIN_A6 (0xff)
|
||||
#define PIN_A7 (0xff)
|
||||
|
||||
@@ -87,9 +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
|
||||
|
||||
Reference in New Issue
Block a user