Compare commits

..

63 Commits
0.6.2 ... 0.6.4

Author SHA1 Message Date
Kevin Hester
1897d4703c Merge pull request #142 from geeksville/reliable
Reliable messages now also work for broadcasts
2020-05-21 17:55:31 -07:00
geeksville
a25cead7d7 Merge remote-tracking branch 'root/master' into reliable 2020-05-21 17:51:50 -07:00
geeksville
3d919b21f6 0.6.4 2020-05-21 17:51:35 -07:00
geeksville
d2de04d5b2 Fix #59 no need for broadcasts when showing new node pane 2020-05-21 17:21:44 -07:00
Kevin Hester
a753c942b9 Merge pull request #141 from geeksville/reliable
Reliable
2020-05-21 16:46:01 -07:00
geeksville
9dd88281af reliable broadcast now works 2020-05-21 16:34:16 -07:00
geeksville
e75561016b retransmissions work again 2020-05-21 15:55:57 -07:00
geeksville
e2cbccb133 add want_ack support for broadcast packets 2020-05-21 12:47:41 -07:00
geeksville
0271df0657 add beginnings of full DSR routing 2020-05-21 12:47:08 -07:00
Kevin Hester
e05e324a7c Merge pull request #136 from geeksville/reliable
WIP for reliable unicast and BLE software update
2020-05-19 16:09:06 -07:00
geeksville
71041e8674 reliable unicast 1 hop works! 2020-05-19 15:51:07 -07:00
geeksville
c65b518432 less logspam 2020-05-19 14:54:58 -07:00
geeksville
6ba960ce47 one hop reliable ready for testing 2020-05-19 14:54:47 -07:00
geeksville
8bf4919576 wip reliable unicast (1 hop) 2020-05-19 11:56:17 -07:00
geeksville
cca4867987 want_ack flag added 2020-05-19 10:27:28 -07:00
geeksville
7aa47cf93b Merge remote-tracking branch 'root/master' into reliable 2020-05-19 10:20:34 -07:00
Kevin Hester
3089de79ee thanks @dafeman for documentation updates!
README GUI update
2020-05-19 07:31:43 -07:00
Dafeman
6b020149f3 Update GUI Install 2020-05-19 13:50:07 +12:00
Dafeman
34fae52e98 Merge pull request #2 from meshtastic/master
Update
2020-05-19 13:30:21 +12:00
geeksville
976bdad067 sniffReceived now allows router to inspect packets not destined for this node 2020-05-18 17:57:58 -07:00
geeksville
26d3ef529e Use the hop_limit field of MeshPacket to limit max delivery depth in
the mesh.
2020-05-18 17:35:23 -07:00
geeksville
53c3d9baa2 doc updates 2020-05-18 17:02:51 -07:00
Kevin Hester
2a6858fa34 Update CNAME 2020-05-18 08:10:51 -07:00
Kevin Hester
5440cbec6a Update CNAME 2020-05-18 08:08:57 -07:00
geeksville
19f5a5ef79 oops - use correct battery shutoff voltage 2020-05-17 05:12:16 -07:00
geeksville
ef831a0b4d Fix leaving display on in deep sleep.
We shutoff screen immediately, rather than waiting for our loop call()
2020-05-17 05:11:32 -07:00
geeksville
efc239533c Fix #133 - force deep sleep if battery reaches 10% 2020-05-17 05:10:51 -07:00
geeksville
ef1463a6a9 have tbeam charge at max rate (450mA) 2020-05-17 04:44:48 -07:00
geeksville
95e952b896 todo update 2020-05-16 16:09:06 -07:00
geeksville
db72faca5e Merge remote-tracking branch 'root/master' 2020-05-15 11:25:47 -07:00
geeksville
6961853ed7 ble software update fixes 2020-05-15 10:16:10 -07:00
geeksville
5ec5248fe4 complete ble ota move 2020-05-14 14:22:11 -07:00
geeksville
14fdd33972 move bluetooth OTA back into main tree for now 2020-05-14 14:20:05 -07:00
geeksville
140e29840a fix rare gurumeditation if we are unlucky and some ISR code is in serial flash 2020-05-14 12:46:29 -07:00
Kevin Hester
d1d318fad5 Merge pull request #135 from mehuman/supported-hardware
Supported hardware README updates
2020-05-13 08:22:23 -07:00
Mark Huson
dfb6c0bb20 Merge branch 'master' into supported-hardware 2020-05-12 19:15:47 -07:00
Mark Huson
7339abbab5 Update README.md 2020-05-12 19:14:12 -07:00
Mark Huson
27db0e27e8 Update supported hardware 2020-05-12 19:11:16 -07:00
geeksville
a0b43b9a95 Send "unset" for hwver and swver if they were unset 2020-05-12 17:57:51 -07:00
geeksville
b6a202d68e runs again with new protobufs 2020-05-12 13:35:22 -07:00
Dafeman
125035d546 Merge pull request #1 from meshtastic/master
Update
2020-05-12 22:49:54 +12:00
geeksville
9f05ad2927 remove random delay hack from broadcast, since we now do that for all transmits 2020-05-11 16:19:44 -07:00
geeksville
86ae69d360 refactor so I can track and ignore recent packets of any type 2020-05-11 16:14:53 -07:00
geeksville
c12fb69ca2 update protos 2020-05-10 14:17:05 -07:00
geeksville
8b911aba7f Cleanup build for NRF52 targets 2020-05-10 12:33:17 -07:00
geeksville
2fa595523f minor fixups to get nrf52 building again 2020-05-09 21:02:56 -07:00
geeksville
190a3c2d6b filename typo 2020-05-09 20:27:08 -07:00
Kevin Hester
1bf9d052fc Merge pull request #132 from geeksville/crypto
Crypto
2020-05-09 19:14:01 -07:00
Kevin Hester
e7b99b0d78 Merge branch 'master' into crypto 2020-05-09 19:12:00 -07:00
geeksville
96313ee1c4 remove stale link 2020-05-09 19:11:06 -07:00
geeksville
3e356e5866 Crypto works! 2020-05-09 19:08:04 -07:00
geeksville
1cc24de787 stub encryptor seems nicely backwards compatible with old devices and apps 2020-05-09 17:51:20 -07:00
geeksville
e6875d559c Remove MeshRadio wrapper class - we don't need it anymore. 2020-05-09 16:32:26 -07:00
geeksville
b73dd5b23b misc todo 2020-05-09 16:15:16 -07:00
geeksville
28d21ecdcc begin work on crypto 2020-05-09 16:15:01 -07:00
Kevin Hester
42298abfad Merge pull request #131 from Dafeman/master
Pad Bluetooth passkey to 6 digits
2020-05-09 10:13:29 -07:00
Dafeman
c4a1fe0f36 Pad Bluetooth passkey to 6 digits 2020-05-09 23:09:36 +12:00
Mark Huson
0396f8f968 Merge pull request #1 from meshtastic/master
Merge upstream
2020-05-08 08:50:58 -07:00
Kevin Hester
f1b840dede Merge pull request #128 from mehuman/device-update
Device update
2020-05-07 14:42:42 -07:00
Mark Huson
4da5d79e88 add device-update to zip 2020-05-06 19:45:02 -07:00
Mark Huson
fc0c9bcfe3 add update script and README changes 2020-05-06 19:43:17 -07:00
Kevin Hester
32d0368f59 Merge pull request #127 from geeksville/nema-124
0.6.3 - fix the problem of BLE message receiption being busted in 0.6.2
2020-05-05 18:49:44 -07:00
geeksville
8bfe9fa8fc 0.6.3 - fix the problem of BLE message receiption being busted in 0.6.2 2020-05-05 18:40:17 -07:00
63 changed files with 1762 additions and 721 deletions

View File

@@ -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 dont 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.

View File

@@ -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
View File

@@ -0,0 +1,8 @@
#!/bin/bash
set -e
FILENAME=$1
echo "Trying to update $FILENAME"
esptool.py --baud 921600 writeflash 0x10000 $FILENAME

View File

@@ -1,3 +1,3 @@
export VERSION=0.6.2
export VERSION=0.6.4

View 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"
}

View File

@@ -10,7 +10,7 @@
"hwids": [["0x239A", "0x4403"]],
"usb_product": "PPR",
"mcu": "nrf52840",
"variant": "pca10056-rc-clock",
"variant": "ppr",
"variants_dir": "variants",
"bsp": {
"name": "adafruit"

View File

@@ -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.

View File

@@ -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) ;-)

View File

@@ -5,21 +5,16 @@ Items to complete soon (next couple of alpha releases).
- lower wait_bluetooth_secs to 30 seconds once we have the GPS power on (but GPS in sleep mode) across light sleep. For the time
being I have it set at 2 minutes to ensure enough time for a GPS lock from scratch.
- remeasure wake time power draws now that we run CPU down at 80MHz
# AXP192 tasks
- figure out why this fixme is needed: "FIXME, disable wake due to PMU because it seems to fire all the time?"
- "AXP192 interrupt is not firing, remove this temporary polling of battery state"
- make debug info screen show real data (including battery level & charging) - close corresponding github issue
# Medium priority
Items to complete before the first beta release.
- Don't store position packets in the to phone fifo if we are disconnected. The phone will get that info for 'free' when it
fetches the fresh nodedb.
- Use the RFM95 sequencer to stay in idle mode most of the time, then automatically go to receive mode and automatically go from transmit to receive mode. See 4.2.8.2 of manual.
- Use 32 bits for message IDs
- Use fixed32 for node IDs
- Remove the "want node" node number arbitration process
- Don't store position packets in the to phone fifo if we are disconnected. The phone will get that info for 'free' when it
fetches the fresh nodedb.
- Use the RFM95 sequencer to stay in idle mode most of the time, then automatically go to receive mode and automatically go from transmit to receive mode. See 4.2.8.2 of manual.
- possibly switch to https://github.com/SlashDevin/NeoGPS for gps comms
- good source of battery/signal/gps icons https://materialdesignicons.com/
- research and implement better mesh algorithm - investigate changing routing to https://github.com/sudomesh/LoRaLayer2 ?
@@ -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
View 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)

View File

@@ -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

View File

@@ -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.
```

View File

@@ -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

Submodule proto updated: b35e7fb17e...bfae47bdc0

View File

@@ -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");

View File

@@ -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;

View File

@@ -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()
{

View File

@@ -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:
/**

View File

@@ -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"
@@ -188,35 +217,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
0 // If defined, this will be used for user button presses, if your board doesn't have a physical switch, you can wire one
// between this pin and ground
#define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio
#define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio
#define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
#define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
#elif defined(NRF52840_XXAA) // All of the NRF52 targets are configured using variant.h, so this section shouldn't need to be
// board specific
#define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio
#define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio
#define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
#define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
#endif
// FIXME, use variant.h defs for all of this!!!
#ifdef ARDUINO_NRF52840_PCA10056
// This string must exactly match the case used in release file names or the android updater won't work
#define HW_VENDOR "nrf52"
#define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth)
// We bind to the GPS using variant.h instead for this platform (Serial1)
#undef GPS_RX_PIN
#undef GPS_TX_PIN
// FIXME, not yet ready for NRF52
#define RTC_DATA_ATTR
#define LED_PIN PIN_LED1 // LED1 on nrf52840-DK
#define BUTTON_PIN PIN_BUTTON1
#define HW_VENDOR "nrf52dk"
// This board uses 0 to be mean LED on
#undef LED_INVERTED
#define LED_INVERTED 1
// Temporarily testing if we can build the RF95 driver for NRF52
// Uncomment to confirm if we can build the RF95 driver for NRF52
#if 0
#define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio
#define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio
@@ -224,6 +240,10 @@ along with this program. If not, see <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
// -----------------------------------------------------------------------------

View File

@@ -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));

View File

@@ -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();

View File

@@ -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);
}

View 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();

View File

@@ -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
}

View File

@@ -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());

View File

@@ -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
View 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
View 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
View 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
View 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);
};

View File

@@ -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");
packetPool.release(p);
} else {
if (p->to == NODENUM_BROADCAST) {
if (p->id != 0) {
MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
if (wasSeenRecently(p)) {
DEBUG_MSG("Ignoring incoming msg, because we've already seen it\n");
packetPool.release(p);
} else {
// If 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);
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);
toResend.enqueue(tosend);
setPeriod(delay); // This will work even if we were already waiting a random delay
} else {
DEBUG_MSG("Rebroadcasting received floodmsg to neighbors, fr=0x%x,to=0x%x,id=%d\n", p->from, p->to,
p->id);
// Note: we are careful to resend using the original senders node id
// We are careful not to call our hooked version of send() - because we don't want to check this again
Router::send(tosend);
}
} else {
DEBUG_MSG("Ignoring a simple (0 hop) broadcast\n");
}
} else {
DEBUG_MSG("Ignoring a simple (0 id) broadcast\n");
}
// handle the packet as normal
Router::handleReceived(p);
}
} else
// handle the packet as normal
Router::handleReceived(p);
}
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;
}

View File

@@ -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);
};

View File

@@ -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(&notifyDeepSleep);
#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;
}

View File

@@ -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();
};

View File

@@ -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,40 +238,21 @@ 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");
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
if (router.sendLocal(p) != ERRNO_OK) {
DEBUG_MSG("No radio was able to send packet, discarding...\n");
releaseToPool(p);
} else {
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
if (router.send(p) != ERRNO_OK) {
DEBUG_MSG("No radio was able to send packet, discarding...\n");
releaseToPool(p);
}
}
}
MeshPacket *MeshService::allocForSending()
{
MeshPacket *p = packetPool.allocZeroed();
p->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)
{
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
@@ -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)

View File

@@ -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);

View File

@@ -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

View File

@@ -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;
}
}
}
}

View File

@@ -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;

View 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
View 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);
};

View File

@@ -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:
/**

View File

@@ -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(&notifyDeepSleep);
// 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);
}

View File

@@ -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

View File

@@ -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,33 +283,31 @@ void RadioLibInterface::handleReceiveInterrupt()
rxBad++;
} else {
const PacketHeader *h = (PacketHeader *)radiobuf;
uint8_t ourAddr = nodeDB.getNodeNum();
rxGood++;
if (h->to != 255 && h->to != ourAddr) {
DEBUG_MSG("ignoring packet not sent to us\n");
} else {
MeshPacket *mp = packetPool.allocZeroed();
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;
addReceiveMetadata(mp);
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);
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;
DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id);
addReceiveMetadata(mp);
deliverToReceiver(mp);
}
}
mp->which_payload = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point
assert(payloadLen <= sizeof(mp->encrypted.bytes));
memcpy(mp->encrypted.bytes, payload, payloadLen);
mp->encrypted.size = payloadLen;
DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id);
deliverToReceiver(mp);
}
}
}
@@ -313,7 +315,7 @@ void RadioLibInterface::handleReceiveInterrupt()
/** start an immediate transmit */
void RadioLibInterface::startSend(MeshPacket *txp)
{
DEBUG_MSG("Starting low level send from=0x%x, id=%u!\n", txp->from, txp->id);
DEBUG_MSG("Starting low level send from=0x%x, 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);

View File

@@ -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
View 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
View 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();
};

View File

@@ -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
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);
// 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);
}

View File

@@ -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();

View File

@@ -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

View File

@@ -51,6 +51,9 @@ PB_BIND(FromRadio, FromRadio, 2)
PB_BIND(ToRadio, ToRadio, 2)
PB_BIND(ManufacturingData, ManufacturingData, AUTO)

View File

@@ -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;
Position position;
bool has_data;
Data data;
bool has_user;
User user;
pb_size_t which_payload;
union {
Position position;
Data data;
User user;
RouteDiscovery request;
RouteDiscovery reply;
};
bool want_response;
uint32_t dest;
pb_size_t which_ack;
union {
uint32_t success_id;
uint32_t fail_id;
} ack;
} SubPacket;
typedef PB_BYTES_ARRAY_T(256) MeshPacket_encrypted_t;
typedef struct _MeshPacket {
int32_t from;
int32_t to;
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" */

View File

@@ -0,0 +1,5 @@
#include "CryptoEngine.h"
// FIXME, do a NRF52 version
CryptoEngine *crypto = new CryptoEngine();

View File

@@ -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

View File

@@ -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}); }

View File

@@ -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
// -----------------------------------------------------------------------------

View File

@@ -19,31 +19,24 @@
*/
#include "variant.h"
#include "nrf.h"
#include "wiring_constants.h"
#include "wiring_digital.h"
#include "nrf.h"
const uint32_t g_ADigitalPinMap[] =
{
// P0
0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 ,
8 , 9 , 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31,
// P1
32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47
};
const uint32_t g_ADigitalPinMap[] = {
// P0
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0xff, 12, 13, 0xff, 15, 0xff, 17, 18, 0xff, 20, 0xff, 22, 0xff, 24, 0xff, 26, 0xff, 28, 29,
30, 31,
// P1
32, 0xff, 34, 0xff, 36, 0xff, 38, 0xff, 0xff, 41, 42, 43, 0xff, 45};
void initVariant()
{
// LED1 & LED2
pinMode(PIN_LED1, OUTPUT);
ledOff(PIN_LED1);
// LED1 & LED2
pinMode(PIN_LED1, OUTPUT);
ledOff(PIN_LED1);
pinMode(PIN_LED2, OUTPUT);
ledOff(PIN_LED2);;
pinMode(PIN_LED2, OUTPUT);
ledOff(PIN_LED2);
}

View File

@@ -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