Compare commits

...

282 Commits
0.4.3 ... 0.7.4

Author SHA1 Message Date
Kevin Hester
e650033f2c Merge pull request #166 from geeksville/usb
Changes to support USB
2020-06-09 11:52:48 -07:00
geeksville
846fc14b4a 0.7.4 2020-06-09 10:35:13 -07:00
geeksville
a8a5e036f5 turn off serial debug output once we are using the protocol on the stream 2020-06-09 10:35:06 -07:00
geeksville
009f05b61d temp workaround for sleep bug #167 2020-06-09 06:38:09 -07:00
geeksville
d8a6f82459 Merge remote-tracking branch 'root/master' into usb 2020-06-08 16:42:45 -07:00
Kevin Hester
185fe8520a Merge pull request #164 from rradar/patch-1
Update build-instructions.md to use code tags
2020-06-08 16:42:31 -07:00
geeksville
ce9bac34d6 add a new SERIAL psm state, to ensure device doesn't sleep while
connected to the phone over USB.

In support of

https://github.com/meshtastic/Meshtastic-Android/issues/38
2020-06-08 16:37:49 -07:00
Kevin Hester
e80673ed15 Merge branch 'master' into patch-1 2020-06-08 16:32:43 -07:00
geeksville
bdbaf9c655 remove old BLE api 2020-06-08 16:08:02 -07:00
geeksville
7473a6c27a unify activity detection in PhoneAPI, turn off BLE API while serial API in use 2020-06-08 16:06:59 -07:00
Kevin Hester
4919129bbc Merge pull request #165 from slavino/patch-1
Update README.md
2020-06-08 13:53:12 -07:00
Slavomir Hustaty
a02175cec0 Update README.md
https://www.everythingrf.com/community/lora-frequency-in-europe

The LoRa Alliance has defined two frequency bands for the usage of LoRa technology in Europe. These bands are EU433 from 433.05 to 434.79 MHz and EU863 from 863 to 870 MHz.

EU433 (433.05 to 434.79 MHz)

The end devices in EU433 band operate from 433.05 to 434.79 MHz and use a channel data structure to support at least 16 channels.

and so on...
https://lora-alliance.org/sites/default/files/2018-04/lorawantm_regional_parameters_v1.1rb_-_final.pdf
2020-06-08 21:34:02 +02:00
rradar
63affdd2e7 Update build-instructions.md to use code tags
Update build-instructions.md to make (more) use of code tags
2020-06-08 10:55:03 +01:00
Kevin Hester
351687e5cd Merge pull request #160 from geeksville/longaddr
PROTOCOL CHANGE! activate 32 bit nodenums/packetids
2020-06-07 22:23:31 -07:00
geeksville
739f497bbd Merge branch 'master' into longaddr 2020-06-07 22:16:43 -07:00
geeksville
2d2ed591e9 set num_bits for nodenum and packet id after loading save file 2020-06-07 22:12:06 -07:00
geeksville
871a85d688 force all devices to discard old settings 2020-06-07 17:22:07 -07:00
geeksville
a5f05019db fix build instructions 2020-06-06 14:30:15 -07:00
geeksville
e124d2094f PROTOCOL CHANGE! activate 32 bit nodenums/packetids 2020-06-06 13:16:36 -07:00
Kevin Hester
5d874cd43b Merge pull request #159 from geeksville/master
hotfix release for reboot bug
2020-06-06 08:48:14 -07:00
geeksville
9f6cdadd3e Merge remote-tracking branch 'root/master' 2020-06-06 08:43:10 -07:00
geeksville
6a3853ef35 0.6.8 2020-06-06 08:33:20 -07:00
geeksville
9ea65c6793 Fix #153 - details below
Somehow nodenum was getting reset to zero (and saved to flash - which is
bad because it makes the failure permanent).  So I've changed nodenum
selection to occur after we load the saved preferences (and we try to keep
nodenum stable in that case).

I'm puzzled as to how it ever got set to zero (unless there *shudder*
is some errant pointer that clobbered it).  But next week I'm turning
4 byte nodenums back on, which will make this moot - because they
will always be based on macaddr and the current process where nodes
haggle with the mesh to pick a unique one-byte nodenum will be gone.
2020-06-06 08:30:01 -07:00
geeksville
8d14e97dfa oops - we were not saving radio state 2020-06-06 08:07:21 -07:00
Kevin Hester
420b7d48d9 Merge pull request #157 from geeksville/master
update webpage
2020-06-05 11:38:17 -07:00
geeksville
5915669f6f Merge remote-tracking branch 'root/master' 2020-06-05 11:33:58 -07:00
geeksville
52b01db306 announce beta 2020-06-05 11:33:19 -07:00
Kevin Hester
a0d6ecb331 Merge pull request #156 from geeksville/master
minor
2020-06-05 11:09:00 -07:00
geeksville
0271b02d50 Merge remote-tracking branch 'root/master' 2020-06-05 11:06:40 -07:00
geeksville
9f61c78c0e doc merge 2020-06-05 11:05:36 -07:00
geeksville
4b5cfaf9ba changes from bringing up PPR 2020-06-05 11:00:18 -07:00
Kevin Hester
c014c1bafe Merge pull request #155 from geeksville/master
now in beta
2020-06-04 11:28:39 -07:00
geeksville
96594516af now in beta 2020-06-04 11:25:06 -07:00
Kevin Hester
bed20bf1fa Merge pull request #151 from geeksville/longaddr
Longaddr
2020-06-04 10:41:39 -07:00
geeksville
4e5a445d8b 0.6.7 2020-06-04 10:37:08 -07:00
geeksville
99437d931e fix #153 2020-06-03 16:08:11 -07:00
geeksville
5166717298 confirm randomSeed is set correctly 2020-06-03 14:24:34 -07:00
geeksville
49b5738f4f add min_app_version so apps can warn if they are too old 2020-06-03 13:57:30 -07:00
geeksville
c753ea7cd1 don't use a fixed randomSeed. 2020-06-03 13:51:53 -07:00
geeksville
5b1488ddf0 Allow packet and nodenums to be 32 bits long (but don't change yet) 2020-06-03 13:46:31 -07:00
geeksville
a34cfb0ee0 Populate metainfo for apps to allow 32 bit node and packet ids 2020-06-03 13:15:45 -07:00
Kevin Hester
d8ff605d32 Merge pull request #147 from geeksville/nrf52
my DSR changes broke acks for flood routing also.  Fix #146
2020-06-03 12:57:56 -07:00
geeksville
8031c47602 put nrf52 on back burner for a couple of days 2020-06-03 12:55:55 -07:00
geeksville
e522e47544 Full DSR WIP 2020-06-03 12:49:36 -07:00
geeksville
ed0f79b683 Merge branch 'master' into nrf52 2020-05-27 15:54:44 -07:00
Kevin Hester
de1337d351 Merge pull request #148 from geeksville/master
fixes for @dafeman's board
2020-05-27 15:52:00 -07:00
geeksville
1b34a0c6d8 Help make sx1262 go for @dafeman 's board. See below for details:
Hi, I think the problem you were having building for ESP32 was due to
a funny thing.  Notice the #define for INTERRUPT_ATTR.  That macro expands
to IRAM_ATTR - which is a special flag the ESP32 requires for _any_ code
that is going to be called from an ISR.  So that the code is guaranteed
to be in RAM (the ESP32 uses a clever scheme where the FLASH is actually
high speed serial flash and all reads/writes are actually only happening
to a small number of pages in RAM and they have a driver that is constantly
copying blocks they need into that ram.  This essentially how VM works
for desktop computers, but in their case they are paging to FLASH.

But for code that runs in an interrupt handler must _always_ be in RAM
because if you took a 'page fault' for that code being missing in RAM they
can't nicely do their clever VM scheme.

So that's all good.  The problem was - apparently GCC for the ESP32 has a
a bug when that attribute is applied in the class declaration.  So
I moved it out into the cpp file and all seems well now.
2020-05-27 15:47:59 -07:00
geeksville
313380381b no need for this old debug output 2020-05-27 15:40:47 -07:00
geeksville
f56ff2ca20 DSR WIP 2020-05-27 15:31:32 -07:00
geeksville
da2ef0ac61 misc nrf52 todo 2020-05-27 15:31:23 -07:00
geeksville
6a3033fa85 improve NRF52 debugging environment 2020-05-26 15:55:36 -07:00
geeksville
f3a1c5e679 Possible fix for https://meshtastic.discourse.group/t/a-note-about-limited-support-for-the-neo-8m-gps-boards/233/3?u=geeksville 2020-05-26 13:10:34 -07:00
geeksville
a96c8fd4db nrf52 debugging is supported as long as BLE is not advertising 2020-05-25 17:16:09 -07:00
geeksville
da3ed9926b add monitor mode debugging support 2020-05-25 15:56:06 -07:00
geeksville
fd386d9d7f UC1701 WIP 2020-05-25 15:55:38 -07:00
geeksville
5d1614989e Only add interfaces to the router if they can be initied 2020-05-25 15:47:45 -07:00
Kevin Hester
bae73a9526 Merge branch 'master' into nrf52 2020-05-25 11:56:38 -07:00
geeksville
f4b1678535 my DSR changes broke acks for flood routing also. Fix #146 2020-05-25 11:55:42 -07:00
Kevin Hester
943517a1e4 Merge pull request #145 from geeksville/nrf52
nrf52 and misc other wip changes
2020-05-25 10:48:25 -07:00
geeksville
03999e9d56 fix build for esp32 2020-05-25 10:46:26 -07:00
geeksville
d5f177b1ee begin UC1701 driver 2020-05-25 10:41:46 -07:00
geeksville
fdaed7e323 Fix MIN_BAT_MILLIVOLTS per @spattinson 2020-05-25 10:41:19 -07:00
geeksville
d39e775c95 make flash filesystem work on NRF52 2020-05-25 10:07:42 -07:00
geeksville
829e0b6e26 fix extra free 2020-05-25 08:19:14 -07:00
geeksville
1656c8d0cb use my Timer class on all platforms, it works better than the freertos version 2020-05-25 07:48:36 -07:00
geeksville
2770cc7de3 Use the SX1262 receive duty cycle mode to get radio current draw down to
about 2.5mA @ 3V while in receive mode.
2020-05-24 19:23:50 -07:00
geeksville
48de631e04 disable activelyReceiving for sx1262 for now - it doesn't yet work 2020-05-24 16:34:18 -07:00
geeksville
66b11bcbd7 print RF52 reset reason 2020-05-24 16:20:21 -07:00
geeksville
cda7487cbe add a NRF52 hardfault handler 2020-05-24 16:08:58 -07:00
geeksville
e8f6504ec4 Make an accelerated NRF52 implementation for AEX256-CTR crypto 2020-05-24 14:45:50 -07:00
geeksville
8f1b26bdda DSR wip still kinda busted (rx packets not working - even for regular router) 2020-05-24 12:59:10 -07:00
geeksville
5bd3e4bcd0 DSR WIP 2020-05-23 17:39:08 -07:00
geeksville
fb3b62f8f0 CSR WIP 2020-05-23 15:48:23 -07:00
geeksville
e89fe2f7d9 DSR WIP 2020-05-23 12:50:33 -07:00
geeksville
16812c3ee4 add ignore_incoming to user preferences, for automated testing of DSR topologies 2020-05-23 10:01:36 -07:00
geeksville
c9cb293bf2 cleanup virtual inheritence for Router/Reliable/Flooding/DSR 2020-05-23 09:24:22 -07:00
geeksville
8e2e154cdd Merge remote-tracking branch 'root/master' into nrf52 2020-05-22 19:06:35 -07:00
geeksville
b7505ab575 Merge branch 'nrf52' 2020-05-22 19:06:14 -07:00
geeksville
cc47e29fac released 0.6.4 already 2020-05-22 19:06:08 -07:00
geeksville
608f8349d9 todo updates 2020-05-22 19:05:29 -07:00
Kevin Hester
3ecd93dcf6 Merge pull request #144 from kalon33/update_fixes
thanks @kalon33!
2020-05-22 19:01:06 -07:00
geeksville
dd0209b192 Merge remote-tracking branch 'root/master' into nrf52
# Conflicts:
#	docs/software/nrf52-TODO.md
#	src/mesh/RadioLibInterface.cpp
#	src/mesh/mesh.pb.h
2020-05-22 11:12:22 -07:00
geeksville
ae92567383 notes 2020-05-22 11:09:10 -07:00
Nicolas Derive
9f2646ba03 writeflash is actually write_flash in esptool.py 2020-05-22 12:53:54 +02:00
geeksville
925e46da8c make serial console work on nrf52 2020-05-21 21:17:53 -07:00
geeksville
a6475ce848 experiment with CFG_DEBUG (it fails) 2020-05-21 21:17:38 -07:00
geeksville
9149912a2d adafruit includes segger by default 2020-05-21 21:17:19 -07:00
geeksville
2dadb4d7a2 make nrf52dk build again 2020-05-21 20:31:22 -07:00
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
Kevin Hester
f10ad07f97 Merge pull request #125 from geeksville/nema-124
support ublox 8m gpses (I think)
2020-05-04 20:18:33 -07:00
geeksville
95df7dd8dc 0.6.2 2020-05-04 20:04:44 -07:00
geeksville
dcd1f7478a fix 124 - we now fallback to nema if we can't talk ublox protocol to
the GPS.  Though we are super power inefficient about it so TODO/FIXME
someday to decrease our power draw.
2020-05-04 20:02:43 -07:00
geeksville
c2be6c4068 WIP on #124 2020-05-04 17:39:57 -07:00
geeksville
101eef5495 oops lat/lon need to be signed ;-) 2020-05-04 11:21:24 -07:00
geeksville
933d5424da abstract out the UBlox GPS driver 2020-05-04 11:15:05 -07:00
geeksville
ecf528f9b6 move gps before refactoring 2020-05-04 10:23:47 -07:00
geeksville
9b309fe0a0 Use int based lat/long from now on in the device code
for https://github.com/meshtastic/Meshtastic-device/issues/124
2020-05-04 08:09:08 -07:00
Kevin Hester
29fd8dc7a5 Merge pull request #123 from geeksville/screen
fix missing carriage returns.  thanks to @gregwalters in #119
2020-05-02 20:24:03 -07:00
geeksville
624b95782d fix missing carriage returns. thanks to @gregwalters in #119 2020-05-02 20:21:42 -07:00
Kevin Hester
4fa25042c8 Merge pull request #122 from geeksville/dropped
changes to fix #121
2020-05-02 20:17:14 -07:00
Kevin Hester
9f9cb030ad Merge branch 'master' into dropped 2020-05-02 20:14:48 -07:00
geeksville
1d9290afc0 now that the rfinterfaces are smarter, no need to do backoff in
the flood router.  the interfaces will handle it.
2020-05-02 19:53:58 -07:00
geeksville
ad2f639195 don't leak messages if they are handled locally 2020-05-02 19:53:13 -07:00
geeksville
07b4eea037 fix log msg 2020-05-02 19:52:54 -07:00
geeksville
79c61cf0e0 limit max power on rf95 to 17 (rather than 20, because 20 can...
burn up parts if you exceed 1% duty cycle)
2020-05-02 19:52:37 -07:00
geeksville
80268ea56a send() is supposed to always free buffers, even if it returns an error 2020-05-02 19:51:55 -07:00
geeksville
bb9f595b8b Fix #11 2020-05-02 19:51:25 -07:00
geeksville
2ad314f150 we now always listen before transmit - even if we have just completed a packet 2020-05-02 08:29:51 -07:00
Kevin Hester
916e62398d Merge pull request #120 from geeksville/master
fix busted build on heltec style devices (0.6.1)
2020-05-01 17:01:52 -07:00
geeksville
4735b3ff5b 0.6.1 hotfix build for busted heltec style devices 2020-05-01 16:35:32 -07:00
geeksville
cfd6483ea5 oops - platform IO can have stale target specific builds without this 2020-05-01 16:34:16 -07:00
geeksville
4f7a25f562 remove unneeded include 2020-05-01 16:33:40 -07:00
geeksville
4176d79ee9 fix warnings 2020-05-01 16:33:26 -07:00
Kevin Hester
fad496378c Merge pull request #118 from geeksville/master
misc fri workqueue
2020-05-01 12:37:28 -07:00
geeksville
e084699704 SNR is now a float, fix the screen display 2020-05-01 12:31:36 -07:00
geeksville
50213d8323 move packet handling into its own thread 2020-05-01 12:11:04 -07:00
geeksville
763276a2c8 Merge branch 'radiolib' 2020-05-01 09:04:19 -07:00
geeksville
71fcdba017 cleanup directory structure 2020-05-01 09:04:00 -07:00
Kevin Hester
08227e79d0 Merge pull request #117 from geeksville/radiolib
Change to use Radiolib as the basis for our networking, and add RX1262 support
2020-05-01 08:55:17 -07:00
geeksville
5a4fab2506 start msg sequence numbers with a random number each boot 2020-05-01 08:51:53 -07:00
geeksville
31eb2f5337 very important: don't allow immediate sends if we have pending ISRs 2020-05-01 08:32:16 -07:00
geeksville
82c1752d85 less logspam 2020-05-01 08:31:52 -07:00
geeksville
49a13bbfd3 increase gps config timeout, could take up to 2.5 secs 2020-05-01 08:31:31 -07:00
geeksville
0096f54ae9 better debug output 2020-04-30 22:53:21 -07:00
geeksville
5af122b39d update todo list 2020-04-30 21:49:55 -07:00
geeksville
4e106f4098 remove radiohead 2020-04-30 21:42:11 -07:00
geeksville
1f1d683f4f add back the old code that checked if the radio was actvively receiving 2020-04-30 21:29:51 -07:00
geeksville
a8f64c3cc8 make a custom version fo rf95 class, so we can can deal with
chips that have bad version codes.
2020-04-30 21:11:03 -07:00
geeksville
968a2d7fbc store SNR in received packets 2020-04-30 19:58:10 -07:00
geeksville
1fab9c5aac temp hack to get new rf95 driver working 2020-04-30 19:31:17 -07:00
geeksville
22bca31ce3 properly set the RF95 iface 2020-04-30 18:05:06 -07:00
geeksville
48c045a253 move SPI init into main 2020-04-30 17:56:30 -07:00
geeksville
e9ca7792eb new RF95 driver is written 2020-04-30 17:43:29 -07:00
geeksville
b1a55b4576 old RF95 API works again 2020-04-30 16:47:56 -07:00
geeksville
dd7452ad96 old RF95 code builds again 2020-04-30 16:36:59 -07:00
geeksville
d7d8188093 implement most of sleep handling for the new radio stack 2020-04-30 15:50:07 -07:00
geeksville
62a893c760 SX1262 approximately works top-to-bottom, but need to add sleep modes 2020-04-30 15:43:41 -07:00
geeksville
a2ba9d3c44 new receive code works a little better 2020-04-30 13:50:40 -07:00
geeksville
22720e9f63 ex1262 receiving kinda works 2020-04-30 12:37:58 -07:00
geeksville
11b79a942d add todos 2020-04-30 10:54:53 -07:00
geeksville
3c3e722181 new sending kinda works 2020-04-30 10:00:40 -07:00
geeksville
fce31560c6 the mountain of changes needed to kinda make tx work compiles. 2020-04-30 09:44:16 -07:00
geeksville
074ac33b8a make a gdb "restart" command that allows restarting without rebuilding 2020-04-29 20:23:59 -07:00
geeksville
2982e197e0 radio settings now work on real sx1262 hw 2020-04-29 19:04:59 -07:00
geeksville
f69ddf168b we now hopefully apply the same radio settings as we did for the RF95 2020-04-29 18:46:32 -07:00
geeksville
8d985cfd37 cleanup so eventually rf95 can share common msg code with sx1262 2020-04-29 16:28:11 -07:00
geeksville
4693302d82 crummy sx1262 fake init kinda works 2020-04-29 16:06:23 -07:00
geeksville
814c126e67 ugly WIP on switching to RadioLib, still need to set freq etc... 2020-04-29 14:54:03 -07:00
geeksville
a7d153abcb CUSTOM GPIOs the SX1262MB2CAS shield when installed on the NRF52840-DK development board 2020-04-29 12:57:34 -07:00
geeksville
1b265eb48d switch from sx126x-arduino to radiolab 2020-04-29 10:50:50 -07:00
geeksville
94e80d3b44 mention python API 2020-04-28 20:51:02 -07:00
geeksville
f1ec95f49b update project name 2020-04-28 20:47:20 -07:00
Kevin Hester
7ee2643e9a Merge pull request #114 from geeksville/master
Add python API to webpage
2020-04-28 17:48:00 -07:00
geeksville
2ab34357d5 emit FromRadio.rebooted to serial test harness can detect reboots 2020-04-28 17:43:16 -07:00
geeksville
b53392ab73 Merge remote-tracking branch 'root/master' 2020-04-28 17:07:42 -07:00
geeksville
803d2dfefb add note about python API 2020-04-28 17:06:00 -07:00
Kevin Hester
6a54bbcf2a Merge pull request #113 from geeksville/master
Make new (optional) serial protobuf API work
2020-04-28 15:46:56 -07:00
geeksville
02dfe7564f 0.6.0 release 2020-04-28 15:39:05 -07:00
geeksville
b704911603 minor protobuf update 2020-04-28 11:20:00 -07:00
geeksville
0193c281ef change webpage to say android app is in general availability 2020-04-28 08:42:09 -07:00
geeksville
59086fd477 fixes after testing stream protocol with python client 2020-04-27 18:52:57 -07:00
geeksville
88a704c4d3 for now, allow debug out to be interleaved with protobufs 2020-04-27 09:46:06 -07:00
geeksville
9f49f90acd ignore vscode/launch.json 2020-04-27 09:42:31 -07:00
Kevin Hester
93f69d5a94 Merge pull request #112 from geeksville/serialproto
Serial/USB API for stress testing and other API clients
2020-04-27 09:40:10 -07:00
geeksville
cceecf5f8e New serial protobuf transport approximately works and is backward
compatiable with the text debug output.
2020-04-27 09:36:39 -07:00
geeksville
e5d2d24e2c move nanopb includes to correct directory 2020-04-27 09:27:36 -07:00
geeksville
eb40013ddc Create RedirectablePrint and NoopPrint for serial debug redirection 2020-04-27 09:01:25 -07:00
geeksville
dda946d933 Stream API coded but not tested 2020-04-27 08:45:39 -07:00
geeksville
178e800969 add beginnings of StreamAPI 2020-04-27 08:10:17 -07:00
geeksville
dec4870649 begin cleaning up mesh library layer so that it could be split someday 2020-04-27 07:54:19 -07:00
Kevin Hester
97f5a7c5fe Merge pull request #111 from geeksville/nrf52
Changes to support the NRF52
2020-04-27 07:47:29 -07:00
geeksville
7ff42b89a6 CI autobuilder can't yet build nrf52 targets 2020-04-27 07:44:57 -07:00
Kevin Hester
44de3bd179 Merge branch 'master' into nrf52 2020-04-27 07:41:23 -07:00
geeksville
15cb599cd1 move nrf52 stuff to correct directory name 2020-04-27 07:39:50 -07:00
geeksville
038b7c9c91 update todos 2020-04-27 07:29:36 -07:00
Kevin Hester
724e39bc92 Merge pull request #109 from geeksville/master
my saturday recreational work queue
2020-04-27 07:26:30 -07:00
geeksville
def86131f0 Merge branch 'master' into nrf52
# Conflicts:
#	src/main.cpp
2020-04-26 18:34:41 -07:00
geeksville
8f1c1a9049 move debug msg 2020-04-25 11:46:46 -07:00
geeksville
3f3a1a11df when flooding, randomly delay sent packets to decrease chances of...
stomping on other senders that we can't even hear.
2020-04-25 11:43:28 -07:00
geeksville
64f6c0f5c0 clean up PeriodicTask so I can eventually use it with a scheduler 2020-04-25 10:59:40 -07:00
geeksville
7cd60d859e possibly use radiohub for the new radio 2020-04-24 21:59:05 -07:00
geeksville
db11d9280c add nrf52 DFU app helper 2020-04-24 21:08:16 -07:00
geeksville
ca03110932 Update ESP32 build to work with latest NRF52 changes 2020-04-24 14:55:51 -07:00
geeksville
b8b503cb0a Add starting point of PPR variants definition 2020-04-24 12:40:22 -07:00
geeksville
bb885a5110 add a variant based on the nrf52840-dk but with a RC clock.
Because I was dumb and accidentally ran some code that configured gpio 0
as an output and that was enough to smoke the xtal that was preinstalled
between P0.0 and P0.1.
2020-04-24 12:30:06 -07:00
geeksville
bebaa838c4 no need for LightSleep state on NRF52 CPUs 2020-04-24 11:21:10 -07:00
geeksville
7fa9d09d9f placeholder guess at PMU code until I have HW 2020-04-24 09:33:45 -07:00
geeksville
7bc299573f move esp32 specific code into esp32 land 2020-04-24 08:52:49 -07:00
geeksville
e0a1855429 Add PMU driver 2020-04-24 08:38:00 -07:00
geeksville
5e75beff3f don't block but queue log messages for the ICE (and eventual crash reports) 2020-04-24 08:06:29 -07:00
geeksville
4f7e85c1a4 cleanup serial instanciation on boards where we might not use it 2020-04-24 08:05:56 -07:00
geeksville
5ad30caf67 todo updates 2020-04-24 08:05:33 -07:00
geeksville
8f26ae240a Add UC1701 and SX126X drivers (not yet using them though) 2020-04-24 08:05:25 -07:00
geeksville
d445cbe083 fix device name 2020-04-23 21:22:58 -07:00
geeksville
12599849db update todo list 2020-04-23 19:05:03 -07:00
geeksville
3c9c01189d old RF95 driver probably works on NRF52 now 2020-04-23 18:47:27 -07:00
geeksville
4f3a9d8646 example BLE code approximately works 2020-04-23 18:11:32 -07:00
geeksville
0c7c3f17e5 fix nrf52 macaddr byte order 2020-04-23 18:06:46 -07:00
geeksville
5b0451f25c add NRF52 BLE example code 2020-04-23 18:02:28 -07:00
geeksville
8f3b33c84c use a real macaddr on the nrf52 2020-04-23 16:55:25 -07:00
geeksville
2fdb75efdf make GPS 'work' on nrf52 2020-04-23 16:20:07 -07:00
geeksville
fbd12e1929 oled screen probably works now on nrf52 2020-04-23 13:56:15 -07:00
geeksville
ffe95f62ab no need to pass in scl & sda into screen constructor 2020-04-23 13:53:51 -07:00
geeksville
3e4ccef992 fix warnings 2020-04-23 13:53:29 -07:00
geeksville
309e7be00c use segger console on nrf52 2020-04-23 13:52:46 -07:00
geeksville
f0f6c4950b on NRF52 use the Segger debug console for debug logging 2020-04-23 13:27:16 -07:00
geeksville
16998ebd8d fix compiler warnings 2020-04-23 13:26:53 -07:00
geeksville
b77c068881 create MeshRadio even on NRF52 (though it is currently using a Sim interface) 2020-04-23 12:50:54 -07:00
geeksville
e94227cddd cope with missing interfaces in send 2020-04-23 12:48:00 -07:00
geeksville
fe3cbeed3a misc NRF52 fixes 2020-04-23 12:47:41 -07:00
Kevin Hester
2061706c11 Merge pull request #105 from geeksville/newapi
The new bluetooth API
2020-04-23 11:44:56 -07:00
geeksville
a0b6d57591 Fix #69 - new BLE API is in and tested from android 2020-04-23 11:41:30 -07:00
geeksville
3673f95fe5 woot! using new BLE api approximately works for reading 2020-04-23 11:02:14 -07:00
geeksville
c67b53b969 remove owner from ToRadio 2020-04-23 10:30:14 -07:00
geeksville
562b227c73 new API now seems fully implemented - now on to testing. #69 2020-04-22 16:11:54 -07:00
geeksville
169d85d0fa handle the new set_owner and set_radio messages 2020-04-22 15:13:05 -07:00
geeksville
bd77d47215 change serial baud rate to 921600 2020-04-22 14:58:35 -07:00
geeksville
e40524baf0 begin moving comms glue from the old crufty BLE code to the new cleaner PhoneAPI class 2020-04-22 14:55:36 -07:00
geeksville
31f735ae1f minor status update 2020-04-20 19:30:41 -07:00
geeksville
9232dfcccf WIP - add new baseclass for all api endpoints (serial, bluetooth, udp)
https://github.com/meshtastic/Meshtastic-esp32/issues/69
2020-04-20 18:03:13 -07:00
160 changed files with 8058 additions and 7716 deletions

1
.gitignore vendored
View File

@@ -6,6 +6,5 @@ main/credentials.h
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace

3
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "proto"]
path = proto
url = https://github.com/meshtastic/Meshtastic-protobufs.git
[submodule "sdk-nrfxlib"]
path = sdk-nrfxlib
url = https://github.com/nrfconnect/sdk-nrfxlib.git

32
.vscode/launch.json vendored
View File

@@ -1,32 +0,0 @@
// AUTOMATICALLY GENERATED FILE. PLEASE DO NOT MODIFY IT MANUALLY
// PIO Unified Debugger
//
// Documentation: https://docs.platformio.org/page/plus/debugging.html
// Configuration: https://docs.platformio.org/page/projectconf/section_env_debug.html
{
"version": "0.2.0",
"configurations": [
{
"type": "platformio-debug",
"request": "launch",
"name": "PIO Debug",
"executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf",
"toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin",
"preLaunchTask": {
"type": "PlatformIO",
"task": "Pre-Debug"
},
"internalConsoleOptions": "openOnSessionStart"
},
{
"type": "platformio-debug",
"request": "launch",
"name": "PIO Debug (skip Pre-Debug)",
"executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf",
"toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin",
"internalConsoleOptions": "openOnSessionStart"
}
]
}

View File

@@ -50,8 +50,15 @@
"cassert": "cpp"
},
"cSpell.words": [
"Blox",
"HFSR",
"Meshtastic",
"NEMAGPS",
"Ublox",
"bkpt",
"cfsr",
"descs",
"ocrypto",
"protobufs"
]
}

View File

@@ -1,10 +1,10 @@
# Meshtastic-esp32
# Meshtastic-device
This is the device side code for the [meshtastic.org](https://www.meshtastic.org) project.
![Continuous Integration](https://github.com/meshtastic/Meshtastic-esp32/workflows/Continuous%20Integration/badge.svg)
Meshtastic is a project that lets you use
Meshtastic is a project that lets you use
inexpensive GPS mesh radios as an extensible, super long battery life mesh GPS communicator. These radios are great for hiking, skiing, paragliding -
essentially any hobby where you don't have reliable internet access. Each member of your private mesh can always see the location and distance of all other
members and any text messages sent to your group chat.
@@ -14,15 +14,34 @@ will optionally work with your phone, but no phone is required.
Typical time between recharging the radios should be about eight days.
This project is currently early-alpha, but if you have questions please [join our discussion forum](https://meshtastic.discourse.group/).
This project is is currently in beta-testing - if you have questions please [join our discussion forum](https://meshtastic.discourse.group/).
This software is 100% open source and developed by a group of hobbyist experimenters. No warranty is provided, if you'd like to improve it - we'd love your help. Please post in the chat.
## Supported hardware
We currently support three models of radios. 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.
See (meshtastic.org) for 3D printable cases.
- 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)
- [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 - 868MHz, 433MHz
Getting a version that includes a screen is optional, but highly recommended.
## Firmware Installation
@@ -35,12 +54,13 @@ Please post comments on our [group chat](https://meshtastic.discourse.group/) if
1. Download and unzip the latest Meshtastic firmware [release](https://github.com/meshtastic/Meshtastic-esp32/releases).
2. Download [ESPHome Flasher](https://github.com/esphome/esphome-flasher/releases) (either x86-32bit Windows or x64-64 bit Windows).
3. Connect your radio to your USB port and open ESPHome Flasher.
4. If your board is not showing under Serial Port then you likely need to install the drivers for the CP210X serial chip. In Windows you can check by searching “Device Manager” and ensuring the device is shown under “Ports”.
5. If there is an error, download the drivers [here](https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers), then unzip and run the Installer application.
4. If your board is not showing under Serial Port then you likely need to install the drivers for the CP210X serial chip. In Windows you can check by searching “Device Manager” and ensuring the device is shown under “Ports”.
5. If there is an error, download the drivers [here](https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers), then unzip and run the Installer application.
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,9 +90,12 @@ 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.
Note: If you have previously installed meshtastic, you don't need to run this full script instead just run `esptool.py --baud 921600 write_flash 0x10000 firmware-_board_-_country_-_version_.bin`. This will be faster, also all of your current preferences will be preserved.
You should see something like this:
@@ -143,14 +166,13 @@ Hard resetting via RTS pin...
7. The board will boot and show the Meshtastic logo.
8. Please post a comment on our chat so we know if these instructions worked for you ;-). If you find bugs/have-questions post there also - we will be rapidly iterating over the next few weeks.
## Meshtastic Android app
# Meshtastic Android app
The source code for the (optional) Meshtastic Android app is [here](https://github.com/meshtastic/Meshtastic-Android).
The companion (optional) Meshtastic Android app is [here](https://github.com/meshtastic/Meshtastic-Android). You can also download it on Google Play.
Alpha test builds are current available by opting into our alpha test group. See (www.meshtastic.org) for instructions.
# Python API
After our rate of change slows a bit, we will make beta builds available here (without needing to join the alphatest group):
[![Download at https://play.google.com/store/apps/details?id=com.geeksville.mesh](https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png)](https://play.google.com/store/apps/details?id=com.geeksville.mesh&referrer=utm_source%3Dgithub%26utm_medium%3Desp32-readme%26utm_campaign%3Dmeshtastic-esp32%2520readme%26anid%3Dadmob&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1)
We offer a [python API](https://github.com/meshtastic/Meshtastic-python) that makes it easy to use these devices to provide mesh networking for your custom projects.
# Development

View File

@@ -39,6 +39,12 @@ 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
for COUNTRY in $COUNTRIES; do
for BOARD in $BOARDS; do
do_build $BOARD
@@ -61,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
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 write_flash 0x10000 $FILENAME

4
bin/nrf52-console.sh Executable file
View File

@@ -0,0 +1,4 @@
# JLinkRTTViewer
JLinkRTTClient

3
bin/nrf52-gdbserver.sh Executable file
View File

@@ -0,0 +1,3 @@
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52840_XXAA

View File

@@ -3,4 +3,4 @@
echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.1"
# the nanopb tool seems to require that the .options file be in the current directory!
cd proto
../../nanopb-0.4.1-linux-x86/generator-bin/protoc --nanopb_out=-v:../src -I=../proto mesh.proto
../../nanopb-0.4.1-linux-x86/generator-bin/protoc --nanopb_out=-v:../src/mesh -I=../proto mesh.proto

View File

@@ -1 +1 @@
pio device monitor -b 115200
pio device monitor -b 921600

View File

@@ -1 +1 @@
pio device monitor -p /dev/ttyUSB1 -b 115200
pio device monitor -p /dev/ttyUSB1 -b 921600

View File

@@ -1,3 +1,3 @@
export VERSION=0.4.3
export VERSION=0.7.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"
}

46
boards/ppr.json Normal file
View File

@@ -0,0 +1,46 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_PPR -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [["0x239A", "0x4403"]],
"usb_product": "PPR",
"mcu": "nrf52840",
"variant": "ppr",
"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": "Meshtastic PPR (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": "Othernet"
}

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.
Meshtastic is a project that lets you use
inexpensive (\$30 ish) GPS radios as an extensible, long battery life, secure, mesh GPS communicator. These radios are great for hiking, skiing, paragliding - essentially any hobby where you don't have reliable internet access. Each member of your private mesh can always see the location and distance of all other members and any text messages sent to your group chat.
The radios automatically create a mesh to forward packets as needed, so everyone in the group can receive messages from even the furthest member. The radios will optionally work with your phone, but no phone is required.
@@ -13,6 +13,7 @@ Note: Questions after reading this? See our new [forum](https://meshtastic.disco
- Applications where closed source GPS communicators just won't cut it (it is easy to add features for glider pilots etc...)
- Secure long-range communication within groups without depending on cellular providers
- Finding your lost kids ;-)
- Through our [python API](https://pypi.org/project/meshtastic/) use these inexpensive radios to easily add mesh networking to your own projects.
[![Youtube video demo](desk-video-screenshot.png)](https://www.youtube.com/watch?v=WlNbMbVZlHI "Meshtastic early demo")
@@ -23,21 +24,24 @@ Not all of these features are fully implemented yet - see **important** disclaim
- Very long battery life (should be about eight days with the beta software)
- Built in GPS and [LoRa](https://en.wikipedia.org/wiki/LoRa) radio, but we manage the radio automatically for you
- Long range - a few miles per node but each node will forward packets as needed
- Secure - channels are encrypted by AES256 (But see important disclaimers below wrt this feature)
- Shows direction and distance to all members of your channel
- Directed or broadcast text messages for channel members
- Open and extensible codebase supporting multiple hardware vendors - no lock in to one vendor
- Communication API for bluetooth devices (such as our Android app) to use the mesh. So if you have some application that needs long range low power networking, this might work for you.
- Eventually (within a couple of months) we should have a modified version of Signal that works with this project.
- Communication API for bluetooth devices (such as our Android app) to use the mesh. An iOS application is in the works. And [Meshtastic-python](https://pypi.org/project/meshtastic/) provides access from desktop computers.
- Very easy sharing of private secured channels. Just share a special link or QR code with friends and they can join your encrypted mesh
This project is currently in early alpha - if you have questions please [join our discussion forum](https://meshtastic.discourse.group/).
This project is currently in beta testing but it is fairly stable and feature complete - if you have questions please [join our discussion forum](https://meshtastic.discourse.group/).
This software is 100% open source and developed by a group of hobbyist experimenters. No warranty is provided, if you'd like to improve it - we'd love your help. Please post in the [forum](https://meshtastic.discourse.group/).
# Updates
Note: Updates are happening almost daily, only major updates are listed below. For more details see our chat, github releases or the Android alpha tester emails.
Note: Updates are happening almost daily, only major updates are listed below. For more details see our forum.
- 06/04/2020 - 0.6.7 Beta releases of both the application and the device code are released. Features are fairly solid now with a sizable number of users.
- 04/28/2020 - 0.6.0 [Python API](https://pypi.org/project/meshtastic/) released. Makes it easy to use meshtastic devices as "zero config / just works" mesh transport adapters for other projects.
- 04/20/2020 - 0.4.3 Pretty solid now both for the android app and the device code. Many people have donated translations and code. Probably going to call it a beta soon.
- 03/03/2020 - 0.0.9 of the Android app and device code is released. Still an alpha but fairly functional.
- 02/25/2020 - 0.0.4 of the Android app is released. This is a very early alpha, see below to join the alpha-testers group.
- 02/23/2020 - 0.0.4 release. Still very bleeding edge but much closer to the final power management, a charged T-BEAM should run for many days with this load. If you'd like to try it, we'd love your feedback. Click [here](https://github.com/meshtastic/Meshtastic-esp32/blob/master/README.md) for instructions.
@@ -45,11 +49,11 @@ Note: Updates are happening almost daily, only major updates are listed below. F
## Meshtastic Android app
Once out of alpha the companion Android application will be released here:
Our Android application is available here:
[![Download at https://play.google.com/store/apps/details?id=com.geeksville.mesh](https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png)](https://play.google.com/store/apps/details?id=com.geeksville.mesh&referrer=utm_source%3Dhomepage%26anid%3Dadmob)
But if you want the bleeding edge app now, we'd love to have your help testing. Three steps to opt-in to the alpha- test:
The link above will return older more stable releases. We would prefer if you join our alpha-test group, because the application is rapidly improving. Three steps to opt-in to the alpha- test:
1. Join [this Google group](https://groups.google.com/forum/#!forum/meshtastic-alpha-testers) with the account you use in Google Play.
2. Go to this [URL](https://play.google.com/apps/testing/com.geeksville.mesh) to opt-in to the alpha test.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

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

View File

@@ -1,30 +1,30 @@
# Bluetooth API
The Bluetooth API is design to have only a few characteristics and most polymorphism comes from the flexible set of Google Protocol Buffers which are sent over the wire. We use protocol buffers extensively both for the bluetooth API and for packets inside the mesh or when providing packets to other applications on the phone.
The Bluetooth API is design to have only a few characteristics and most polymorphism comes from the flexible set of Google Protocol Buffers which are sent over the wire. We use protocol buffers extensively both for the bluetooth API and for packets inside the mesh or when providing packets to other applications on the phone.
## A note on MTU sizes
This device will work with any MTU size, but it is highly recommended that you call your phone's "setMTU function to increase MTU to 512 bytes" as soon as you connect to a service. This will dramatically improve performance when reading/writing packets.
This device will work with any MTU size, but it is highly recommended that you call your phone's "setMTU function to increase MTU to 512 bytes" as soon as you connect to a service. This will dramatically improve performance when reading/writing packets.
## MeshBluetoothService
## MeshBluetoothService
This is the main bluetooth service for the device and provides the API your app should use to get information about the mesh, send packets or provision the radio.
This is the main bluetooth service for the device and provides the API your app should use to get information about the mesh, send packets or provision the radio.
For a reference implementation of a client that uses this service see [RadioInterfaceService](https://github.com/meshtastic/Meshtastic-Android/blob/master/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt). Typical flow when
For a reference implementation of a client that uses this service see [RadioInterfaceService](https://github.com/meshtastic/Meshtastic-Android/blob/master/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt). Typical flow when
a phone connects to the device should be the following:
* SetMTU size to 512
* Read a RadioConfig from "radio" - used to get the channel and radio settings
* Read (and write if incorrect) a User from "user" - to get the username for this node
* Read a MyNodeInfo from "mynode" to get information about this local device
* Write an empty record to "nodeinfo" to restart the nodeinfo reading state machine
* Read from "nodeinfo" until it returns empty to build the phone's copy of the current NodeDB for the mesh
* Read from "fromradio" until it returns empty to get any messages that arrived for this node while the phone was away
* Subscribe to notify on "fromnum" to get notified whenever the device has a new received packet
* Read that new packet from "fromradio"
* Whenever the phone has a packet to send write to "toradio"
- SetMTU size to 512
- Read a RadioConfig from "radio" - used to get the channel and radio settings
- Read (and write if incorrect) a User from "user" - to get the username for this node
- Read a MyNodeInfo from "mynode" to get information about this local device
- Write an empty record to "nodeinfo" to restart the nodeinfo reading state machine
- Read from "nodeinfo" until it returns empty to build the phone's copy of the current NodeDB for the mesh
- Read from "fromradio" until it returns empty to get any messages that arrived for this node while the phone was away
- Subscribe to notify on "fromnum" to get notified whenever the device has a new received packet
- Read that new packet from "fromradio"
- Whenever the phone has a packet to send write to "toradio"
For definitions (and documentation) on FromRadio, ToRadio, MyNodeInfo, NodeInfo and User protocol buffers see [mesh.proto](https://github.com/meshtastic/Meshtastic-protobufs/blob/master/mesh.proto)
For definitions (and documentation) on FromRadio, ToRadio, MyNodeInfo, NodeInfo and User protocol buffers see [mesh.proto](https://github.com/meshtastic/Meshtastic-protobufs/blob/master/mesh.proto)
UUID for the service: 6ba1b218-15a8-461f-9fa8-5dcae273eafd
@@ -37,7 +37,7 @@ Description (including human readable name)
8ba2bcc2-ee02-4a55-a531-c525c5e454d5
read
fromradio - contains a newly received FromRadio packet destined towards the phone (up to MAXPACKET bytes per packet).
After reading the esp32 will put the next packet in this mailbox. If the FIFO is empty it will put an empty packet in this
After reading the esp32 will put the next packet in this mailbox. If the FIFO is empty it will put an empty packet in this
mailbox.
f75c76d2-129e-4dad-a1dd-7866124401e7
@@ -49,34 +49,22 @@ read,notify,write
fromnum - the current packet # in the message waiting inside fromradio, if the phone sees this notify it should read messages
until it catches up with this number.
The phone can write to this register to go backwards up to FIXME packets, to handle the rare case of a fromradio packet was dropped after the esp32 callback was called, but before it arrives at the phone. If the phone writes to this register the esp32 will discard older packets and put the next packet >= fromnum in fromradio.
The phone can write to this register to go backwards up to FIXME packets, to handle the rare case of a fromradio packet was dropped after the esp32 callback was called, but before it arrives at the phone. If the phone writes to this register the esp32 will discard older packets and put the next packet >= fromnum in fromradio.
When the esp32 advances fromnum, it will delay doing the notify by 100ms, in the hopes that the notify will never actally need to be sent if the phone is already pulling from fromradio.
Note: that if the phone ever sees this number decrease, it means the esp32 has rebooted.
ea9f3f82-8dc4-4733-9452-1f6da28892a2
read
mynode - read this to access a MyNodeInfo protobuf
d31e02e0-c8ab-4d3f-9cc9-0b8466bdabe8
read, write
nodeinfo - read this to get a series of NodeInfos (ending with a null empty record), write to this to restart the read statemachine that returns all the node infos
b56786c8-839a-44a1-b98e-a1724c4a0262
read,write
radio - read/write this to access a RadioConfig protobuf
6ff1d8b6-e2de-41e3-8c0b-8fa384f64eb6
read,write
owner - read/write this to access a User protobuf
Re: queue management
Not all messages are kept in the fromradio queue (filtered based on SubPacket):
* only the most recent Position and User messages for a particular node are kept
* all Data SubPackets are kept
* No WantNodeNum / DenyNodeNum messages are kept
A variable keepAllPackets, if set to true will suppress this behavior and instead keep everything for forwarding to the phone (for debugging)
- only the most recent Position and User messages for a particular node are kept
- all Data SubPackets are kept
- No WantNodeNum / DenyNodeNum messages are kept
A variable keepAllPackets, if set to true will suppress this behavior and instead keep everything for forwarding to the phone (for debugging)
## Protobuf API
On connect, you should send a want_config_id protobuf to the device. This will cause the device to send its node DB and radio config via the fromradio endpoint. After sending the full DB, the radio will send a want_config_id to indicate it is done sending the configuration.
## Other bluetooth services
@@ -85,21 +73,21 @@ provided by the device.
### BluetoothSoftwareUpdate
The software update service. For a sample function that performs a software update using this API see [startUpdate](https://github.com/meshtastic/Meshtastic-Android/blob/master/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt).
The software update service. For a sample function that performs a software update using this API see [startUpdate](https://github.com/meshtastic/Meshtastic-Android/blob/master/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt).
SoftwareUpdateService UUID cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30
Characteristics
| UUID | properties | description|
|--------------------------------------|------------------|------------|
| e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e | write,read | total image size, 32 bit, write this first, then read read back to see if it was acceptable (0 mean not accepted) |
| e272ebac-d463-4b98-bc84-5cc1a39ee517 | write | data, variable sized, recommended 512 bytes, write one for each block of file |
| 4826129c-c22a-43a3-b066-ce8f0d5bacc6 | write | crc32, write last - writing this will complete the OTA operation, now you can read result |
| 5e134862-7411-4424-ac4a-210937432c77 | read,notify | result code, readable but will notify when the OTA operation completes |
| GATT_UUID_SW_VERSION_STR/0x2a28 | read | We also implement these standard GATT entries because SW update probably needs them: |
| GATT_UUID_MANU_NAME/0x2a29 | read | |
| GATT_UUID_HW_VERSION_STR/0x2a27 | read | |
| UUID | properties | description |
| ------------------------------------ | ----------- | ----------------------------------------------------------------------------------------------------------------- |
| e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e | write,read | total image size, 32 bit, write this first, then read read back to see if it was acceptable (0 mean not accepted) |
| e272ebac-d463-4b98-bc84-5cc1a39ee517 | write | data, variable sized, recommended 512 bytes, write one for each block of file |
| 4826129c-c22a-43a3-b066-ce8f0d5bacc6 | write | crc32, write last - writing this will complete the OTA operation, now you can read result |
| 5e134862-7411-4424-ac4a-210937432c77 | read,notify | result code, readable but will notify when the OTA operation completes |
| GATT_UUID_SW_VERSION_STR/0x2a28 | read | We also implement these standard GATT entries because SW update probably needs them: |
| GATT_UUID_MANU_NAME/0x2a29 | read | |
| GATT_UUID_HW_VERSION_STR/0x2a27 | read | |
### DeviceInformationService
@@ -107,4 +95,4 @@ Implements the standard BLE contract for this service (has software version, har
### BatteryLevelService
Implements the standard BLE contract service, provides battery level in a way that most client devices should automatically understand (i.e. it should show in the bluetooth devices screen automatically)
Implements the standard BLE contract service, provides battery level in a way that most client devices should automatically understand (i.e. it should show in the bluetooth devices screen automatically)

View File

@@ -6,10 +6,11 @@ in these instructions I describe use of their command line tool.
1. Purchase a suitable radio (see above)
2. Install [PlatformIO](https://platformio.org/platformio-ide)
3. Download this git repo and cd into it
4. If you are outside the USA, edit [platformio.ini](/platformio.ini) to set the correct frequency range for your country. The line you need to change starts with "hw_version" and instructions are provided above that line. Options are provided for EU433, EU835, CN, JP and US. Pull-requests eagerly accepted for other countries.
5. Plug the radio into your USB port
6. Type "pio run --environment XXX -t upload" (This command will fetch dependencies, build the project and install it on the board via USB). For XXX, use the board type you have (either tbeam, heltec, ttgo-lora32-v1, ttgo-lora32-v2).
7. Platform IO also installs a very nice VisualStudio Code based IDE, see their [tutorial](https://docs.platformio.org/en/latest/tutorials/espressif32/arduino_debugging_unit_testing.html) if you'd like to use it.
4. Run `git submodule update --init --recursive` to pull in dependencies this project needs.
5. If you are outside the USA, edit [platformio.ini](/platformio.ini) to set the correct frequency range for your country. The line you need to change starts with `hw_version` and instructions are provided above that line. Options are provided for `EU433`, `EU835`, `CN`, `JP` and `US` (default). Pull-requests eagerly accepted for other countries.
6. Plug the radio into your USB port
7. Type `pio run --environment XXX -t upload` (This command will fetch dependencies, build the project and install it on the board via USB). For XXX, use the board type you have (either `tbeam`, `heltec`, `ttgo-lora32-v1`, `ttgo-lora32-v2`).
8. Platform IO also installs a very nice VisualStudio Code based IDE, see their [tutorial](https://docs.platformio.org/en/latest/tutorials/espressif32/arduino_debugging_unit_testing.html) if you'd like to use it.
## Decoding stack traces

37
docs/software/crypto.md Normal file
View File

@@ -0,0 +1,37 @@
# 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
- Have the app change the crypto key when the user generates a new channel

View File

@@ -1,19 +1,92 @@
# 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
- oops I might have broken message reception
- DONE Don't use broadcasts for the network pings (close open github issue)
- DONE add ignoreSenders to radioconfig to allow testing different mesh topologies by refusing to see certain senders
- test multihop delivery with the python framework
optimizations / low priority:
- read @cyclomies long email with good ideas on optimizations and reply
- Remove NodeNum assignment algorithm (now that we use 4 byte node nums)
- make android app warn if firmware is too old or too new to talk to
- change nodenums and packetids in protobuf to be fixed32
- low priority: think more careful about reliable retransmit intervals
- make ReliableRouter.pending threadsafe
- bump up PacketPool size for all the new ack/nak/routing packets
- 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 +150,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

184
docs/software/nrf52-TODO.md Normal file
View File

@@ -0,0 +1,184 @@
# NRF52 TODO
## Misc work items
## Initial work items
Minimum items needed to make sure hardware is good.
- set power UICR per https://devzone.nordicsemi.com/f/nordic-q-a/28562/nrf52840-regulator-configuration
- switch charge controller into / out of performance mode (see 8.3.1 in datasheet)
- write UC1701 wrapper
- Test hardfault handler for null ptrs (if one isn't already installed)
- test my hackedup bootloader on the real hardware
- Use the PMU driver on real hardware
- Use new radio driver on real hardware
- Use UC1701 LCD driver on real hardware. Still need to create at startup and probe on SPI. Make sure SPI is atomic.
- set vbus voltage per https://infocenter.nordicsemi.com/topic/ps_nrf52840/power.html?cp=4_0_0_4_2
- test the LEDs
- test the buttons
## Secondary work items
Needed to be fully functional at least at the same level of the ESP32 boards. At this point users would probably want them.
- DONE get serial API working
- get full BLE api working
- make power management/sleep work properly
- make a settimeofday implementation
- DONE increase preamble length? - will break other clients? so all devices must update
- DONE enable BLE DFU somehow
- report appversion/hwversion in BLE
- use new LCD driver from screen.cpp. Still need to hook it to a subclass of (poorly named) OLEDDisplay, and override display() to stream bytes out to the screen.
- we need to enable the external tcxo for the sx1262 (on dio3)?
- figure out which regulator mode the sx1262 is operating in
- turn on security for BLE, make pairing work
- make ble endpoints not require "start config", just have them start in config mode
- use new PMU to provide battery voltage/% full to app (both bluetooth and screen)
- do initial power measurements, measure effects of more preamble bits, measure power management and confirm battery life
- set UICR.CUSTOMER to indicate board model & version
## Items to be 'feature complete'
- check datasheet about sx1262 temperature compensation
- enable brownout detection and watchdog
- stop polling for GPS characters, instead stay blocked on read in a thread
- 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
- call PMU set_ADC_CONV(0) during sleep, to stop reading PMU adcs and decrease current draw
- do final power measurements
- backport the common PMU API between AXP192 and PmuBQ25703A
- use the new buttons in the UX
- currently using soft device SD140, is that ideal?
- turn on the watchdog timer, require servicing from key application threads
- nrf52setup should call randomSeed(tbd)
- implement SYSTEMOFF behavior per https://infocenter.nordicsemi.com/topic/ps_nrf52840/power.html?cp=4_0_0_4_2
## Things to do 'someday'
Nice ideas worth considering someday...
- Use flego to me an iOS/linux app? https://felgo.com/doc/qt/qtbluetooth-index/ or
- Use flutter to make an iOS/linux app? https://github.com/Polidea/FlutterBleLib
- enable monitor mode debugging (need to use real jlink): https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/monitor-mode-debugging-with-j-link-and-gdbeclipse
- Improve efficiency of PeriodicTimer by only checking the next queued timer event, and carefully sorting based on schedule
- make a Mfg Controller and device under test classes as examples of custom app code for third party devs. Make a post about this. Use a custom payload type code. Have device under test send a broadcast with max hopcount of 0 for the 'mfgcontroller' payload type. mfg controller will read SNR and reply. DOT will declare failure/success and switch to the regular app screen.
- 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
- Use NRF logger module (includes flash logging etc...) instead of DEBUG_MSG
- Use "LED softblink" library on NRF52 to do nice pretty "breathing" LEDs. Don't whack LED from main thread anymore.
- decrease BLE xmit power "At 0dBm with the DC/DC on, the nRF52832 transmitter draws 5.3mA. Increasing the TX power to +4dBm adds only 2.2mA. Decreasing it to -40 dBm saves only 2.6mA."
- 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)
- 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
- the SX126x class does SPI transfers on a byte by byte basis, which is very ineffecient. Much better to do block writes/reads.
## Old unorganized notes
## Notes on PCA10059 Dongle
- docs: https://infocenter.nordicsemi.com/pdf/nRF52840_Dongle_User_Guide_v1.0.pdf
- Currently using Nordic PCA10059 Dongle hardware
- https://community.platformio.org/t/same-bootloader-same-softdevice-different-board-different-pins/11411/9
- To make Segger JLink more reliable, turn off its fake filesystem. "JLinkExe MSDDisable" per https://learn.adafruit.com/circuitpython-on-the-nrf52/nrf52840-bootloader
## Done
- DONE add "DFU trigger library" to application load
- DONE: using this: Possibly use this bootloader? https://github.com/adafruit/Adafruit_nRF52_Bootloader
- DONE select and install a bootloader (adafruit)
- DONE get old radio driver working on NRF52
- DONE basic test of BLE
- DONE get a debug 'serial' console working via the ICE passthrough feature
- DONE switch to RadioLab? test it with current radio. https://github.com/jgromes/RadioLib
- DONE change rx95 to radiolib
- DONE track rxbad, rxgood, txgood
- DONE neg 7 error code from receive
- 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.
- DONE use SX126x::startReceiveDutyCycleAuto to save power by sleeping and briefly waking to check for preamble bits. Change xmit rules to have more preamble bits.
- scheduleOSCallback doesn't work yet - it is way too fast (causes rapid polling of busyTx, high power draw etc...)
- find out why we reboot while debugging - it was bluetooth/softdevice
- make a file system implementation (preferably one that can see the files the bootloader also sees) - preferably https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/libraries/InternalFileSytem/examples/Internal_ReadWrite/Internal_ReadWrite.ino else use https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.3.0/lib_fds_usage.html?cp=7_5_0_3_55_3
- change packet numbers to be 32 bits
```
/*
per
https://docs.platformio.org/en/latest/tutorials/nordicnrf52/arduino_debugging_unit_testing.html
ardunino github is here https://github.com/sandeepmistry/arduino-nRF5
devboard hw docs here:
https://infocenter.nordicsemi.com/topic/ug_nrf52840_dk/UG/nrf52840_DK/hw_buttons_leds.html?cp=4_0_4_7_6
https://docs.platformio.org/en/latest/boards/nordicnrf52/nrf52840_dk_adafruit.html
must install adafruit bootloader first!
https://learn.adafruit.com/circuitpython-on-the-nrf52/nrf52840-bootloader
see link above and turn off jlink filesystem if we see unreliable serial comms
over USBCDC
adafruit bootloader install commands (from their readme)
kevinh@kevin-server:~/.platformio/packages/framework-arduinoadafruitnrf52/bootloader$
nrfjprog -e -f nrf52 Erasing user available code and UICR flash areas. Applying
system reset.
kevinh@kevin-server:~/.platformio/packages/framework-arduinoadafruitnrf52/bootloader$
nrfjprog --program pca10056/pca10056_bootloader-0.3.2_s140_6.1.1.hex -f nrf52
Parsing hex file.
Reading flash area to program to guarantee it is erased.
Checking that the area to write is not protected.
Programming device.
kevinh@kevin-server:~/.platformio/packages/framework-arduinoadafruitnrf52/bootloader$
nrfjprog --reset -f nrf52 Applying system reset. Run.
install jlink tools from here:
https://www.segger.com/downloads/jlink#J-LinkSoftwareAndDocumentationPack
install nrf tools from here:
https://www.nordicsemi.com/Software-and-tools/Development-Tools/nRF-Command-Line-Tools/Download#infotabs
examples of turning off the loop call to save power:
https://learn.adafruit.com/bluefruit-nrf52-feather-learning-guide/advertising-beacon
example of a more complex BLE service:
https://learn.adafruit.com/bluefruit-nrf52-feather-learning-guide/custom-hrm
*/
// See g_ADigitalPinMap to see how arduino maps to the real gpio#s - and all in
// P0
#define LED1 14
#define LED2 13
/*
good led ble demo:
https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/libraries/Bluefruit52Lib/examples/Peripheral/nrf_blinky/nrf_blinky.ino
*/
```

View File

@@ -4,82 +4,89 @@ i.e. sleep behavior
## Power measurements
Since one of the main goals of this project is long battery life, it is important to consider that in our software/protocol design. Based on initial measurements it seems that the current code should run about three days between charging, and with a bit more software work (see the [TODO list](TODO.md)) a battery life of eight days should be quite doable. Our current power measurements/model is in [this spreadsheet](https://docs.google.com/spreadsheets/d/1ft1bS3iXqFKU8SApU8ZLTq9r7QQEGESYnVgdtvdT67k/edit?usp=sharing).
Since one of the main goals of this project is long battery life, it is important to consider that in our software/protocol design. Based on initial measurements it seems that the current code should run about three days between charging, and with a bit more software work (see the [TODO list](TODO.md)) a battery life of eight days should be quite doable. Our current power measurements/model is in [this spreadsheet](https://docs.google.com/spreadsheets/d/1ft1bS3iXqFKU8SApU8ZLTq9r7QQEGESYnVgdtvdT67k/edit?usp=sharing).
## States
From lower to higher power consumption.
* Super-deep-sleep (SDS) - everything is off, CPU, radio, bluetooth, GPS. Only wakes due to timer or button press. We enter this mode only after no radio comms for a few hours, used to put the device into what is effectively "off" mode.
- Super-deep-sleep (SDS) - everything is off, CPU, radio, bluetooth, GPS. Only wakes due to timer or button press. We enter this mode only after no radio comms for a few hours, used to put the device into what is effectively "off" mode.
onEntry: setBluetoothOn(false), call doDeepSleep
onExit: (standard bootup code, starts in DARK)
* deep-sleep (DS) - CPU is off, radio is on, bluetooth and GPS is off. Note: This mode is never used currently, because it only saves 1.5mA vs light-sleep
- deep-sleep (DS) - CPU is off, radio is on, bluetooth and GPS is off. Note: This mode is never used currently, because it only saves 1.5mA vs light-sleep
(Not currently used)
* light-sleep (LS) - CPU is suspended (RAM stays alive), radio is on, bluetooth is off, GPS is off. Note: currently GPS is not turned
off during light sleep, but there is a TODO item to fix this.
onEntry: setBluetoothOn(false), setGPSPower(false), doLightSleep()
- light-sleep (LS) - CPU is suspended (RAM stays alive), radio is on, bluetooth is off, GPS is off. Note: currently GPS is not turned
off during light sleep, but there is a TODO item to fix this.
NOTE: On NRF52 platforms (because CPU current draw is so low), light-sleep state is never used.
onEntry: setBluetoothOn(false), setGPSPower(false), doLightSleep()
onIdle: (if we wake because our led blink timer has expired) blink the led then go back to sleep until we sleep for ls_secs
onExit: setGPSPower(true), start trying to get gps lock: gps.startLock(), once lock arrives service.sendPosition(BROADCAST)
* No bluetooth (NB) - CPU is running, radio is on, GPS is on but bluetooth is off, screen is off.
- No bluetooth (NB) - CPU is running, radio is on, GPS is on but bluetooth is off, screen is off.
onEntry: setBluetoothOn(false)
onExit:
onExit:
* running dark (DARK) - Everything is on except screen
- running dark (DARK) - Everything is on except screen
onEntry: setBluetoothOn(true)
onExit:
onExit:
* full on (ON) - Everything is on
- full on (ON) - Everything is on
onEntry: setBluetoothOn(true), screen.setOn(true)
onExit: screen.setOn(false)
- serial API usage (SERIAL) - Screen is on, device doesn't sleep, bluetooth off
onEntry: setBluetooth off, screen on
onExit:
## Behavior
### events that increase CPU activity
* At cold boot: The initial state (after setup() has run) is DARK
* While in DARK: if we receive EVENT_BOOT, transition to ON (and show the bootscreen). This event will be sent if we detect we woke due to reset (as opposed to deep sleep)
* While in LS: Once every position_broadcast_secs (default 15 mins) - the unit will wake into DARK mode and broadcast a "networkPing" (our position) and stay alive for wait_bluetooth_secs (default 30 seconds). This allows other nodes to have a record of our last known position if we go away and allows a paired phone to hear from us and download messages.
* While in LS: Every send_owner_interval (defaults to 4, i.e. one hour), when we wake to send our position we _also_ broadcast our owner. This lets new nodes on the network find out about us or correct duplicate node number assignments.
* While in LS/NB/DARK: If the user presses a button (EVENT_PRESS) we go to full ON mode for screen_on_secs (default 30 seconds). Multiple presses keeps resetting this timeout
* While in LS/NB/DARK: If we receive new text messages (EVENT_RECEIVED_TEXT_MSG), we go to full ON mode for screen_on_secs (same as if user pressed a button)
* While in LS: while we receive packets on the radio (EVENT_RECEIVED_PACKET) we will wake and handle them and stay awake in NB mode for min_wake_secs (default 10 seconds)
* While in NB: If we do have packets the phone (EVENT_PACKETS_FOR_PHONE) would want we transition to DARK mode for wait_bluetooth secs.
* While in DARK/ON: If we receive EVENT_BLUETOOTH_PAIR we transition to ON and start our screen_on_secs timeout
* While in NB/DARK/ON: If we receive EVENT_NODEDB_UPDATED we transition to ON (so the new screen can be shown)
* While in DARK: While the phone talks to us over BLE (EVENT_CONTACT_FROM_PHONE) reset any sleep timers and stay in DARK (needed for bluetooth sw update and nice user experience if the user is reading/replying to texts)
- At cold boot: The initial state (after setup() has run) is DARK
- While in DARK: if we receive EVENT_BOOT, transition to ON (and show the bootscreen). This event will be sent if we detect we woke due to reset (as opposed to deep sleep)
- While in LS: Once every position_broadcast_secs (default 15 mins) - the unit will wake into DARK mode and broadcast a "networkPing" (our position) and stay alive for wait_bluetooth_secs (default 30 seconds). This allows other nodes to have a record of our last known position if we go away and allows a paired phone to hear from us and download messages.
- While in LS: Every send*owner_interval (defaults to 4, i.e. one hour), when we wake to send our position we \_also* broadcast our owner. This lets new nodes on the network find out about us or correct duplicate node number assignments.
- While in LS/NB/DARK: If the user presses a button (EVENT_PRESS) we go to full ON mode for screen_on_secs (default 30 seconds). Multiple presses keeps resetting this timeout
- While in LS/NB/DARK: If we receive new text messages (EVENT_RECEIVED_TEXT_MSG), we go to full ON mode for screen_on_secs (same as if user pressed a button)
- While in LS: while we receive packets on the radio (EVENT_RECEIVED_PACKET) we will wake and handle them and stay awake in NB mode for min_wake_secs (default 10 seconds)
- While in NB: If we do have packets the phone (EVENT_PACKETS_FOR_PHONE) would want we transition to DARK mode for wait_bluetooth secs.
- While in DARK/ON: If we receive EVENT_BLUETOOTH_PAIR we transition to ON and start our screen_on_secs timeout
- While in NB/DARK/ON: If we receive EVENT_NODEDB_UPDATED we transition to ON (so the new screen can be shown)
- While in DARK: While the phone talks to us over BLE (EVENT_CONTACT_FROM_PHONE) reset any sleep timers and stay in DARK (needed for bluetooth sw update and nice user experience if the user is reading/replying to texts)
- while in LS/NB/DARK: if SERIAL_CONNECTED, go to serial
### events that decrease cpu activity
* While in ON: If PRESS event occurs, reset screen_on_secs timer and tell the screen to handle the pess
* While in ON: If it has been more than screen_on_secs since a press, lower to DARK
* While in DARK: If time since last contact by our phone exceeds phone_timeout_secs (15 minutes), we transition down into NB mode
* While in DARK or NB: If nothing above is forcing us to stay in a higher mode (wait_bluetooth_secs, min_wake_secs) we will lower down to LS state
* While in LS: If either phone_sds_timeout_secs (default 2 hr) or mesh_sds_timeout_secs (default 2 hr) are exceeded we will lower into SDS mode for sds_secs (default 1 yr) (or a button press). (Note: phone_sds_timeout_secs is currently disabled for now, because most users
are using without a phone)
* Any time we enter LS mode: We stay in that until an interrupt, button press or other state transition. Every ls_secs (default 1 hr) and let the arduino loop() run one iteration (FIXME, not sure if we need this at all), and then immediately reenter lightsleep mode on the CPU.
- While in SERIAL: if SERIAL_DISCONNECTED, go to NB
- While in ON: If PRESS event occurs, reset screen_on_secs timer and tell the screen to handle the pess
- While in ON: If it has been more than screen_on_secs since a press, lower to DARK
- While in DARK: If time since last contact by our phone exceeds phone_timeout_secs (15 minutes), we transition down into NB mode
- While in DARK or NB: If nothing above is forcing us to stay in a higher mode (wait_bluetooth_secs, min_wake_secs) we will lower down to LS state
- While in LS: If either phone_sds_timeout_secs (default 2 hr) or mesh_sds_timeout_secs (default 2 hr) are exceeded we will lower into SDS mode for sds_secs (default 1 yr) (or a button press). (Note: phone_sds_timeout_secs is currently disabled for now, because most users
are using without a phone)
- Any time we enter LS mode: We stay in that until an interrupt, button press or other state transition. Every ls_secs (default 1 hr) and let the arduino loop() run one iteration (FIXME, not sure if we need this at all), and then immediately reenter lightsleep mode on the CPU.
TODO: Eventually these scheduled intervals should be synchronized to the GPS clock, so that we can consider leaving the lora receiver off to save even more power.
TODO: In NB mode we should put cpu into light sleep any time we really aren't that busy (without declaring LS state) - i.e. we should leave GPS on etc...
# Low power consumption tasks
General ideas to hit the power draws our spreadsheet predicts. Do the easy ones before beta, the last 15% can be done after 1.0.
General ideas to hit the power draws our spreadsheet predicts. Do the easy ones before beta, the last 15% can be done after 1.0.
* don't even power on the gps until someone else wants our position, just stay in lora deep sleep until press or rxpacket (except for once an hour updates)
* (possibly bad idea - better to have lora radio always listen - check spreadsheet) have every node wake at the same tick and do their position syncs then go back to deep sleep
* lower BT announce interval to save battery
* change to use RXcontinuous mode and config to drop packets with bad CRC (see section 6.4 of datasheet) - I think this is already the case
* have mesh service run in a thread that stays blocked until a packet arrives from the RF95
* platformio sdkconfig CONFIG_PM and turn on modem sleep mode
* keep cpu 100% in deepsleep until irq from radio wakes it. Then stay awake for 30 secs to attempt delivery to phone.
* use https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/ association sleep pattern to save power - but see https://github.com/espressif/esp-idf/issues/2070 and https://esp32.com/viewtopic.php?f=13&t=12182 it seems with BLE on the 'easy' draw people are getting is 80mA
* stop using loop() instead use a job queue and let cpu sleep
* measure power consumption and calculate battery life assuming no deep sleep
* do lowest sleep level possible where BT still works during normal sleeping, make sure cpu stays in that mode unless lora rx packet happens, bt rx packet happens or button press happens
* optionally do lora messaging only during special scheduled intervals (unless nodes are told to go to low latency mode), then deep sleep except during those intervals - before implementing calculate what battery life would be with this feature
* see section 7.3 of https://cdn.sparkfun.com/assets/learn_tutorials/8/0/4/RFM95_96_97_98W.pdf and have hope radio wake only when a valid packet is received. Possibly even wake the ESP32 from deep sleep via GPIO.
* never enter deep sleep while connected to USB power (but still go to other low power modes)
* when main cpu is idle (in loop), turn cpu clock rate down and/or activate special sleep modes. We want almost everything shutdown until it gets an interrupt.
- don't even power on the gps until someone else wants our position, just stay in lora deep sleep until press or rxpacket (except for once an hour updates)
- (possibly bad idea - better to have lora radio always listen - check spreadsheet) have every node wake at the same tick and do their position syncs then go back to deep sleep
- lower BT announce interval to save battery
- change to use RXcontinuous mode and config to drop packets with bad CRC (see section 6.4 of datasheet) - I think this is already the case
- have mesh service run in a thread that stays blocked until a packet arrives from the RF95
- platformio sdkconfig CONFIG_PM and turn on modem sleep mode
- keep cpu 100% in deepsleep until irq from radio wakes it. Then stay awake for 30 secs to attempt delivery to phone.
- use https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/ association sleep pattern to save power - but see https://github.com/espressif/esp-idf/issues/2070 and https://esp32.com/viewtopic.php?f=13&t=12182 it seems with BLE on the 'easy' draw people are getting is 80mA
- stop using loop() instead use a job queue and let cpu sleep
- measure power consumption and calculate battery life assuming no deep sleep
- do lowest sleep level possible where BT still works during normal sleeping, make sure cpu stays in that mode unless lora rx packet happens, bt rx packet happens or button press happens
- optionally do lora messaging only during special scheduled intervals (unless nodes are told to go to low latency mode), then deep sleep except during those intervals - before implementing calculate what battery life would be with this feature
- see section 7.3 of https://cdn.sparkfun.com/assets/learn_tutorials/8/0/4/RFM95_96_97_98W.pdf and have hope radio wake only when a valid packet is received. Possibly even wake the ESP32 from deep sleep via GPIO.
- never enter deep sleep while connected to USB power (but still go to other low power modes)
- when main cpu is idle (in loop), turn cpu clock rate down and/or activate special sleep modes. We want almost everything shutdown until it gets an interrupt.

19
gdbinit Normal file
View File

@@ -0,0 +1,19 @@
# Setup Monitor Mode Debugging
# Per .platformio/packages/framework-arduinoadafruitnrf52-old/cores/nRF5/linker/nrf52840_s140_v6.ld
# our appload starts at 0x26000
# Disable for now because our version on board doesn't support monitor mode debugging
# mon exec SetMonModeDebug=1
# mon exec SetMonModeVTableAddr=0x26000
# echo setting RTTAddr
# eval "monitor exec SetRTTAddr %p", &_SEGGER_RTT
# the jlink debugger seems to want a pause after reset before we tell it to start running
define restart
echo Restarting
monitor reset
shell sleep 1
cont
end

View File

@@ -1,312 +0,0 @@
#include "BluetoothUtil.h"
#include "BluetoothSoftwareUpdate.h"
#include <esp_gatt_defs.h>
#include <BLE2902.h>
#include <Arduino.h>
#include <Update.h>
#include "configuration.h"
SimpleAllocator btPool;
/**
* Create standard device info service
**/
BLEService *createDeviceInfomationService(BLEServer *server, std::string hwVendor, std::string swVersion, std::string hwVersion = "")
{
BLEService *deviceInfoService = server->createService(BLEUUID((uint16_t)ESP_GATT_UUID_DEVICE_INFO_SVC));
BLECharacteristic *swC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ);
BLECharacteristic *mfC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_MANU_NAME), BLECharacteristic::PROPERTY_READ);
// BLECharacteristic SerialNumberCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_SERIAL_NUMBER_STR), BLECharacteristic::PROPERTY_READ);
/*
* Mandatory characteristic for device info service?
BLECharacteristic *m_pnpCharacteristic = m_deviceInfoService->createCharacteristic(ESP_GATT_UUID_PNP_ID, BLECharacteristic::PROPERTY_READ);
uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version;
uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> 8), (uint8_t) version };
m_pnpCharacteristic->setValue(pnp, sizeof(pnp));
*/
swC->setValue(swVersion);
deviceInfoService->addCharacteristic(addBLECharacteristic(swC));
mfC->setValue(hwVendor);
deviceInfoService->addCharacteristic(addBLECharacteristic(mfC));
if (!hwVersion.empty())
{
BLECharacteristic *hwvC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ);
hwvC->setValue(hwVersion);
deviceInfoService->addCharacteristic(addBLECharacteristic(hwvC));
}
//SerialNumberCharacteristic.setValue("FIXME");
//deviceInfoService->addCharacteristic(&SerialNumberCharacteristic);
// m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, BLECharacteristic::PROPERTY_READ);
// m_manufacturerCharacteristic->setValue(name);
/* add these later?
ESP_GATT_UUID_SYSTEM_ID
*/
// caller must call service->start();
return deviceInfoService;
}
bool _BLEClientConnected = false;
class MyServerCallbacks : public BLEServerCallbacks
{
void onConnect(BLEServer *pServer)
{
_BLEClientConnected = true;
};
void onDisconnect(BLEServer *pServer)
{
_BLEClientConnected = false;
}
};
#define MAX_DESCRIPTORS 32
#define MAX_CHARACTERISTICS 32
static BLECharacteristic *chars[MAX_CHARACTERISTICS];
static size_t numChars;
static BLEDescriptor *descs[MAX_DESCRIPTORS];
static size_t numDescs;
/// Add a characteristic that we will delete when we restart
BLECharacteristic *addBLECharacteristic(BLECharacteristic *c)
{
assert(numChars < MAX_CHARACTERISTICS);
chars[numChars++] = c;
return c;
}
/// Add a characteristic that we will delete when we restart
BLEDescriptor *addBLEDescriptor(BLEDescriptor *c)
{
assert(numDescs < MAX_DESCRIPTORS);
descs[numDescs++] = c;
return c;
}
// Help routine to add a description to any BLECharacteristic and add it to the service
// We default to require an encrypted BOND for all these these characterstics
void addWithDesc(BLEService *service, BLECharacteristic *c, const char *description)
{
c->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
BLEDescriptor *desc = new BLEDescriptor(BLEUUID((uint16_t)ESP_GATT_UUID_CHAR_DESCRIPTION), strlen(description) + 1);
assert(desc);
desc->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
desc->setValue(description);
c->addDescriptor(desc);
service->addCharacteristic(c);
addBLECharacteristic(c);
addBLEDescriptor(desc);
}
static BLECharacteristic *batteryLevelC;
/**
* Create a battery level service
*/
BLEService *createBatteryService(BLEServer *server)
{
// Create the BLE Service
BLEService *pBattery = server->createService(BLEUUID((uint16_t)0x180F));
batteryLevelC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_BATTERY_LEVEL), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
addWithDesc(pBattery, batteryLevelC, "Percentage 0 - 100");
batteryLevelC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification
// I don't think we need to advertise this
// server->getAdvertising()->addServiceUUID(pBattery->getUUID());
pBattery->start();
return pBattery;
}
/**
* Update the battery level we are currently telling clients.
* level should be a pct between 0 and 100
*/
void updateBatteryLevel(uint8_t level)
{
// Pretend to update battery levels - fixme do elsewhere
if (batteryLevelC)
{
batteryLevelC->setValue(&level, 1);
batteryLevelC->notify();
}
}
void dumpCharacteristic(BLECharacteristic *c)
{
std::string value = c->getValue();
if (value.length() > 0)
{
DEBUG_MSG("New value: ");
for (int i = 0; i < value.length(); i++)
DEBUG_MSG("%c", value[i]);
DEBUG_MSG("\n");
}
}
/** converting endianness pull out a 32 bit value */
uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue)
{
std::string value = c->getValue();
uint32_t r = defaultValue;
if (value.length() == 4)
r = value[0] | (value[1] << 8UL) | (value[2] << 16UL) | (value[3] << 24UL);
return r;
}
class MySecurity : public BLESecurityCallbacks
{
protected:
bool onConfirmPIN(uint32_t pin)
{
Serial.printf("onConfirmPIN %u\n", pin);
return false;
}
uint32_t onPassKeyRequest()
{
Serial.println("onPassKeyRequest");
return 123511; // not used
}
void onPassKeyNotify(uint32_t pass_key)
{
Serial.printf("onPassKeyNotify %u\n", pass_key);
startCb(pass_key);
}
bool onSecurityRequest()
{
Serial.println("onSecurityRequest");
return true;
}
void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl)
{
if (cmpl.success)
{
uint16_t length;
esp_ble_gap_get_whitelist_size(&length);
Serial.printf(" onAuthenticationComplete -> success size: %d\n", length);
}
else
{
Serial.printf("onAuthenticationComplete -> fail %d\n", cmpl.fail_reason);
}
// Remove our custom PIN request screen.
stopCb();
}
public:
StartBluetoothPinScreenCallback startCb;
StopBluetoothPinScreenCallback stopCb;
};
BLEServer *pServer;
BLEService *pDevInfo, *pUpdate;
void deinitBLE()
{
assert(pServer);
pServer->getAdvertising()->stop();
destroyUpdateService();
pUpdate->stop();
pDevInfo->stop();
pUpdate->stop(); // we delete them below
// First shutdown bluetooth
BLEDevice::deinit(false);
// do not delete this - it is dynamically allocated, but only once - statically in BLEDevice
// delete pServer->getAdvertising();
delete pUpdate;
delete pDevInfo;
delete pServer;
batteryLevelC = NULL; // Don't let anyone generate bogus notifies
for (int i = 0; i < numChars; i++)
delete chars[i];
numChars = 0;
for (int i = 0; i < numDescs; i++)
delete descs[i];
numDescs = 0;
btPool.reset();
}
BLEServer *initBLE(
StartBluetoothPinScreenCallback startBtPinScreen,
StopBluetoothPinScreenCallback stopBtPinScreen,
std::string deviceName, std::string hwVendor, std::string swVersion, std::string hwVersion)
{
BLEDevice::init(deviceName);
BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT);
/*
* Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation
*/
static MySecurity mySecurity;
mySecurity.startCb = startBtPinScreen;
mySecurity.stopCb = stopBtPinScreen;
BLEDevice::setSecurityCallbacks(&mySecurity);
// Create the BLE Server
pServer = BLEDevice::createServer();
static MyServerCallbacks myCallbacks;
pServer->setCallbacks(&myCallbacks);
pDevInfo = createDeviceInfomationService(pServer, hwVendor, swVersion, hwVersion);
// We now let users create the battery service only if they really want (not all devices have a battery)
// BLEService *pBattery = createBatteryService(pServer);
pUpdate = createUpdateService(pServer, hwVendor, swVersion, hwVersion); // We need to advertise this so our android ble scan operation can see it
// It seems only one service can be advertised - so for now don't advertise our updater
// pServer->getAdvertising()->addServiceUUID(pUpdate->getUUID());
// start all our services (do this after creating all of them)
pDevInfo->start();
pUpdate->start();
// FIXME turn on this restriction only after the device is paired with a phone
// advert->setScanFilter(false, true); // We let anyone scan for us (FIXME, perhaps only allow that until we are paired with a phone and configured) but only let whitelist phones connect
static BLESecurity security; // static to avoid allocs
BLESecurity *pSecurity = &security;
pSecurity->setCapability(ESP_IO_CAP_OUT);
pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND);
pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);
return pServer;
}
// Called from loop
void loopBLE()
{
bluetoothRebootCheck();
}

View File

@@ -1,33 +0,0 @@
#pragma once
#include "PowerFSM.h" // FIXME - someday I want to make this OTA thing a separate lb at at that point it can't touch this
#include "BLECharacteristic.h"
/**
* This mixin just lets the power management state machine know the phone is still talking to us
*/
class BLEKeepAliveCallbacks : public BLECharacteristicCallbacks
{
public:
void onRead(BLECharacteristic *c)
{
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE);
}
void onWrite(BLECharacteristic *c)
{
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE);
}
};
/**
* A characterstic with a set of overridable callbacks
*/
class CallbackCharacteristic : public BLECharacteristic, public BLEKeepAliveCallbacks
{
public:
CallbackCharacteristic(const char *uuid, uint32_t btprops)
: BLECharacteristic(uuid, btprops)
{
setCallbacks(this);
}
};

View File

@@ -9,7 +9,7 @@
; https://docs.platformio.org/page/projectconf.html
[platformio]
default_envs = tbeam
default_envs = tbeam ; Note: the github actions CI test build can't yet build NRF52 targets
[common]
; common is not currently used
@@ -31,7 +31,7 @@ board_build.partitions = partition-table.csv
; note: we add src to our include search path so that lmic_project_config can override
; FIXME: fix lib/BluetoothOTA dependency back on src/ so we can remove -Isrc
build_flags = -Wno-missing-field-initializers -Isrc -Isrc/rf95 -Os -Wl,-Map,.pio/build/output.map
build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -Ilib/nanopb/include -Os -Wl,-Map,.pio/build/output.map
-DAXP_DEBUG_PORT=Serial
-DHW_VERSION_${sysenv.COUNTRY}
-DAPP_VERSION=${sysenv.APP_VERSION}
@@ -51,7 +51,8 @@ build_flags = -Wno-missing-field-initializers -Isrc -Isrc/rf95 -Os -Wl,-Map,.pio
; the default is esptool
; upload_protocol = esp-prog
monitor_speed = 115200
; monitor_speed = 115200
monitor_speed = 921600
# debug_tool = esp-prog
# debug_port = /dev/ttyACM0
@@ -73,15 +74,18 @@ lib_deps =
Wire ; explicitly needed here because the AXP202 library forgets to add it
https://github.com/meshtastic/arduino-fsm.git
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git
https://github.com/meshtastic/RadioLib.git
https://github.com/meshtastic/TinyGPSPlus.git
; Common settings for ESP targes, mixin with extends = esp32_base
[esp32_base]
src_filter =
${env.src_filter} -<bare/>
${env.src_filter} -<nrf52/>
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
[env:tbeam]
@@ -89,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
@@ -119,19 +123,48 @@ board = ttgo-lora32-v1
build_flags =
${esp32_base.build_flags} -D TTGO_LORA_V2
; This is a temporary build target to test turning off particular hardare bits in the build (to improve modularity)
[env:bare]
; Common settings for NRF52 based targets
[nrf52_base]
platform = nordicnrf52
board = nrf52840_dk_adafruit ; nicer than nrf52840_dk - more full gpio mappings
framework = arduino
debug_tool = jlink
build_type = debug ; I'm debugging with ICE a lot now
; note: liboberon provides the AES256 implementation for NRF52 (though not using the hardware acceleration of the NRF52840 - FIXME)
build_flags =
${env.build_flags} -D BARE_BOARD -Wno-unused-variable -Isrc/bare
${env.build_flags} -Wno-unused-variable -Isrc/nrf52 -Isdk-nrfxlib/crypto/nrf_oberon/include -Lsdk-nrfxlib/crypto/nrf_oberon/lib/cortex-m4/hard-float/ -lliboberon_3.0.3
;-DCFG_DEBUG=3
src_filter =
${env.src_filter} -<esp32/>
lib_ignore =
BluetoothOTA
monitor_port = /dev/ttyACM1
debug_extra_cmds =
source gdbinit
; after programming the flash, reset the initial PC
; debug_load_cmds = load
; Set initial breakpoint (defaults to main)
debug_init_break =
;debug_init_break = tbreak loop
;debug_init_break = tbreak Reset_Handler
; The NRF52840-dk development board
[env:nrf52dk]
extends = nrf52_base
board = nrf52840_dk_modified
lib_deps =
UC1701 ; for temp testing
; The PPR board
[env:ppr]
extends = nrf52_base
board = ppr
lib_deps =
${env.lib_deps}
monitor_port = /dev/ttyACM1
UC1701
https://github.com/meshtastic/BQ25703A.git

2
proto

Submodule proto updated: e06645d8db...3ba76bbe4c

1
sdk-nrfxlib Submodule

Submodule sdk-nrfxlib added at 17e8453553

View File

@@ -1,222 +0,0 @@
#include "GPS.h"
#include "configuration.h"
#include "time.h"
#include <assert.h>
#include <sys/time.h>
#ifdef GPS_RX_PIN
HardwareSerial _serial_gps(GPS_SERIAL_NUM);
#else
// Assume NRF52
// Uart _serial_gps(GPS_SERIAL_NUM);
#endif
bool timeSetFromGPS; // We try to set our time from GPS each time we wake from sleep
GPS gps;
// stuff that really should be in in the instance instead...
static uint32_t
timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time
static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock
static bool wantNewLocation = true;
GPS::GPS() : PeriodicTask() {}
void GPS::setup()
{
readFromRTC(); // read the main CPU RTC at first
#ifdef GPS_RX_PIN
_serial_gps.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
// _serial_gps.setRxBufferSize(1024); // the default is 256
// ublox.enableDebugging(Serial);
// note: the lib's implementation has the wrong docs for what the return val is
// it is not a bool, it returns zero for success
isConnected = ublox.begin(_serial_gps);
// try a second time, the ublox lib serial parsing is buggy?
if (!isConnected)
isConnected = ublox.begin(_serial_gps);
if (isConnected) {
DEBUG_MSG("Connected to GPS successfully, TXpin=%d\n", GPS_TX_PIN);
bool factoryReset = false;
bool ok;
if (factoryReset) {
// It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have
// GPS_TX connected)
ublox.factoryReset();
delay(2000);
isConnected = ublox.begin(_serial_gps);
DEBUG_MSG("Factory reset success=%d\n", isConnected);
if (isConnected) {
ublox.assumeAutoPVT(true, true); // Just parse NEMA for now
}
} else {
ok = ublox.setUART1Output(COM_TYPE_UBX, 500); // Use native API
assert(ok);
ok = ublox.setNavigationFrequency(1, 500); // Produce 4x/sec to keep the amount of time we stall in getPVT low
assert(ok);
// ok = ublox.setAutoPVT(false); // Not implemented on NEO-6M
// assert(ok);
// ok = ublox.setDynamicModel(DYN_MODEL_BIKE); // probably PEDESTRIAN but just in case assume bike speeds
// assert(ok);
ok = ublox.powerSaveMode(); // use power save mode
assert(ok);
}
ok = ublox.saveConfiguration(2000);
assert(ok);
} else {
// Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just
// assume NEMA at 9600 baud.
DEBUG_MSG("ERROR: No bidirectional GPS found, hoping that it still might work\n");
// tell lib, we are expecting the module to send PVT messages by itself to our Rx pin
// you can set second parameter to "false" if you want to control the parsing and eviction of the data (need to call
// checkUblox cyclically)
ublox.assumeAutoPVT(true, true);
}
#endif
}
void GPS::readFromRTC()
{
struct timeval tv; /* btw settimeofday() is helpfull here too*/
if (!gettimeofday(&tv, NULL)) {
uint32_t now = millis();
DEBUG_MSG("Read RTC time as %ld (cur millis %u) valid=%d\n", tv.tv_sec, now, timeSetFromGPS);
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
}
}
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
void GPS::perhapsSetRTC(const struct timeval *tv)
{
if (!timeSetFromGPS) {
timeSetFromGPS = true;
DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec);
#ifndef NO_ESP32
settimeofday(tv, NULL);
#else
assert(0);
#endif
readFromRTC();
}
}
#include <time.h>
// for the time being we need to rapidly read from the serial port to prevent overruns
void GPS::loop()
{
PeriodicTask::loop();
}
uint32_t GPS::getTime()
{
return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
}
uint32_t GPS::getValidTime()
{
return timeSetFromGPS ? getTime() : 0;
}
/// Returns true if we think the board can enter deep or light sleep now (we might be trying to get a GPS lock)
bool GPS::canSleep()
{
return true; // we leave GPS on during sleep now, so sleep is okay !wantNewLocation;
}
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
void GPS::prepareSleep()
{
if (isConnected)
ublox.powerOff();
}
void GPS::doTask()
{
#ifdef GPS_RX_PIN
uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix
if (isConnected) {
// Consume all characters that have arrived
// getPVT automatically calls checkUblox
ublox.checkUblox(); // See if new data is available. Process bytes as they come in.
// If we don't have a fix (a quick check), don't try waiting for a solution)
// 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("sec %d\n", ublox.getSecond());
// DEBUG_MSG("lat %d\n", ublox.getLatitude());
// any fix that has time
if (!timeSetFromGPS && ublox.getT()) {
struct timeval tv;
/* Convert to unix time
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
*/
struct tm t;
t.tm_sec = ublox.getSecond();
t.tm_min = ublox.getMinute();
t.tm_hour = ublox.getHour();
t.tm_mday = ublox.getDay();
t.tm_mon = ublox.getMonth() - 1;
t.tm_year = ublox.getYear() - 1900;
t.tm_isdst = false;
time_t res = mktime(&t);
tv.tv_sec = res;
tv.tv_usec = 0; // time.centisecond() * (10 / 1000);
DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec);
if (t.tm_year < 0 || t.tm_year >= 300)
DEBUG_MSG("Ignoring invalid GPS time\n");
else
perhapsSetRTC(&tv);
}
if ((fixtype >= 3 && fixtype <= 4) && ublox.getP()) // rd fixes only
{
// we only notify if position has changed
latitude = ublox.getLatitude() * 1e-7;
longitude = ublox.getLongitude() * 1e-7;
altitude = ublox.getAltitude() / 1000; // in mm convert to meters
DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d\n", latitude, longitude, altitude);
hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0
if (hasValidLocation) {
wantNewLocation = false;
notifyObservers(NULL);
// ublox.powerOff();
}
} else // we didn't get a location update, go back to sleep and hope the characters show up
wantNewLocation = true;
#endif
// Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s until we have something over
// the serial
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000);
}
void GPS::startLock()
{
DEBUG_MSG("Looking for GPS lock\n");
wantNewLocation = true;
setPeriod(1);
}

View File

@@ -1,57 +0,0 @@
#pragma once
#include "Observer.h"
#include "PeriodicTask.h"
#include "SparkFun_Ublox_Arduino_Library.h"
#include "sys/time.h"
/**
* A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading)
*
* When new data is available it will notify observers.
*/
class GPS : public PeriodicTask, public Observable<void *>
{
SFE_UBLOX_GPS ublox;
public:
double latitude, longitude;
uint32_t altitude;
bool isConnected; // Do we have a GPS we are talking to
GPS();
/// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero
uint32_t getTime();
/// Return time since 1970 in secs. If we don't have a GPS lock return zero
uint32_t getValidTime();
void setup();
virtual void loop();
virtual void doTask();
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
void perhapsSetRTC(const struct timeval *tv);
/// Returns true if we think the board can enter deep or light sleep now (we might be trying to get a GPS lock)
bool canSleep();
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
void prepareSleep();
/// Restart our lock attempt - try to get and broadcast a GPS reading ASAP
void startLock();
/// Returns ture if we have acquired GPS lock.
bool hasLock() const { return hasValidLocation; }
private:
void readFromRTC();
bool hasValidLocation = false; // default to false, until we complete our first read
};
extern GPS gps;

View File

@@ -1,125 +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() // , 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
radioIf.setThisAddress(
nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor time.
if (!radioIf.init()) {
DEBUG_MSG("LoRa radio init failed\n");
DEBUG_MSG("Uncomment '#define SERIAL_DEBUG' in RH_RF95.cpp for detailed debug info\n");
return false;
}
// not needed - defaults on
// rf95.setPayloadCRC(true);
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;
}
int MeshRadio::reloadConfig(void *unused)
{
radioIf.setModeIdle(); // Need to be idle before doing init
// Set up default configuration
// No Sync Words in LORA mode.
radioIf.setModemConfig(
(RH_RF95::ModemConfigChoice)channelSettings.modem_config); // Radio default
// setModemConfig(Bw125Cr48Sf4096); // slow and reliable?
// rf95.setPreambleLength(8); // Default is 8
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
int channel_num = hash(channelSettings.name) % NUM_CHANNELS;
float center_freq = CH0 + CH_SPACING * channel_num;
if (!radioIf.setFrequency(center_freq)) {
DEBUG_MSG("setFrequency failed\n");
assert(0); // fixme panic
}
// Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
// The default transmitter power is 13dBm, using PA_BOOST.
// If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then
// you can set transmitter powers from 5 to 23 dBm:
// FIXME - can we do this? It seems to be in the Heltec board.
radioIf.setTxPower(channelSettings.tx_power, false);
DEBUG_MSG("Set radio: name=%s, config=%u, ch=%d, txpower=%d\n", channelSettings.name, channelSettings.modem_config,
channel_num, channelSettings.tx_power);
// Done with init tell radio to start receiving
radioIf.setModeRx();
return 0;
}

View File

@@ -1,19 +0,0 @@
#pragma once
// low level types
#include "MemoryPool.h"
#include "mesh.pb.h"
#include <Arduino.h>
typedef uint8_t NodeNum;
typedef uint8_t PacketId; // A packet sequence number
#define NODENUM_BROADCAST 255
#define ERRNO_OK 0
#define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER
typedef int ErrorCode;
/// Alloc and free packets to our global, ISR safe pool
extern MemoryPool<MeshPacket> packetPool;

49
src/OSTimer.cpp Normal file
View File

@@ -0,0 +1,49 @@
#include "OSTimer.h"
#include "configuration.h"
/**
* Schedule a callback to run. The callback must _not_ block, though it is called from regular thread level (not ISR)
*
* NOTE! xTimerPend... seems to ignore the time passed in on ESP32 and on NRF52
* The reason this didn't work is bcause xTimerPednFunctCall really isn't a timer function at all - it just means run the callback
* from the timer thread the next time you have spare cycles.
*
* @return true if successful, false if the timer fifo is too full.
bool scheduleOSCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec)
{
return xTimerPendFunctionCall(callback, param1, param2, pdMS_TO_TICKS(delayMsec));
} */
#ifndef NO_ESP32
// Super skanky quick hack to use hardware timers of the ESP32
static hw_timer_t *timer;
static PendableFunction tCallback;
static void *tParam1;
static uint32_t tParam2;
static void IRAM_ATTR onTimer()
{
(*tCallback)(tParam1, tParam2);
}
bool scheduleHWCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec)
{
if (!timer) {
timer = timerBegin(0, 80, true); // one usec per tick (main clock is 80MhZ on ESP32)
assert(timer);
timerAttachInterrupt(timer, &onTimer, true);
}
tCallback = callback;
tParam1 = param1;
tParam2 = param2;
timerAlarmWrite(timer, delayMsec * 1000L, false); // Do not reload, we want it to be a single shot timer
timerRestart(timer);
timerAlarmEnable(timer);
return true;
}
#endif

8
src/OSTimer.h Normal file
View File

@@ -0,0 +1,8 @@
#pragma once
#include <Arduino.h>
typedef void (*PendableFunction)(void *pvParameter1, uint32_t ulParameter2);
/// Uses a hardware timer, but calls the handler in _interrupt_ context
bool scheduleHWCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec);

View File

@@ -1,7 +1,7 @@
#pragma once
#include <Arduino.h>
#include <assert.h>
#include <list>
template <class T> class Observable;
@@ -14,7 +14,6 @@ template <class T> class Observer
Observable<T> *observed = NULL;
public:
virtual ~Observer();
void observe(Observable<T> *o);

View File

@@ -1,21 +1,39 @@
#include "PeriodicTask.h"
#include "Periodic.h"
PeriodicScheduler periodicScheduler;
PeriodicTask::PeriodicTask(uint32_t initialPeriod) : period(initialPeriod) {}
/// call this from loop
void PeriodicTask::loop()
void PeriodicTask::setup()
{
{
meshtastic::LockGuard lg(&lock);
uint32_t now = millis();
if (!period || (now - lastMsec) < period) {
return;
periodicScheduler.schedule(this);
}
/// call this from loop
void PeriodicScheduler::loop()
{
meshtastic::LockGuard lg(&lock);
uint32_t now = millis();
for (auto t : tasks) {
if (t->period && (now - t->lastMsec) >= t->period) {
t->doTask();
t->lastMsec = now;
}
lastMsec = now;
}
// Release the lock in case the task wants to change the period.
doTask();
}
void PeriodicScheduler::schedule(PeriodicTask *t)
{
meshtastic::LockGuard lg(&lock);
tasks.insert(t);
}
void PeriodicScheduler::unschedule(PeriodicTask *t)
{
meshtastic::LockGuard lg(&lock);
tasks.erase(t);
}
void Periodic::doTask()

View File

@@ -1,8 +1,41 @@
#pragma once
#include <cstdint>
#include "lock.h"
#include <Arduino.h>
#include <cstdint>
#include <unordered_set>
class PeriodicTask;
/**
* Runs all PeriodicTasks in the system.
*
* Currently called from main loop() but eventually should be its own thread blocked on a freertos timer.
*/
class PeriodicScheduler
{
friend class PeriodicTask;
/**
* This really should be some form of heap, and when the period gets changed on a task it should get
* rescheduled in that heap. Currently it is just a dumb array and everytime we run loop() we check
* _every_ tasks. If it was a heap we'd only have to check the first task.
*/
std::unordered_set<PeriodicTask *> tasks;
// Protects the above variables.
meshtastic::Lock lock;
public:
/// Run any next tasks which are due for execution
void loop();
private:
void schedule(PeriodicTask *t);
void unschedule(PeriodicTask *t);
};
extern PeriodicScheduler periodicScheduler;
/**
* A base class for tasks that want their doTask() method invoked periodically
@@ -13,27 +46,40 @@
*/
class PeriodicTask
{
friend class PeriodicScheduler;
uint32_t lastMsec = 0;
uint32_t period = 1; // call soon after creation
// Protects the above variables.
meshtastic::Lock lock;
public:
virtual ~PeriodicTask() {}
virtual ~PeriodicTask() { periodicScheduler.unschedule(this); }
/**
* Constructor (will schedule with the global PeriodicScheduler)
*/
PeriodicTask(uint32_t initialPeriod = 1);
/// call this from loop
virtual void loop();
/** MUST be be called once at startup (but after threading is running - i.e. not from a constructor)
*/
void setup();
/// Set a new period in msecs (can be called from doTask or elsewhere and the scheduler will cope)
/**
* Set a new period in msecs (can be called from doTask or elsewhere and the scheduler will cope)
* While zero this task is disabled and will not run
*/
void setPeriod(uint32_t p)
{
meshtastic::LockGuard lg(&lock);
lastMsec = millis(); // reset starting from now
period = p;
}
uint32_t getPeriod() const { return period; }
/**
* Syntatic sugar for suspending tasks
*/
void disable() { setPeriod(0); }
protected:
virtual void doTask() = 0;
};

View File

@@ -87,7 +87,7 @@ static void lsIdle()
static void lsExit()
{
// setGPSPower(true); // restore GPS power
gps.startLock();
gps->startLock();
}
static void nbEnter()
@@ -104,6 +104,12 @@ static void darkEnter()
screen.setOn(false);
}
static void serialEnter()
{
setBluetoothEnable(false);
screen.setOn(true);
}
static void onEnter()
{
screen.setOn(true);
@@ -113,8 +119,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;
}
}
@@ -132,6 +139,7 @@ State stateSDS(sdsEnter, NULL, NULL, "SDS");
State stateLS(lsEnter, lsIdle, lsExit, "LS");
State stateNB(nbEnter, NULL, NULL, "NB");
State stateDARK(darkEnter, NULL, NULL, "DARK");
State stateSERIAL(serialEnter, NULL, NULL, "SERIAL");
State stateBOOT(bootEnter, NULL, NULL, "BOOT");
State stateON(onEnter, NULL, NULL, "ON");
Fsm powerFSM(&stateBOOT);
@@ -147,12 +155,20 @@ void PowerFSM_setup()
powerFSM.add_transition(&stateNB, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet, resetting win wake");
// Handle press events
// Handle press events - note: we ignore button presses when in API mode
powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL, "Press");
powerFSM.add_transition(&stateNB, &stateON, EVENT_PRESS, NULL, "Press");
powerFSM.add_transition(&stateDARK, &stateON, EVENT_PRESS, NULL, "Press");
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(&stateSERIAL, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat");
powerFSM.add_transition(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
powerFSM.add_transition(&stateON, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
@@ -165,6 +181,13 @@ void PowerFSM_setup()
powerFSM.add_transition(&stateDARK, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
powerFSM.add_transition(&stateON, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); // restarts the sleep timer
powerFSM.add_transition(&stateLS, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
powerFSM.add_transition(&stateNB, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
powerFSM.add_transition(&stateDARK, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
powerFSM.add_transition(&stateON, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
powerFSM.add_transition(&stateSERIAL, &stateNB, EVENT_SERIAL_DISCONNECTED, NULL, "serial disconnect");
powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone");
powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone");
@@ -173,10 +196,13 @@ void PowerFSM_setup()
powerFSM.add_timed_transition(&stateDARK, &stateNB, radioConfig.preferences.phone_timeout_secs * 1000, NULL, "Phone timeout");
#ifndef NRF52_SERIES
// We never enter light-sleep state on NRF52 (because the CPU uses so little power normally)
powerFSM.add_timed_transition(&stateNB, &stateLS, radioConfig.preferences.min_wake_secs * 1000, NULL, "Min wake timeout");
powerFSM.add_timed_transition(&stateDARK, &stateLS, radioConfig.preferences.wait_bluetooth_secs * 1000, NULL,
"Bluetooth timeout");
#endif
powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.mesh_sds_timeout_secs * 1000, NULL,
"mesh timeout");

View File

@@ -13,6 +13,9 @@
#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
#define EVENT_SERIAL_CONNECTED 11
#define EVENT_SERIAL_DISCONNECTED 12
extern Fsm powerFSM;

13
src/RedirectablePrint.cpp Normal file
View File

@@ -0,0 +1,13 @@
#include "RedirectablePrint.h"
#include <assert.h>
/**
* A printer that doesn't go anywhere
*/
NoopPrint noopPrint;
void RedirectablePrint::setDestination(Print *_dest)
{
assert(_dest);
dest = _dest;
}

34
src/RedirectablePrint.h Normal file
View File

@@ -0,0 +1,34 @@
#pragma once
#include <Print.h>
/**
* A Printable that can be switched to squirt its bytes to a different sink.
* This class is mostly useful to allow debug printing to be redirected away from Serial
* to some other transport if we switch Serial usage (on the fly) to some other purpose.
*/
class RedirectablePrint : public Print
{
Print *dest;
public:
RedirectablePrint(Print *_dest) : dest(_dest) {}
/**
* Set a new destination
*/
void setDestination(Print *dest);
virtual size_t write(uint8_t c) { return dest->write(c); }
};
class NoopPrint : public Print
{
public:
virtual size_t write(uint8_t c) { return 1; }
};
/**
* A printer that doesn't go anywhere
*/
extern NoopPrint noopPrint;

45
src/SerialConsole.cpp Normal file
View File

@@ -0,0 +1,45 @@
#include "SerialConsole.h"
#include "PowerFSM.h"
#include "configuration.h"
#include <Arduino.h>
#define Port Serial
SerialConsole console;
SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port)
{
canWrite = false; // We don't send packets to our port until it has talked to us first
// setDestination(&noopPrint); for testing, try turning off 'all' debug output and see what leaks
}
/// Do late init that can't happen at constructor time
void SerialConsole::init()
{
Port.begin(SERIAL_BAUD);
StreamAPI::init();
emitRebooted();
}
/**
* we override this to notice when we've received a protobuf over the serial
* stream. Then we shunt off debug serial output.
*/
void SerialConsole::handleToRadio(const uint8_t *buf, size_t len)
{
// Turn off debug serial printing once the API is activated, because other threads could print and corrupt packets
setDestination(&noopPrint);
canWrite = true;
StreamAPI::handleToRadio(buf, len);
}
/// Hookable to find out when connection changes
void SerialConsole::onConnectionChanged(bool connected)
{
if (connected) { // To prevent user confusion, turn off bluetooth while using the serial port api
powerFSM.trigger(EVENT_SERIAL_CONNECTED);
} else {
powerFSM.trigger(EVENT_SERIAL_DISCONNECTED);
}
}

35
src/SerialConsole.h Normal file
View File

@@ -0,0 +1,35 @@
#pragma once
#include "RedirectablePrint.h"
#include "StreamAPI.h"
/**
* Provides both debug printing and, if the client starts sending protobufs to us, switches to send/receive protobufs
* (and starts dropping debug printing - FIXME, eventually those prints should be encapsulated in protobufs).
*/
class SerialConsole : public StreamAPI, public RedirectablePrint
{
public:
SerialConsole();
/// Do late init that can't happen at constructor time
virtual void init();
/**
* we override this to notice when we've received a protobuf over the serial stream. Then we shunt off
* debug serial output.
*/
virtual void handleToRadio(const uint8_t *buf, size_t len);
virtual size_t write(uint8_t c)
{
if (c == '\n') // prefix any newlines with carriage return
RedirectablePrint::write('\r');
return RedirectablePrint::write(c);
}
protected:
/// Hookable to find out when connection changes
virtual void onConnectionChanged(bool connected);
};
extern SerialConsole console;

37
src/WorkerThread.cpp Normal file
View File

@@ -0,0 +1,37 @@
#include "WorkerThread.h"
#include <assert.h>
void Thread::start(const char *name, size_t stackSize, uint32_t priority)
{
auto r = xTaskCreate(callRun, name, stackSize, this, priority, &taskHandle);
assert(r == pdPASS);
}
void Thread::callRun(void *_this)
{
((Thread *)_this)->doRun();
}
void WorkerThread::doRun()
{
while (!wantExit) {
block();
loop();
}
}
/**
* Notify this thread so it can run
*/
void NotifiedWorkerThread::notify(uint32_t v, eNotifyAction action)
{
xTaskNotify(taskHandle, v, action);
}
void NotifiedWorkerThread::block()
{
xTaskNotifyWait(0, // don't clear notification on entry
clearOnRead, &notification, portMAX_DELAY); // Wait forever
}

89
src/WorkerThread.h Normal file
View File

@@ -0,0 +1,89 @@
#include <Arduino.h>
class Thread
{
protected:
TaskHandle_t taskHandle = NULL;
/**
* set this to true to ask thread to cleanly exit asap
*/
volatile bool wantExit = false;
public:
void start(const char *name, size_t stackSize = 1024, uint32_t priority = tskIDLE_PRIORITY);
virtual ~Thread() { vTaskDelete(taskHandle); }
protected:
/**
* The method that will be called when start is called.
*/
virtual void doRun() = 0;
private:
static void callRun(void *_this);
};
/**
* This wraps threading (FreeRTOS for now) with a blocking API intended for efficiently converting onlyschool arduino loop() code.
*
* Use as a mixin base class for the classes you want to convert.
*
* https://www.freertos.org/RTOS_Task_Notification_As_Mailbox.html
*/
class WorkerThread : public Thread
{
protected:
/**
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
*/
virtual void block() = 0;
virtual void loop() = 0;
/**
* The method that will be called when start is called.
*/
virtual void doRun();
};
/**
* A worker thread that waits on a freertos notification
*/
class NotifiedWorkerThread : public WorkerThread
{
public:
/**
* Notify this thread so it can run
*/
void notify(uint32_t v = 0, eNotifyAction action = eNoAction);
/**
* Notify from an ISR
*
* This must be inline or IRAM_ATTR on ESP32
*/
inline void notifyFromISR(BaseType_t *highPriWoken, uint32_t v = 0, eNotifyAction action = eNoAction)
{
xTaskNotifyFromISR(taskHandle, v, action, highPriWoken);
}
protected:
/**
* The notification that was most recently used to wake the thread. Read from loop()
*/
uint32_t notification = 0;
/**
* What notification bits should be cleared just after we read and return them in notification?
*
* Defaults to clear all of them.
*/
uint32_t clearOnRead = UINT32_MAX;
/**
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
*/
virtual void block();
};

View File

@@ -1,6 +0,0 @@
#include "target_specific.h"
void setBluetoothEnable(bool on)
{
// Do nothing
}

View File

@@ -46,45 +46,40 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
//#define USE_JTAG
#endif
#define DEBUG_PORT Serial // Serial debug port
#define SERIAL_BAUD 115200 // Serial debug baud rate
#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
// -----------------------------------------------------------------------------
// DEBUG
// -----------------------------------------------------------------------------
/// Convert a preprocessor name into a quoted string and if that string is empty use "unset"
#define optstr(s) (xstr(s)[0] ? xstr(s) : "unset")
#ifdef DEBUG_PORT
#define DEBUG_MSG(...) DEBUG_PORT.printf(__VA_ARGS__)
#ifdef NRF52840_XXAA // All of the NRF52 targets are configured using variant.h, so this section shouldn't need to be
// board specific
//
// Standard definitions for NRF52 targets
//
#define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth)
// We bind to the GPS using variant.h instead for this platform (Serial1)
// 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
#define DEBUG_MSG(...)
#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
// -----------------------------------------------------------------------------
//
// 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
@@ -101,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"
@@ -122,6 +138,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// Leave undefined to disable our PMU IRQ handler
#define PMU_IRQ 35
#define AXP192_SLAVE_ADDRESS 0x34
#elif defined(TBEAM_V07)
// This string must exactly match the case used in release file names or the android updater won't work
#define HW_VENDOR "tbeam0.7"
@@ -203,32 +221,58 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio
#define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
#define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
#elif defined(BARE_BOARD)
#endif
#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 "bare"
#define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth)
// Turn off GPS code for now
#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
// 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
#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
#elif defined(ARDUINO_NRF52840_PPR)
#define HW_VENDOR "ppr"
#endif
// -----------------------------------------------------------------------------
// DEBUG
// -----------------------------------------------------------------------------
#define SERIAL_BAUD 921600 // Serial debug baud rate
#include "SerialConsole.h"
#define DEBUG_PORT console // Serial debug port
#ifdef NO_ESP32
#define USE_SEGGER
#endif
#ifdef USE_SEGGER
#include "SEGGER_RTT.h"
#define DEBUG_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#else
#ifdef DEBUG_PORT
#define DEBUG_MSG(...) DEBUG_PORT.printf(__VA_ARGS__)
#else
#define DEBUG_MSG(...)
#endif
#endif
// -----------------------------------------------------------------------------
// AXP192 (Rev1-specific options)
// -----------------------------------------------------------------------------
// #define AXP192_SLAVE_ADDRESS 0x34 // Now defined in axp20x.h
#define GPS_POWER_CTRL_CH 3
#define LORA_POWER_CTRL_CH 2

View File

@@ -3,7 +3,7 @@
#include <Arduino.h>
/// Error codes for critical error
enum CriticalErrorCode { NoError, ErrTxWatchdog, ErrSleepEnterWait, ErrNoRadio };
enum CriticalErrorCode { NoError, ErrTxWatchdog, ErrSleepEnterWait, ErrNoRadio, ErrUnspecified };
/// Record an error that should be reported via analytics
void recordCriticalError(CriticalErrorCode code, uint32_t address = 0);

View File

@@ -1,65 +1,81 @@
#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)
{
}
void onWrite(BLECharacteristic *c)
{
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 +83,44 @@ 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 +133,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 +150,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 +159,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();

306
src/esp32/BluetoothUtil.cpp Normal file
View File

@@ -0,0 +1,306 @@
#include "BluetoothUtil.h"
#include "BluetoothSoftwareUpdate.h"
#include "configuration.h"
#include <Arduino.h>
#include <BLE2902.h>
#include <Update.h>
#include <esp_gatt_defs.h>
SimpleAllocator btPool;
/**
* Create standard device info service
**/
BLEService *createDeviceInfomationService(BLEServer *server, std::string hwVendor, std::string swVersion,
std::string hwVersion = "")
{
BLEService *deviceInfoService = server->createService(BLEUUID((uint16_t)ESP_GATT_UUID_DEVICE_INFO_SVC));
BLECharacteristic *swC =
new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ);
BLECharacteristic *mfC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_MANU_NAME), BLECharacteristic::PROPERTY_READ);
// BLECharacteristic SerialNumberCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_SERIAL_NUMBER_STR),
// BLECharacteristic::PROPERTY_READ);
/*
* Mandatory characteristic for device info service?
BLECharacteristic *m_pnpCharacteristic = m_deviceInfoService->createCharacteristic(ESP_GATT_UUID_PNP_ID,
BLECharacteristic::PROPERTY_READ);
uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version;
uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >>
8), (uint8_t) version }; m_pnpCharacteristic->setValue(pnp, sizeof(pnp));
*/
swC->setValue(swVersion);
deviceInfoService->addCharacteristic(addBLECharacteristic(swC));
mfC->setValue(hwVendor);
deviceInfoService->addCharacteristic(addBLECharacteristic(mfC));
if (!hwVersion.empty()) {
BLECharacteristic *hwvC =
new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ);
hwvC->setValue(hwVersion);
deviceInfoService->addCharacteristic(addBLECharacteristic(hwvC));
}
// SerialNumberCharacteristic.setValue("FIXME");
// deviceInfoService->addCharacteristic(&SerialNumberCharacteristic);
// m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29,
// BLECharacteristic::PROPERTY_READ); m_manufacturerCharacteristic->setValue(name);
/* add these later?
ESP_GATT_UUID_SYSTEM_ID
*/
// caller must call service->start();
return deviceInfoService;
}
bool _BLEClientConnected = false;
class MyServerCallbacks : public BLEServerCallbacks
{
void onConnect(BLEServer *pServer) { _BLEClientConnected = true; };
void onDisconnect(BLEServer *pServer) { _BLEClientConnected = false; }
};
#define MAX_DESCRIPTORS 32
#define MAX_CHARACTERISTICS 32
static BLECharacteristic *chars[MAX_CHARACTERISTICS];
static size_t numChars;
static BLEDescriptor *descs[MAX_DESCRIPTORS];
static size_t numDescs;
/// Add a characteristic that we will delete when we restart
BLECharacteristic *addBLECharacteristic(BLECharacteristic *c)
{
assert(numChars < MAX_CHARACTERISTICS);
chars[numChars++] = c;
return c;
}
/// Add a characteristic that we will delete when we restart
BLEDescriptor *addBLEDescriptor(BLEDescriptor *c)
{
assert(numDescs < MAX_DESCRIPTORS);
descs[numDescs++] = c;
return c;
}
// Help routine to add a description to any BLECharacteristic and add it to the service
// We default to require an encrypted BOND for all these these characterstics
void addWithDesc(BLEService *service, BLECharacteristic *c, const char *description)
{
c->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
BLEDescriptor *desc = new BLEDescriptor(BLEUUID((uint16_t)ESP_GATT_UUID_CHAR_DESCRIPTION), strlen(description) + 1);
assert(desc);
desc->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
desc->setValue(description);
c->addDescriptor(desc);
service->addCharacteristic(c);
addBLECharacteristic(c);
addBLEDescriptor(desc);
}
static BLECharacteristic *batteryLevelC;
/**
* Create a battery level service
*/
BLEService *createBatteryService(BLEServer *server)
{
// Create the BLE Service
BLEService *pBattery = server->createService(BLEUUID((uint16_t)0x180F));
batteryLevelC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_BATTERY_LEVEL),
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
addWithDesc(pBattery, batteryLevelC, "Percentage 0 - 100");
batteryLevelC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification
// I don't think we need to advertise this
// server->getAdvertising()->addServiceUUID(pBattery->getUUID());
pBattery->start();
return pBattery;
}
/**
* Update the battery level we are currently telling clients.
* level should be a pct between 0 and 100
*/
void updateBatteryLevel(uint8_t level)
{
// Pretend to update battery levels - fixme do elsewhere
if (batteryLevelC) {
batteryLevelC->setValue(&level, 1);
batteryLevelC->notify();
}
}
void dumpCharacteristic(BLECharacteristic *c)
{
std::string value = c->getValue();
if (value.length() > 0) {
DEBUG_MSG("New value: ");
for (int i = 0; i < value.length(); i++)
DEBUG_MSG("%c", value[i]);
DEBUG_MSG("\n");
}
}
/** converting endianness pull out a 32 bit value */
uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue)
{
std::string value = c->getValue();
uint32_t r = defaultValue;
if (value.length() == 4)
r = value[0] | (value[1] << 8UL) | (value[2] << 16UL) | (value[3] << 24UL);
return r;
}
class MySecurity : public BLESecurityCallbacks
{
protected:
bool onConfirmPIN(uint32_t pin)
{
Serial.printf("onConfirmPIN %u\n", pin);
return false;
}
uint32_t onPassKeyRequest()
{
Serial.println("onPassKeyRequest");
return 123511; // not used
}
void onPassKeyNotify(uint32_t pass_key)
{
Serial.printf("onPassKeyNotify %06u\n", pass_key);
startCb(pass_key);
}
bool onSecurityRequest()
{
Serial.println("onSecurityRequest");
return true;
}
void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl)
{
if (cmpl.success) {
uint16_t length;
esp_ble_gap_get_whitelist_size(&length);
Serial.printf(" authenticated and connected to phone\n");
} else {
Serial.printf("phone authenticate failed %d\n", cmpl.fail_reason);
}
// Remove our custom PIN request screen.
stopCb();
}
public:
StartBluetoothPinScreenCallback startCb;
StopBluetoothPinScreenCallback stopCb;
};
BLEServer *pServer;
BLEService *pDevInfo, *pUpdate;
void deinitBLE()
{
assert(pServer);
pServer->getAdvertising()->stop();
destroyUpdateService();
pUpdate->stop();
pDevInfo->stop();
pUpdate->stop(); // we delete them below
// First shutdown bluetooth
BLEDevice::deinit(false);
// do not delete this - it is dynamically allocated, but only once - statically in BLEDevice
// delete pServer->getAdvertising();
delete pUpdate;
delete pDevInfo;
delete pServer;
batteryLevelC = NULL; // Don't let anyone generate bogus notifies
for (int i = 0; i < numChars; i++)
delete chars[i];
numChars = 0;
for (int i = 0; i < numDescs; i++)
delete descs[i];
numDescs = 0;
btPool.reset();
}
BLEServer *initBLE(StartBluetoothPinScreenCallback startBtPinScreen, StopBluetoothPinScreenCallback stopBtPinScreen,
std::string deviceName, std::string hwVendor, std::string swVersion, std::string hwVersion)
{
BLEDevice::init(deviceName);
BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT);
/*
* Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation
*/
static MySecurity mySecurity;
mySecurity.startCb = startBtPinScreen;
mySecurity.stopCb = stopBtPinScreen;
BLEDevice::setSecurityCallbacks(&mySecurity);
// Create the BLE Server
pServer = BLEDevice::createServer();
static MyServerCallbacks myCallbacks;
pServer->setCallbacks(&myCallbacks);
pDevInfo = createDeviceInfomationService(pServer, hwVendor, swVersion, hwVersion);
// We now let users create the battery service only if they really want (not all devices have a battery)
// BLEService *pBattery = createBatteryService(pServer);
pUpdate = createUpdateService(pServer, hwVendor, swVersion,
hwVersion); // We need to advertise this so our android ble scan operation can see it
// It seems only one service can be advertised - so for now don't advertise our updater
// pServer->getAdvertising()->addServiceUUID(pUpdate->getUUID());
// start all our services (do this after creating all of them)
pDevInfo->start();
pUpdate->start();
// FIXME turn on this restriction only after the device is paired with a phone
// advert->setScanFilter(false, true); // We let anyone scan for us (FIXME, perhaps only allow that until we are paired with a
// phone and configured) but only let whitelist phones connect
static BLESecurity security; // static to avoid allocs
BLESecurity *pSecurity = &security;
pSecurity->setCapability(ESP_IO_CAP_OUT);
pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND);
pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);
return pServer;
}
// Called from loop
void loopBLE()
{
bluetoothRebootCheck();
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "BLECharacteristic.h"
#include "PowerFSM.h" // FIXME - someday I want to make this OTA thing a separate lb at at that point it can't touch this
/**
* A characterstic with a set of overridable callbacks
*/
class CallbackCharacteristic : public BLECharacteristic, public BLECharacteristicCallbacks
{
public:
CallbackCharacteristic(const char *uuid, uint32_t btprops) : BLECharacteristic(uuid, btprops) { setCallbacks(this); }
};

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

@@ -6,19 +6,41 @@
#include <esp_gatt_defs.h>
#include "CallbackCharacteristic.h"
#include "GPS.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PhoneAPI.h"
#include "PowerFSM.h"
#include "configuration.h"
#include "mesh-pb-constants.h"
#include "mesh.pb.h"
#include "GPS.h"
// This scratch buffer is used for various bluetooth reads/writes - but it is safe because only one bt operation can be in
// proccess at once
static uint8_t trBytes[_max(_max(_max(_max(ToRadio_size, RadioConfig_size), User_size), MyNodeInfo_size), FromRadio_size)];
static CallbackCharacteristic *meshFromNumCharacteristic;
BLEService *meshService;
class BluetoothPhoneAPI : public PhoneAPI
{
/**
* Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies)
*/
virtual void onNowHasData(uint32_t fromRadioNum)
{
PhoneAPI::onNowHasData(fromRadioNum);
if (meshFromNumCharacteristic) { // this ptr might change from sleep to sleep, or even be null
meshFromNumCharacteristic->setValue(fromRadioNum);
meshFromNumCharacteristic->notify();
}
}
};
BluetoothPhoneAPI *bluetoothPhoneAPI;
class ProtobufCharacteristic : public CallbackCharacteristic
{
const pb_msgdesc_t *fields;
@@ -33,17 +55,12 @@ class ProtobufCharacteristic : public CallbackCharacteristic
void onRead(BLECharacteristic *c)
{
BLEKeepAliveCallbacks::onRead(c);
size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), fields, my_struct);
DEBUG_MSG("pbread from %s returns %d bytes\n", c->getUUID().toString().c_str(), numbytes);
c->setValue(trBytes, numbytes);
}
void onWrite(BLECharacteristic *c)
{
BLEKeepAliveCallbacks::onWrite(c);
writeToDest(c, my_struct);
}
void onWrite(BLECharacteristic *c) { writeToDest(c, my_struct); }
protected:
/// like onWrite, but we provide an different destination to write to, for use by subclasses that
@@ -58,104 +75,6 @@ class ProtobufCharacteristic : public CallbackCharacteristic
}
};
class NodeInfoCharacteristic : public BLECharacteristic, public BLEKeepAliveCallbacks
{
public:
NodeInfoCharacteristic()
: BLECharacteristic("d31e02e0-c8ab-4d3f-9cc9-0b8466bdabe8",
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ)
{
setCallbacks(this);
}
void onRead(BLECharacteristic *c)
{
BLEKeepAliveCallbacks::onRead(c);
const NodeInfo *info = nodeDB.readNextInfo();
if (info) {
DEBUG_MSG("Sending nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", info->num, info->position.time, info->user.id,
info->user.long_name);
size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), NodeInfo_fields, info);
c->setValue(trBytes, numbytes);
} else {
c->setValue(trBytes, 0); // Send an empty response
DEBUG_MSG("Done sending nodeinfos\n");
}
}
void onWrite(BLECharacteristic *c)
{
BLEKeepAliveCallbacks::onWrite(c);
DEBUG_MSG("Reset nodeinfo read pointer\n");
nodeDB.resetReadPointer();
}
};
// wrap our protobuf version with something that forces the service to reload the config
class RadioCharacteristic : public ProtobufCharacteristic
{
public:
RadioCharacteristic()
: ProtobufCharacteristic("b56786c8-839a-44a1-b98e-a1724c4a0262",
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ, RadioConfig_fields,
&radioConfig)
{
}
void onRead(BLECharacteristic *c)
{
DEBUG_MSG("Reading radio config, sdsecs %u\n", radioConfig.preferences.sds_secs);
ProtobufCharacteristic::onRead(c);
}
void onWrite(BLECharacteristic *c)
{
DEBUG_MSG("Writing radio config\n");
ProtobufCharacteristic::onWrite(c);
service.reloadConfig();
}
};
// wrap our protobuf version with something that forces the service to reload the owner
class OwnerCharacteristic : public ProtobufCharacteristic
{
public:
OwnerCharacteristic()
: ProtobufCharacteristic("6ff1d8b6-e2de-41e3-8c0b-8fa384f64eb6",
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ, User_fields, &owner)
{
}
void onWrite(BLECharacteristic *c)
{
BLEKeepAliveCallbacks::onWrite(
c); // NOTE: We do not call the standard ProtobufCharacteristic superclass, because we want custom write behavior
static User o; // if the phone doesn't set ID we are careful to keep ours, we also always keep our macaddr
if (writeToDest(c, &o)) {
int changed = 0;
if (*o.long_name) {
changed |= strcmp(owner.long_name, o.long_name);
strcpy(owner.long_name, o.long_name);
}
if (*o.short_name) {
changed |= strcmp(owner.short_name, o.short_name);
strcpy(owner.short_name, o.short_name);
}
if (*o.id) {
changed |= strcmp(owner.id, o.id);
strcpy(owner.id, o.id);
}
if (changed) // If nothing really changed, don't broadcast on the network or write to flash
service.reloadOwner();
}
}
};
class ToRadioCharacteristic : public CallbackCharacteristic
{
public:
@@ -163,10 +82,9 @@ class ToRadioCharacteristic : public CallbackCharacteristic
void onWrite(BLECharacteristic *c)
{
BLEKeepAliveCallbacks::onWrite(c);
DEBUG_MSG("Got on write\n");
service.handleToRadio(c->getValue());
bluetoothPhoneAPI->handleToRadio(c->getData(), c->getValue().length());
}
};
@@ -179,32 +97,19 @@ class FromRadioCharacteristic : public CallbackCharacteristic
void onRead(BLECharacteristic *c)
{
BLEKeepAliveCallbacks::onRead(c);
MeshPacket *mp = service.getForPhone();
size_t numBytes = bluetoothPhoneAPI->getFromRadio(trBytes);
// Someone is going to read our value as soon as this callback returns. So fill it with the next message in the queue
// or make empty if the queue is empty
if (!mp) {
DEBUG_MSG("toPhone queue is empty\n");
c->setValue((uint8_t *)"", 0);
if (numBytes) {
c->setValue(trBytes, numBytes);
} else {
static FromRadio fRadio;
// Encapsulate as a FromRadio packet
memset(&fRadio, 0, sizeof(fRadio));
fRadio.which_variant = FromRadio_packet_tag;
fRadio.variant.packet = *mp;
size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), FromRadio_fields, &fRadio);
DEBUG_MSG("delivering toPhone packet to phone %d bytes\n", numbytes);
c->setValue(trBytes, numbytes);
service.releaseToPool(mp); // we just copied the bytes, so don't need this buffer anymore
c->setValue((uint8_t *)"", 0);
}
}
};
class FromNumCharacteristic : public CallbackCharacteristic, public Observer<uint32_t>
class FromNumCharacteristic : public CallbackCharacteristic
{
public:
FromNumCharacteristic()
@@ -212,54 +117,23 @@ class FromNumCharacteristic : public CallbackCharacteristic, public Observer<uin
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_NOTIFY)
{
observe(&service.fromNumChanged);
// observe(&service.fromNumChanged);
}
void onRead(BLECharacteristic *c)
{
BLEKeepAliveCallbacks::onRead(c);
DEBUG_MSG("FIXME implement fromnum read\n");
}
/// If the mesh service tells us fromNum has changed, tell the phone
virtual int onNotify(uint32_t newValue)
{
setValue(newValue);
notify();
return 0;
}
void onRead(BLECharacteristic *c) { DEBUG_MSG("FIXME implement fromnum read\n"); }
};
class MyNodeInfoCharacteristic : public ProtobufCharacteristic
{
public:
MyNodeInfoCharacteristic()
: ProtobufCharacteristic("ea9f3f82-8dc4-4733-9452-1f6da28892a2", BLECharacteristic::PROPERTY_READ, MyNodeInfo_fields,
&myNodeInfo)
{
}
void onRead(BLECharacteristic *c)
{
// update gps connection state
myNodeInfo.has_gps = gps.isConnected;
ProtobufCharacteristic::onRead(c);
myNodeInfo.error_code = 0; // The phone just read us, so throw it away
myNodeInfo.error_address = 0;
}
};
FromNumCharacteristic *meshFromNumCharacteristic;
BLEService *meshService;
/*
See bluetooth-api.md for documentation.
*/
BLEService *createMeshBluetoothService(BLEServer *server)
{
// Only create our phone API object once
if (!bluetoothPhoneAPI) {
bluetoothPhoneAPI = new BluetoothPhoneAPI();
bluetoothPhoneAPI->init();
}
// Create the BLE Service, we need more than the default of 15 handles
BLEService *service = server->createService(BLEUUID("6ba1b218-15a8-461f-9fa8-5dcae273eafd"), 30, 0);
@@ -269,10 +143,6 @@ BLEService *createMeshBluetoothService(BLEServer *server)
addWithDesc(service, meshFromNumCharacteristic, "fromRadio");
addWithDesc(service, new ToRadioCharacteristic, "toRadio");
addWithDesc(service, new FromRadioCharacteristic, "fromNum");
addWithDesc(service, new MyNodeInfoCharacteristic, "myNode");
addWithDesc(service, new RadioCharacteristic, "radio");
addWithDesc(service, new OwnerCharacteristic, "owner");
addWithDesc(service, new NodeInfoCharacteristic, "nodeinfo");
meshFromNumCharacteristic->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification

View File

@@ -3,6 +3,7 @@
#include "PowerFSM.h"
#include "configuration.h"
#include "main.h"
#include "power.h"
#include "target_specific.h"
bool bluetoothOn;
@@ -21,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
@@ -50,4 +51,194 @@ void setBluetoothEnable(bool on)
// heap_trace_dump();
}
}
}
void getMacAddr(uint8_t *dmac)
{
assert(esp_efuse_mac_get_default(dmac) == ESP_OK);
}
#ifdef TBEAM_V10
// FIXME. nasty hack cleanup how we load axp192
#undef AXP192_SLAVE_ADDRESS
#include "axp20x.h"
AXP20X_Class axp;
bool pmu_irq = false;
/// Reads power status to powerStatus singleton.
//
// TODO(girts): move this and other axp stuff to power.h/power.cpp.
void readPowerStatus()
{
powerStatus.haveBattery = axp.isBatteryConnect();
if (powerStatus.haveBattery) {
powerStatus.batteryVoltageMv = axp.getBattVoltage();
}
powerStatus.usb = axp.isVBUSPlug();
powerStatus.charging = axp.isChargeing();
}
#endif // TBEAM_V10
#ifdef AXP192_SLAVE_ADDRESS
/**
* Init the power manager chip
*
* axp192 power
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192
share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!) LDO1
30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can
not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
*/
void axp192Init()
{
if (axp192_found) {
if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS)) {
DEBUG_MSG("AXP192 Begin PASS\n");
// axp.setChgLEDMode(LED_BLINK_4HZ);
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("----------------------------------------\n");
axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio
axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power
axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON);
axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON);
axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON);
axp.setDCDC1Voltage(3300); // for the OLED power
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
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
// Not connected
//val = 0xfc;
//axp._writeByte(AXP202_VHTF_CHGSET, 1, &val); // Set temperature protection
//not used
//val = 0x46;
//axp._writeByte(AXP202_OFF_CTL, 1, &val); // enable bat detection
#endif
axp.debugCharging();
#ifdef PMU_IRQ
pinMode(PMU_IRQ, INPUT);
attachInterrupt(
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ |
AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ,
1);
axp.clearIRQ();
#endif
readPowerStatus();
} else {
DEBUG_MSG("AXP192 Begin FAIL\n");
}
} else {
DEBUG_MSG("AXP192 not found\n");
}
}
#endif
void esp32Setup()
{
uint32_t seed = esp_random();
DEBUG_MSG("Setting random seed %u\n", seed);
randomSeed(seed); // ESP docs say this is fairly random
#ifdef AXP192_SLAVE_ADDRESS
axp192Init();
#endif
}
#if 0
// Turn off for now
uint32_t axpDebugRead()
{
axp.debugCharging();
DEBUG_MSG("vbus current %f\n", axp.getVbusCurrent());
DEBUG_MSG("charge current %f\n", axp.getBattChargeCurrent());
DEBUG_MSG("bat voltage %f\n", axp.getBattVoltage());
DEBUG_MSG("batt pct %d\n", axp.getBattPercentage());
DEBUG_MSG("is battery connected %d\n", axp.isBatteryConnect());
DEBUG_MSG("is USB connected %d\n", axp.isVBUSPlug());
DEBUG_MSG("is charging %d\n", axp.isChargeing());
return 30 * 1000;
}
Periodic axpDebugOutput(axpDebugRead);
#endif
/**
* Per @spattinson
* MIN_BAT_MILLIVOLTS seems high. Typical 18650 are different chemistry to LiPo, even for LiPos that chart seems a bit off, other
* charts put 3690mV at about 30% for a lipo, for 18650 i think 10% remaining iis in the region of 3.2-3.3V. Reference 1st graph
* in [this test report](https://lygte-info.dk/review/batteries2012/Samsung%20INR18650-30Q%203000mAh%20%28Pink%29%20UK.html)
* looking at the red line - discharge at 0.2A - he gets a capacity of 2900mah, 90% of 2900 = 2610, that point in the graph looks
* to be a shade above 3.2V
*/
#define MIN_BAT_MILLIVOLTS 3250 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/
/// loop code specific to ESP32 targets
void esp32Loop()
{
loopBLE();
// for debug printing
// radio.radioIf.canSleep();
#ifdef PMU_IRQ
if (pmu_irq) {
pmu_irq = false;
axp.readIRQ();
DEBUG_MSG("pmu irq!\n");
if (axp.isChargingIRQ()) {
DEBUG_MSG("Battery start charging\n");
}
if (axp.isChargingDoneIRQ()) {
DEBUG_MSG("Battery fully charged\n");
}
if (axp.isVbusRemoveIRQ()) {
DEBUG_MSG("USB unplugged\n");
}
if (axp.isVbusPlugInIRQ()) {
DEBUG_MSG("USB plugged In\n");
}
if (axp.isBattPlugInIRQ()) {
DEBUG_MSG("Battery inserted\n");
}
if (axp.isBattRemoveIRQ()) {
DEBUG_MSG("Battery removed\n");
}
if (axp.isPEKShortPressIRQ()) {
DEBUG_MSG("PEK short button press\n");
}
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
}

81
src/gps/GPS.cpp Normal file
View File

@@ -0,0 +1,81 @@
#include "GPS.h"
#include "configuration.h"
#include "time.h"
#include <assert.h>
#include <sys/time.h>
#ifdef GPS_RX_PIN
HardwareSerial _serial_gps_real(GPS_SERIAL_NUM);
HardwareSerial &GPS::_serial_gps = _serial_gps_real;
#else
// Assume NRF52
HardwareSerial &GPS::_serial_gps = Serial1;
#endif
bool timeSetFromGPS; // We try to set our time from GPS each time we wake from sleep
GPS *gps;
// stuff that really should be in in the instance instead...
static uint32_t
timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time
static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock
void readFromRTC()
{
struct timeval tv; /* btw settimeofday() is helpfull here too*/
if (!gettimeofday(&tv, NULL)) {
uint32_t now = millis();
DEBUG_MSG("Read RTC time as %ld (cur millis %u) valid=%d\n", tv.tv_sec, now, timeSetFromGPS);
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
}
}
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
void perhapsSetRTC(const struct timeval *tv)
{
if (!timeSetFromGPS) {
timeSetFromGPS = true;
DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec);
#ifndef NO_ESP32
settimeofday(tv, NULL);
#else
DEBUG_MSG("ERROR TIME SETTING NOT IMPLEMENTED!\n");
#endif
readFromRTC();
}
}
void perhapsSetRTC(struct tm &t)
{
/* Convert to unix time
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
*/
time_t res = mktime(&t);
struct timeval tv;
tv.tv_sec = res;
tv.tv_usec = 0; // time.centisecond() * (10 / 1000);
// DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec);
if (t.tm_year < 0 || t.tm_year >= 300)
DEBUG_MSG("Ignoring invalid GPS time\n");
else
perhapsSetRTC(&tv);
}
#include <time.h>
uint32_t getTime()
{
return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
}
uint32_t getValidTime()
{
return timeSetFromGPS ? getTime() : 0;
}

55
src/gps/GPS.h Normal file
View File

@@ -0,0 +1,55 @@
#pragma once
#include "Observer.h"
#include "PeriodicTask.h"
#include "sys/time.h"
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
void perhapsSetRTC(const struct timeval *tv);
void perhapsSetRTC(struct tm &t);
/// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero
uint32_t getTime();
/// Return time since 1970 in secs. If we don't have a GPS lock return zero
uint32_t getValidTime();
void readFromRTC();
/**
* A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading)
*
* When new data is available it will notify observers.
*/
class GPS : public Observable<void *>
{
protected:
bool hasValidLocation = false; // default to false, until we complete our first read
static HardwareSerial &_serial_gps;
public:
int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double
int32_t altitude = 0;
bool isConnected = false; // Do we have a GPS we are talking to
virtual ~GPS() {}
/**
* Returns true if we succeeded
*/
virtual bool setup() { return true; }
/// A loop callback for subclasses that need it. FIXME, instead just block on serial reads
virtual void loop() {}
/// Returns ture if we have acquired GPS lock.
bool hasLock() const { return hasValidLocation; }
/**
* Restart our lock attempt - try to get and broadcast a GPS reading ASAP
* called after the CPU wakes from light-sleep state */
virtual void startLock() {}
};
extern GPS *gps;

65
src/gps/NEMAGPS.cpp Normal file
View File

@@ -0,0 +1,65 @@
#include "NEMAGPS.h"
#include "configuration.h"
static int32_t toDegInt(RawDegrees d)
{
int32_t degMult = 10000000; // 1e7
int32_t r = d.deg * degMult + d.billionths / 100;
if (d.negative)
r *= -1;
return r;
}
void NEMAGPS::loop()
{
while (_serial_gps.available() > 0) {
int c = _serial_gps.read();
// Serial.write(c);
reader.encode(c);
}
uint32_t now = millis();
if ((now - lastUpdateMsec) > 20 * 1000) { // Ugly hack for now - limit update checks to once every 20 secs (but still consume
// serial chars at whatever rate)
lastUpdateMsec = now;
auto ti = reader.time;
auto d = reader.date;
if (ti.isUpdated() && ti.isValid() && d.isValid()) {
/* Convert to unix time
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
*/
struct tm t;
t.tm_sec = ti.second();
t.tm_min = ti.minute();
t.tm_hour = ti.hour();
t.tm_mday = d.day();
t.tm_mon = d.month() - 1;
t.tm_year = d.year() - 1900;
t.tm_isdst = false;
perhapsSetRTC(t);
isConnected = true; // we seem to have a real GPS (but not necessarily a lock)
}
if (reader.location.isUpdated()) {
if (reader.altitude.isValid())
altitude = reader.altitude.meters();
if (reader.location.isValid()) {
auto loc = reader.location.value();
latitude = toDegInt(loc.lat);
longitude = toDegInt(loc.lng);
}
// expect gps pos lat=37.520825, lon=-122.309162, alt=158
DEBUG_MSG("new NEMA GPS pos lat=%f, lon=%f, alt=%d\n", latitude * 1e-7, longitude * 1e-7, altitude);
hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0
if (hasValidLocation)
notifyObservers(NULL);
}
}
}

21
src/gps/NEMAGPS.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include "GPS.h"
#include "Observer.h"
#include "PeriodicTask.h"
#include "TinyGPS++.h"
/**
* A gps class thatreads from a NEMA GPS stream (and FIXME - eventually keeps the gps powered down except when reading)
*
* When new data is available it will notify observers.
*/
class NEMAGPS : public GPS
{
TinyGPSPlus reader;
uint32_t lastUpdateMsec = 0;
public:
virtual void loop();
};

139
src/gps/UBloxGPS.cpp Normal file
View File

@@ -0,0 +1,139 @@
#include "UBloxGPS.h"
#include "sleep.h"
#include <assert.h>
UBloxGPS::UBloxGPS() : PeriodicTask()
{
notifySleepObserver.observe(&notifySleep);
}
bool UBloxGPS::setup()
{
#ifdef GPS_RX_PIN
_serial_gps.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
#else
_serial_gps.begin(GPS_BAUDRATE);
#endif
// _serial_gps.setRxBufferSize(1024); // the default is 256
// ublox.enableDebugging(Serial);
// note: the lib's implementation has the wrong docs for what the return val is
// it is not a bool, it returns zero for success
isConnected = ublox.begin(_serial_gps);
// try a second time, the ublox lib serial parsing is buggy?
if (!isConnected)
isConnected = ublox.begin(_serial_gps);
if (isConnected) {
DEBUG_MSG("Connected to UBLOX GPS successfully\n");
bool factoryReset = false;
bool ok;
if (factoryReset) {
// It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have
// GPS_TX connected)
ublox.factoryReset();
delay(3000);
isConnected = ublox.begin(_serial_gps);
DEBUG_MSG("Factory reset success=%d\n", isConnected);
ok = ublox.saveConfiguration(3000);
assert(ok);
return false;
} else {
ok = ublox.setUART1Output(COM_TYPE_UBX, 500); // Use native API
assert(ok);
ok = ublox.setNavigationFrequency(1, 500); // Produce 4x/sec to keep the amount of time we stall in getPVT low
assert(ok);
// ok = ublox.setAutoPVT(false); // Not implemented on NEO-6M
// assert(ok);
// ok = ublox.setDynamicModel(DYN_MODEL_BIKE); // probably PEDESTRIAN but just in case assume bike speeds
// assert(ok);
ok = ublox.powerSaveMode(true, 2000); // use power save mode, the default timeout (1100ms seems a bit too tight)
assert(ok);
}
ok = ublox.saveConfiguration(3000);
assert(ok);
PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device
return true;
} else {
return false;
}
}
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
int UBloxGPS::prepareSleep(void *unused)
{
if (isConnected)
ublox.powerOff();
return 0;
}
void UBloxGPS::doTask()
{
uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix
assert(isConnected);
// Consume all characters that have arrived
// getPVT automatically calls checkUblox
ublox.checkUblox(); // See if new data is available. Process bytes as they come in.
// If we don't have a fix (a quick check), don't try waiting for a solution)
// 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("sec %d\n", ublox.getSecond());
// DEBUG_MSG("lat %d\n", ublox.getLatitude());
// any fix that has time
if (ublox.getT()) {
/* Convert to unix time
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
*/
struct tm t;
t.tm_sec = ublox.getSecond();
t.tm_min = ublox.getMinute();
t.tm_hour = ublox.getHour();
t.tm_mday = ublox.getDay();
t.tm_mon = ublox.getMonth() - 1;
t.tm_year = ublox.getYear() - 1900;
t.tm_isdst = false;
perhapsSetRTC(t);
}
if ((fixtype >= 3 && fixtype <= 4) && ublox.getP()) // rd fixes only
{
// we only notify if position has changed
latitude = ublox.getLatitude();
longitude = ublox.getLongitude();
altitude = ublox.getAltitude() / 1000; // in mm convert to meters
DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d\n", latitude * 1e-7, longitude * 1e-7, altitude);
hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0
if (hasValidLocation) {
wantNewLocation = false;
notifyObservers(NULL);
// ublox.powerOff();
}
} else // we didn't get a location update, go back to sleep and hope the characters show up
wantNewLocation = true;
// Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s until we have something over
// the serial
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000);
}
void UBloxGPS::startLock()
{
DEBUG_MSG("Looking for GPS lock\n");
wantNewLocation = true;
setPeriod(1);
}

41
src/gps/UBloxGPS.h Normal file
View File

@@ -0,0 +1,41 @@
#pragma once
#include "GPS.h"
#include "Observer.h"
#include "PeriodicTask.h"
#include "SparkFun_Ublox_Arduino_Library.h"
/**
* A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading)
*
* When new data is available it will notify observers.
*/
class UBloxGPS : public GPS, public PeriodicTask
{
SFE_UBLOX_GPS ublox;
bool wantNewLocation = true;
CallbackObserver<UBloxGPS, void *> notifySleepObserver = CallbackObserver<UBloxGPS, void *>(this, &UBloxGPS::prepareSleep);
public:
UBloxGPS();
/**
* Returns true if we succeeded
*/
virtual bool setup();
virtual void doTask();
/**
* Restart our lock attempt - try to get and broadcast a GPS reading ASAP
* called after the CPU wakes from light-sleep state */
virtual void startLock();
private:
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
/// always returns 0 to indicate okay to sleep
int prepareSleep(void *unused);
};

View File

@@ -21,17 +21,19 @@
*/
#include "GPS.h"
#include "MeshRadio.h"
#include "MeshService.h"
#include "NEMAGPS.h"
#include "NodeDB.h"
#include "Periodic.h"
#include "PowerFSM.h"
#include "UBloxGPS.h"
#include "configuration.h"
#include "error.h"
#include "power.h"
// #include "rom/rtc.h"
#include "FloodingRouter.h"
#include "DSRRouter.h"
#include "main.h"
#include "screen.h"
#include "sleep.h"
#include <Wire.h>
@@ -41,27 +43,16 @@
#include "BluetoothUtil.h"
#endif
#ifdef TBEAM_V10
#include "axp20x.h"
AXP20X_Class axp;
bool pmu_irq = false;
#endif
// Global Screen singleton
#ifdef I2C_SDA
meshtastic::Screen screen(SSD1306_ADDRESS, I2C_SDA, I2C_SCL);
#else
// Fake values for pins to keep build happy, we won't ever initialize it.
meshtastic::Screen screen(SSD1306_ADDRESS, 0, 0);
#endif
// We always create a screen object, but we only init it if we find the hardware
meshtastic::Screen screen(SSD1306_ADDRESS);
// Global power status singleton
static meshtastic::PowerStatus powerStatus;
meshtastic::PowerStatus powerStatus;
bool ssd1306_found;
bool axp192_found;
FloodingRouter realRouter;
DSRRouter realRouter;
Router &router = realRouter; // Users of router don't care what sort of subclass implements that API
// -----------------------------------------------------------------------------
@@ -84,7 +75,7 @@ void scanI2Cdevice(void)
ssd1306_found = true;
DEBUG_MSG("ssd1306 display found\n");
}
#ifdef TBEAM_V10
#ifdef AXP192_SLAVE_ADDRESS
if (addr == AXP192_SLAVE_ADDRESS) {
axp192_found = true;
DEBUG_MSG("axp192 PMU found\n");
@@ -100,117 +91,6 @@ void scanI2Cdevice(void)
DEBUG_MSG("done\n");
}
#ifdef TBEAM_V10
/// Reads power status to powerStatus singleton.
//
// TODO(girts): move this and other axp stuff to power.h/power.cpp.
void readPowerStatus()
{
powerStatus.haveBattery = axp.isBatteryConnect();
if (powerStatus.haveBattery) {
powerStatus.batteryVoltageMv = axp.getBattVoltage();
}
powerStatus.usb = axp.isVBUSPlug();
powerStatus.charging = axp.isChargeing();
}
#endif // TBEAM_V10
/**
* Init the power manager chip
*
* axp192 power
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192
share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!) LDO1
30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can
not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
*/
void axp192Init()
{
#ifdef TBEAM_V10
if (axp192_found) {
if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS)) {
DEBUG_MSG("AXP192 Begin PASS\n");
// axp.setChgLEDMode(LED_BLINK_4HZ);
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("----------------------------------------\n");
axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio
axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power
axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON);
axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON);
axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON);
axp.setDCDC1Voltage(3300); // for the OLED power
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
#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;
//axp._writeByte(AXP202_VHTF_CHGSET, 1, &val); // Set temperature protection
//not used
//val = 0x46;
//axp._writeByte(AXP202_OFF_CTL, 1, &val); // enable bat detection
#endif
axp.debugCharging();
#ifdef PMU_IRQ
pinMode(PMU_IRQ, INPUT);
attachInterrupt(
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ |
AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ,
1);
axp.clearIRQ();
#endif
readPowerStatus();
} else {
DEBUG_MSG("AXP192 Begin FAIL\n");
}
} else {
DEBUG_MSG("AXP192 not found\n");
}
#endif
}
void getMacAddr(uint8_t *dmac)
{
#ifndef NO_ESP32
assert(esp_efuse_mac_get_default(dmac) == ESP_OK);
#else
dmac[0] = 0xde;
dmac[1] = 0xad;
dmac[2] = 0xbe;
dmac[3] = 0xef;
dmac[4] = 0x01;
dmac[5] = 0x02; // FIXME, macaddr stuff needed for NRF52
#endif
}
const char *getDeviceName()
{
uint8_t dmac[6];
@@ -223,15 +103,35 @@ const char *getDeviceName()
return name;
}
static MeshRadio *radio = NULL;
static uint32_t ledBlinker()
{
static bool ledOn;
ledOn ^= 1;
#include "Router.h"
setLed(ledOn);
// have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that
return powerStatus.charging ? 1000 : (ledOn ? 2 : 1000);
}
Periodic ledPeriodic(ledBlinker);
#include "RF95Interface.h"
#include "SX1262Interface.h"
#ifdef NO_ESP32
#include "variant.h"
#endif
void setup()
{
#ifdef USE_SEGGER
SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_TRIM);
#endif
// Debug
#ifdef DEBUG_PORT
DEBUG_PORT.begin(SERIAL_BAUD);
DEBUG_PORT.init(); // Set serial baud rate and init our mesh console
#endif
initDeepSleep();
@@ -248,8 +148,10 @@ void setup()
#ifdef I2C_SDA
Wire.begin(I2C_SDA, I2C_SCL);
scanI2Cdevice();
#else
Wire.begin();
#endif
scanI2Cdevice();
// Buttons & LED
#ifdef BUTTON_PIN
@@ -261,36 +163,80 @@ void setup()
digitalWrite(LED_PIN, 1 ^ LED_INVERTED); // turn on for now
#endif
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
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER)
ssd1306_found = false; // forget we even have the hardware
esp32Setup();
#endif
#ifdef NRF52_SERIES
nrf52Setup();
#endif
// Initialize the screen first so we can show the logo while we start up everything else.
if (ssd1306_found)
screen.setup();
axp192Init();
screen.print("Started...\n");
// Init GPS
gps.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()) {
// Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just
// assume NEMA at 9600 baud.
DEBUG_MSG("ERROR: No UBLOX GPS found, hoping that NEMA might work\n");
delete gps;
gps = new NEMAGPS();
gps->setup();
}
#else
gps = new NEMAGPS();
gps->setup();
#endif
service.init();
#ifndef NO_ESP32
// MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
radio = new MeshRadio();
router.addInterface(&radio->radioIf);
#ifdef SX1262_ANT_SW
// make analog PA vs not PA switch on SX1262 eval board work properly
pinMode(SX1262_ANT_SW, OUTPUT);
digitalWrite(SX1262_ANT_SW, 1);
#endif
if (radio && !radio->init())
// Init our SPI controller
#ifdef NRF52_SERIES
SPI.begin();
#else
// ESP32
SPI.begin(SCK_GPIO, MISO_GPIO, MOSI_GPIO, NSS_GPIO);
SPI.setFrequency(4000000);
#endif
// MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
RadioInterface *rIf =
#if defined(RF95_IRQ_GPIO)
// new CustomRF95(); old Radiohead based driver
new RF95Interface(NSS_GPIO, RF95_IRQ_GPIO, RESET_GPIO, SPI);
#elif defined(SX1262_CS)
new SX1262Interface(SX1262_CS, SX1262_DIO1, SX1262_RESET, SX1262_BUSY, SPI);
#else
new SimRadio();
#endif
if (!rIf->init())
recordCriticalError(ErrNoRadio);
else
router.addInterface(rIf);
// This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values
PowerFSM_setup(); // we will transition to ON in a couple of seconds, FIXME, only do this for cold boots, not waking from SDS
@@ -299,19 +245,6 @@ void setup()
setCPUFast(false); // 80MHz is fine for our slow peripherals
}
uint32_t ledBlinker()
{
static bool ledOn;
ledOn ^= 1;
setLed(ledOn);
// have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that
return powerStatus.charging ? 1000 : (ledOn ? 2 : 1000);
}
Periodic ledPeriodic(ledBlinker);
#if 0
// Turn off for now
@@ -330,60 +263,28 @@ uint32_t axpDebugRead()
}
Periodic axpDebugOutput(axpDebugRead);
axpDebugOutput.setup();
#endif
void loop()
{
uint32_t msecstosleep = 1000 * 30; // How long can we sleep before we again need to service the main loop?
gps.loop();
gps->loop(); // FIXME, remove from main, instead block on read
router.loop();
powerFSM.run_machine();
service.loop();
ledPeriodic.loop();
periodicScheduler.loop();
// axpDebugOutput.loop();
#ifndef NO_ESP32
loopBLE();
#ifdef DEBUG_PORT
DEBUG_PORT.loop(); // Send/receive protobufs over the serial port
#endif
// for debug printing
// radio.radioIf.canSleep();
#ifdef PMU_IRQ
if (pmu_irq) {
pmu_irq = false;
axp.readIRQ();
DEBUG_MSG("pmu irq!\n");
if (axp.isChargingIRQ()) {
DEBUG_MSG("Battery start charging\n");
}
if (axp.isChargingDoneIRQ()) {
DEBUG_MSG("Battery fully charged\n");
}
if (axp.isVbusRemoveIRQ()) {
DEBUG_MSG("USB unplugged\n");
}
if (axp.isVbusPlugInIRQ()) {
DEBUG_MSG("USB plugged In\n");
}
if (axp.isBattPlugInIRQ()) {
DEBUG_MSG("Battery inserted\n");
}
if (axp.isBattRemoveIRQ()) {
DEBUG_MSG("Battery removed\n");
}
if (axp.isPEKShortPressIRQ()) {
DEBUG_MSG("PEK short button press\n");
}
readPowerStatus();
axp.clearIRQ();
}
#endif // T_BEAM_V10
#ifndef NO_ESP32
esp32Loop();
#endif
#ifdef BUTTON_PIN
// if user presses button for more than 3 secs, discard our network prefs and reboot (FIXME, use a debounce lib instead of
@@ -418,8 +319,7 @@ void loop()
screen.debug()->setChannelNameStatus(channelSettings.name);
screen.debug()->setPowerStatus(powerStatus);
// TODO(#4): use something based on hdop to show GPS "signal" strength.
screen.debug()->setGPSStatus(gps.hasLock() ? "ok" : ":(");
screen.loop();
screen.debug()->setGPSStatus(gps->hasLock() ? "good" : "bad");
// No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in)
// i.e. don't just keep spinning in loop as fast as we can.

View File

@@ -12,3 +12,7 @@ extern meshtastic::Screen screen;
// Return a human readable string of the form "Meshtastic_ab13"
const char *getDeviceName();
void getMacAddr(uint8_t *dmac);
void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop();

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

52
src/mesh/CryptoEngine.h Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include <Arduino.h>
/**
* see docs/software/crypto.md for details.
*
*/
#define MAX_BLOCKSIZE 256
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;

245
src/mesh/DSRRouter.cpp Normal file
View File

@@ -0,0 +1,245 @@
#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.
*/
ErrorCode DSRRouter::send(MeshPacket *p)
{
// We only consider multihop routing packets (i.e. those with dest set)
if (p->decoded.dest) {
// add an entry for this pending message
auto pending = startRetransmission(p);
// FIXME - when acks come in for this packet, we should _not_ delete the record unless the ack was from
// the final dest. We need to keep that record around until FIXME
// Also we should not retransmit multihop entries in that table at all
// If we have an entry in our routing tables, just send it, otherwise start a route discovery
NodeNum nextHop = getNextHop(p->decoded.dest);
if (nextHop) {
sendNextHop(nextHop, p); // start a reliable single hop send
} else {
pending->wantRoute = true;
// start discovery, but only if we don't already a discovery in progress for that node number
startDiscovery(p->decoded.dest);
}
return ERRNO_OK;
} else
return ReliableRouter::send(p);
}
void DSRRouter::sniffReceived(const MeshPacket *p)
{
// Learn 0 hop routes by just hearing any adjacent nodes
// But treat broadcasts carefully, because when flood broadcasts go out they keep the same original "from". So we want to
// ignore rebroadcasts.
// this will also add records for any ACKs we receive for our messages
if (p->to != NODENUM_BROADCAST || p->hop_limit != HOP_RELIABLE) {
addRoute(p->from, p->from, 0); // We are adjacent with zero hops
}
switch (p->decoded.which_payload) {
case SubPacket_route_request_tag:
// Handle route discovery packets (will be a broadcast message)
// FIXME - always start request with the senders nodenum
if (weAreInRoute(p->decoded.route_request)) {
DEBUG_MSG("Ignoring a route request that contains us\n");
} else {
updateRoutes(p->decoded.route_request,
true); // Update our routing tables based on the route that came in so far on this request
if (p->decoded.dest == getNodeNum()) {
// They were looking for us, send back a route reply (the sender address will be first in the list)
sendRouteReply(p->decoded.route_request);
} else {
// They were looking for someone else, forward it along (as a zero hop broadcast)
NodeNum nextHop = getNextHop(p->decoded.dest);
if (nextHop) {
// in our route cache, reply to the requester (the sender address will be first in the list)
sendRouteReply(p->decoded.route_request, nextHop);
} else {
// Not in our route cache, rebroadcast on their behalf (after adding ourselves to the request route)
resendRouteRequest(p);
}
}
}
break;
case SubPacket_route_reply_tag:
updateRoutes(p->decoded.route_reply, false);
// FIXME, if any of our current pending packets were waiting for this route, send them (and leave them as regular pending
// packets until ack arrives)
// FIXME, if we don't get a route reply at all (or a route error), timeout and generate a routeerror TIMEOUT on our own...
break;
case SubPacket_route_error_tag:
removeRoute(p->decoded.dest);
// FIXME: if any pending packets were waiting on this route, delete them
break;
default:
break;
}
// We simply ignore ACKs - because ReliableRouter will delete the pending packet for us
// Handle regular packets
if (p->to == getNodeNum()) { // Destined for us (at least for this hop)
// We need to route this packet to some other node
if (p->decoded.dest && p->decoded.dest != p->to) {
// if we have a route out, resend the packet to the next hop, otherwise return RouteError no-route available
NodeNum nextHop = getNextHop(p->decoded.dest);
if (nextHop) {
sendNextHop(nextHop, p); // start a reliable single hop send
} else {
// We don't have a route out
assert(p->decoded.source); // I think this is guaranteed by now
// FIXME - what if the current packet _is_ a route error packet?
sendRouteError(p, RouteError_NO_ROUTE);
}
// FIXME, stop local processing of this packet
}
// handle naks - convert them to route error packets
// All naks are generated locally, because we failed resending the packet too many times
PacketId nakId = p->decoded.which_ack == SubPacket_fail_id_tag ? p->decoded.ack.fail_id : 0;
if (nakId) {
auto pending = findPendingPacket(p->to, nakId);
if (pending && pending->packet->decoded.source) { // if source not set, this was not a multihop packet, just ignore
removeRoute(pending->packet->decoded.dest); // We no longer have a route to the specified node
sendRouteError(p, RouteError_GOT_NAK);
}
}
}
return ReliableRouter::sniffReceived(p);
}
/**
* Does our node appear in the specified route
*/
bool DSRRouter::weAreInRoute(const RouteDiscovery &route)
{
return true; // FIXME
}
/**
* Given a DSR route, use that route to update our DB of possible routes
*
* Note: routes are always listed in the same order - from sender to receipient (i.e. route_replies also use this some order)
*
* @param isRequest is true if we are looking at a route request, else we are looking at a reply
**/
void DSRRouter::updateRoutes(const RouteDiscovery &route, bool isRequest)
{
DEBUG_MSG("FIXME not implemented");
}
/**
* send back a route reply (the sender address will be first in the list)
*/
void DSRRouter::sendRouteReply(const RouteDiscovery &route, NodeNum toAppend)
{
DEBUG_MSG("FIXME not implemented");
}
/**
* Given a nodenum return the next node we should forward to if we want to reach that node.
*
* @return 0 if no route found
*/
NodeNum DSRRouter::getNextHop(NodeNum dest)
{
DEBUG_MSG("FIXME not implemented");
return 0;
}
/** Not in our route cache, rebroadcast on their behalf (after adding ourselves to the request route)
*
* We will bump down hop_limit in this call.
*/
void DSRRouter::resendRouteRequest(const MeshPacket *p)
{
DEBUG_MSG("FIXME not implemented");
}
/**
* Record that forwarder can reach dest for us, but they will need numHops to get there.
* If our routing tables already have something that can reach that node in fewer hops we will keep the existing route
* instead.
*/
void DSRRouter::addRoute(NodeNum dest, NodeNum forwarder, uint8_t numHops)
{
DEBUG_MSG("FIXME not implemented");
}
/**
* Record that we no longer have a route to the dest
*/
void DSRRouter::removeRoute(NodeNum dest)
{
DEBUG_MSG("FIXME not implemented");
}
/**
* Forward the specified packet to the specified node
*/
void DSRRouter::sendNextHop(NodeNum n, const MeshPacket *p)
{
DEBUG_MSG("FIXME not implemented");
}
/**
* Send a route error packet towards whoever originally sent this message
*/
void DSRRouter::sendRouteError(const MeshPacket *p, RouteError err)
{
DEBUG_MSG("FIXME not implemented");
}
/** make a copy of p, start discovery, but only if we don't
* already a discovery in progress for that node number. Caller has already scheduled this message for retransmission
* when the discovery is complete.
*/
void DSRRouter::startDiscovery(NodeNum dest)
{
DEBUG_MSG("FIXME not implemented");
}

80
src/mesh/DSRRouter.h Normal file
View File

@@ -0,0 +1,80 @@
#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);
/**
* Send a packet on a suitable interface. This routine will
* later free() the packet to pool. This routine is not allowed to stall.
* If the txmit queue is full it might return an error
*/
virtual ErrorCode send(MeshPacket *p);
private:
/**
* Does our node appear in the specified route
*/
bool weAreInRoute(const RouteDiscovery &route);
/**
* Given a DSR route, use that route to update our DB of possible routes
*
* Note: routes are always listed in the same order - from sender to receipient (i.e. route_replies also use this some order)
*
* @param isRequest is true if we are looking at a route request, else we are looking at a reply
**/
void updateRoutes(const RouteDiscovery &route, bool isRequest);
/**
* 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)
*
* We will bump down hop_limit in this call.
*/
void resendRouteRequest(const MeshPacket *p);
/**
* Record that forwarder can reach dest for us, but they will need numHops to get there.
* If our routing tables already have something that can reach that node in fewer hops we will keep the existing route
* instead.
*/
void addRoute(NodeNum dest, NodeNum forwarder, uint8_t numHops);
/**
* Record that we no longer have a route to the dest
*/
void removeRoute(NodeNum dest);
/**
* Forward the specified packet to the specified node
*/
void sendNextHop(NodeNum n, const MeshPacket *p);
/**
* Send a route error packet towards whoever originally sent this message
*/
void sendRouteError(const MeshPacket *p, RouteError err);
/** make a copy of p, start discovery, but only if we don't
* already a discovery in progress for that node number. Caller has already scheduled this message for retransmission
* when the discovery is complete.
*/
void startDiscovery(NodeNum dest);
};

View File

@@ -0,0 +1,54 @@
#include "FloodingRouter.h"
#include "configuration.h"
#include "mesh-pb-constants.h"
FloodingRouter::FloodingRouter() {}
/**
* 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
*/
ErrorCode FloodingRouter::send(MeshPacket *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);
}
bool FloodingRouter::shouldFilterReceived(const MeshPacket *p)
{
if (wasSeenRecently(p)) { // Note: this will also add a recent packet record
DEBUG_MSG("Ignoring incoming msg, because we've already seen it: fr=0x%x,to=0x%x,id=%d,hop_limit=%d\n", p->from, p->to,
p->id, p->hop_limit);
return true;
}
return Router::shouldFilterReceived(p);
}
void FloodingRouter::sniffReceived(const MeshPacket *p)
{
// If a broadcast, possibly _also_ send copies out into the mesh.
// (FIXME, do something smarter than naive flooding here)
if (p->to == NODENUM_BROADCAST && p->hop_limit > 0) {
if (p->id != 0) {
MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
tosend->hop_limit--; // bump down the hop count
DEBUG_MSG("Rebroadcasting received floodmsg to neighbors, fr=0x%x,to=0x%x,id=%d,hop_limit=%d\n", p->from, p->to,
p->id, tosend->hop_limit);
// Note: we are careful to resend using the original senders node id
// We are careful not to call our hooked version of send() - because we don't want to check this again
Router::send(tosend);
} else {
DEBUG_MSG("Ignoring a simple (0 id) broadcast\n");
}
}
// handle the packet as normal
Router::sniffReceived(p);
}

View File

@@ -1,16 +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)
@@ -35,11 +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
class FloodingRouter : public Router, protected PacketHistory
{
private:
std::vector<BroadcastRecord> recentBroadcasts;
public:
/**
* Constructor
@@ -56,17 +46,15 @@ class FloodingRouter : public Router
protected:
/**
* Called from loop()
* Handle any packet that is received by an interface on this node.
* Note: some packets may merely being passed through this node and will be forwarded elsewhere.
* Should this incoming filter be dropped?
*
* Note: this method will free the provided packet
* Called immedately on receiption, before any further processing.
* @return true to abandon the packet
*/
virtual void handleReceived(MeshPacket *p);
virtual bool shouldFilterReceived(const MeshPacket *p);
private:
/**
* Update recentBroadcasts and return true if we have already seen this packet
* Look for broadcasts we need to rebroadcast
*/
bool wasSeenRecently(const MeshPacket *p);
virtual void sniffReceived(const MeshPacket *p);
};

View File

@@ -1,9 +1,7 @@
#pragma once
#include "CustomRF95.h"
#include "MemoryPool.h"
#include "MeshTypes.h"
#include "Observer.h"
#include "PointerQueue.h"
#include "configuration.h"
#include "mesh.pb.h"
@@ -61,47 +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)
*/
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.
#ifdef RF95_IRQ_GPIO
CustomRF95
radioIf; // the raw radio interface - for now I'm leaving public - because this class is shrinking to be almost nothing
#else
SimRadio radioIf;
#endif
/** 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();
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;
}
};

View File

@@ -46,18 +46,15 @@ MeshService service;
#include "Router.h"
#define NUM_PACKET_ID 255 // 0 is consider invalid
/// Generate a unique packet id
// FIXME, move this someplace better
PacketId generatePacketId()
static uint32_t sendOwnerCb()
{
static uint32_t i;
service.sendOurOwner();
i++;
return (i % NUM_PACKET_ID) + 1; // return number between 1 and 255
return radioConfig.preferences.send_owner_interval * radioConfig.preferences.position_broadcast_secs * 1000;
}
static Periodic sendOwnerPeriod(sendOwnerCb);
MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE)
{
// assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro
@@ -65,19 +62,20 @@ MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE)
void MeshService::init()
{
sendOwnerPeriod.setup();
nodeDB.init();
gpsObserver.observe(&gps);
gpsObserver.observe(gps);
packetReceivedObserver.observe(&router.notifyPacketReceived);
}
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);
@@ -88,12 +86,14 @@ void MeshService::sendOurOwner(NodeNum dest, bool wantReplies)
const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp)
{
bool wasBroadcast = mp->to == NODENUM_BROADCAST;
bool isCollision = mp->from == myNodeInfo.my_node_num;
// we win if we have a lower macaddr
bool weWin = memcmp(&owner.macaddr, &mp->payload.user.macaddr, sizeof(owner.macaddr)) < 0;
// Disable this collision testing if we use 32 bit nodenums
bool isCollision = (sizeof(NodeNum) == 1) && (mp->from == myNodeInfo.my_node_num);
if (isCollision) {
// we win if we have a lower macaddr
bool weWin = memcmp(&owner.macaddr, &mp->decoded.user.macaddr, sizeof(owner.macaddr)) < 0;
if (weWin) {
DEBUG_MSG("NOTE! Received a nodenum collision and we are vetoing\n");
@@ -117,7 +117,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());
}
@@ -126,17 +126,17 @@ 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;
gps.perhapsSetRTC(&tv);
perhapsSetRTC(&tv);
}
} else {
DEBUG_MSG("Ignoring incoming packet - not a position\n");
@@ -148,13 +148,13 @@ int MeshService::handleFromRadio(const MeshPacket *mp)
powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping
// If it is a position packet, perhaps set our clock (if we don't have a GPS of our own, otherwise wait for that to work)
if (!gps.isConnected)
if (!gps->isConnected)
handleIncomingPosition(mp);
else {
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);
}
@@ -175,7 +175,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");
@@ -184,15 +184,6 @@ int MeshService::handleFromRadio(const MeshPacket *mp)
return 0;
}
uint32_t sendOwnerCb()
{
service.sendOurOwner();
return radioConfig.preferences.send_owner_interval * radioConfig.preferences.position_broadcast_secs * 1000;
}
Periodic sendOwnerPeriod(sendOwnerCb);
/// Do idle processing (mostly processing messages which have been queued from the radio)
void MeshService::loop()
{
@@ -200,9 +191,6 @@ void MeshService::loop()
fromNumChanged.notifyObservers(fromNum);
oldFromNum = fromNum;
}
// occasionally send our owner info
sendOwnerPeriod.loop();
}
/// The radioConfig object just changed, call this to force the hw to change to the new settings
@@ -214,46 +202,34 @@ void MeshService::reloadConfig()
nodeDB.saveToDisk();
}
/// Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh)
void MeshService::handleToRadio(std::string s)
/**
* Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh)
* Called by PhoneAPI.handleToRadio. Note: p is a scratch buffer, this function is allowed to write to it but it can not keep a
* reference
*/
void MeshService::handleToRadio(MeshPacket &p)
{
static ToRadio r; // this is a static scratch object, any data must be copied elsewhere before returning
handleIncomingPosition(&p); // If it is a position packet, perhaps set our clock
if (pb_decode_from_bytes((const uint8_t *)s.c_str(), s.length(), ToRadio_fields, &r)) {
switch (r.which_variant) {
case ToRadio_packet_tag: {
// If our phone is sending a position, see if we can use it to set our RTC
MeshPacket &p = r.variant.packet;
handleIncomingPosition(&p); // If it is a position packet, perhaps set our clock
if (p.from == 0) // If the phone didn't set a sending node ID, use ours
p.from = nodeDB.getNodeNum();
if (p.from == 0) // If the phone didn't set a sending node ID, use ours
p.from = nodeDB.getNodeNum();
if (p.id == 0)
p.id = generatePacketId(); // If the phone didn't supply one, then pick one
if (p.id == 0)
p.id = generatePacketId(); // If the phone didn't supply one, then pick one
p.rx_time = getValidTime(); // Record the time the packet arrived from the phone
// (so we update our nodedb for the local node)
p.rx_time = gps.getValidTime(); // Record the time the packet arrived from the phone
// (so we update our nodedb for the local node)
// Send the packet into the mesh
// Send the packet into the mesh
sendToMesh(packetPool.allocCopy(p));
sendToMesh(packetPool.allocCopy(p));
bool loopback = false; // if true send any packet the phone sends back itself (for testing)
if (loopback) {
// no need to copy anymore because handle from radio assumes it should _not_ delete
// packetPool.allocCopy(r.variant.packet);
handleFromRadio(&p);
// handleFromRadio will tell the phone a new packet arrived
}
break;
}
default:
DEBUG_MSG("Error: unexpected ToRadio variant\n");
break;
}
} else {
DEBUG_MSG("Error: ignoring malformed toradio\n");
bool loopback = false; // if true send any packet the phone sends back itself (for testing)
if (loopback) {
// no need to copy anymore because handle from radio assumes it should _not_ delete
// packetPool.allocCopy(r.variant.packet);
handleFromRadio(&p);
// handleFromRadio will tell the phone a new packet arrived
}
}
@@ -264,37 +240,16 @@ 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 (!gps.isConnected) {
DEBUG_MSG("Stripping time %u from position send\n", p->payload.position.time);
p->payload.position.time = 0;
if (p->which_payload == MeshPacket_decoded_tag && p->decoded.which_payload == SubPacket_position_tag) {
if (!gps->isConnected) {
DEBUG_MSG("Stripping time %u from position send\n", p->decoded.position.time);
p->decoded.position.time = 0;
} 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");
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 = gps.getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp
return p;
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
router.sendLocal(p);
}
void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
@@ -316,31 +271,31 @@ 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 = gps.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);
}
int MeshService::onGPSChanged(void *unused)
{
DEBUG_MSG("got gps notify\n");
// 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)
pos.altitude = gps.altitude;
pos.latitude = gps.latitude;
pos.longitude = gps.longitude;
pos.time = gps.getValidTime();
if (gps->latitude != 0 || gps->longitude != 0) {
if (gps->altitude != 0)
pos.altitude = gps->altitude;
pos.latitude_i = gps->latitude;
pos.longitude_i = gps->longitude;
pos.time = getValidTime();
}
// We limit our GPS broadcasts to a max rate

View File

@@ -54,8 +54,12 @@ class MeshService
/// Allows the bluetooth handler to free packets after they have been sent
void releaseToPool(MeshPacket *p) { packetPool.release(p); }
/// Given a ToRadio buffer (from bluetooth) parse it and properly handle it (setup radio, owner or send packet into the mesh)
void handleToRadio(std::string s);
/**
* Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh)
* Called by PhoneAPI.handleToRadio. Note: p is a scratch buffer, this function is allowed to write to it but it can not keep
* a reference
*/
void handleToRadio(MeshPacket &p);
/// The radioConfig object just changed, call this to force the hw to change to the new settings
void reloadConfig();
@@ -63,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);

32
src/mesh/MeshTypes.h Normal file
View File

@@ -0,0 +1,32 @@
#pragma once
// low level types
#include "MemoryPool.h"
#include "mesh.pb.h"
#include <Arduino.h>
typedef uint32_t NodeNum;
typedef uint32_t PacketId; // A packet sequence number
#define NODENUM_BROADCAST (sizeof(NodeNum) == 4 ? UINT32_MAX : UINT8_MAX)
#define ERRNO_OK 0
#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
extern MemoryPool<MeshPacket> packetPool;

View File

@@ -5,9 +5,12 @@
#include "FS.h"
#include "SPIFFS.h"
#include "CryptoEngine.h"
#include "GPS.h"
#include "NodeDB.h"
#include "PacketHistory.h"
#include "PowerFSM.h"
#include "Router.h"
#include "configuration.h"
#include "error.h"
#include "mesh-pb-constants.h"
@@ -27,11 +30,19 @@ DeviceState versions used to be defined in the .proto file but really only this
#define here.
*/
#define DEVICESTATE_CUR_VER 7
#define DEVICESTATE_CUR_VER 9
#define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER
#ifndef NO_ESP32
#define FS SPIFFS
#define FSBegin() FS.begin(true)
#define FILE_O_WRITE "w"
#define FILE_O_READ "r"
#else
#include "InternalFileSystem.h"
#define FS InternalFS
#define FSBegin() FS.begin()
using namespace Adafruit_LittleFS_Namespace;
#endif
// FIXME - move this somewhere else
@@ -47,11 +58,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 +92,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;
@@ -93,7 +114,6 @@ void NodeDB::init()
devicestate.has_my_node = true;
devicestate.has_radio = true;
devicestate.has_owner = true;
devicestate.has_radio = false;
devicestate.radio.has_channel_settings = true;
devicestate.radio.has_preferences = true;
devicestate.node_db_count = 0;
@@ -103,10 +123,9 @@ 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));
myNodeInfo.message_timeout_msec = FLOOD_EXPIRE_TIME;
myNodeInfo.min_app_version = 167;
generatePacketId(); // FIXME - ugly way to init current_packet_id;
// Init our blank owner info to reasonable defaults
getMacAddr(ourMacAddr);
@@ -114,13 +133,29 @@ void NodeDB::init()
ourMacAddr[5]);
memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr));
// make each node start with ad different random seed (but okay that the sequence is the same each boot)
randomSeed((ourMacAddr[2] << 24L) | (ourMacAddr[3] << 16L) | (ourMacAddr[4] << 8L) | ourMacAddr[5]);
// Set default owner name
pickNewNodeNum(); // Note: we will repick later, just in case the settings are corrupted, but we need a valid
// owner.short_name now
sprintf(owner.long_name, "Unknown %02x%02x", ourMacAddr[4], ourMacAddr[5]);
sprintf(owner.short_name, "?%02X", ourMacAddr[5]);
sprintf(owner.short_name, "?%02X", myNodeInfo.my_node_num & 0xff);
// Crummy guess at our nodenum
if (!FSBegin()) // FIXME - do this in main?
{
DEBUG_MSG("ERROR filesystem mount Failed\n");
// FIXME - report failure to phone
}
// saveToDisk();
loadFromDisk();
// saveToDisk();
// We set node_num and packet_id _after_ loading from disk, because we always want to use the values this
// rom was compiled for, not what happens to be in the save file.
myNodeInfo.node_num_bits = sizeof(NodeNum) * 8;
myNodeInfo.packet_id_bits = sizeof(PacketId) * 8;
// Note! We do this after loading saved settings, so that if somehow an invalid nodenum was stored in preferences we won't
// keep using that nodenum forever. Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts)
pickNewNodeNum();
// Include our owner in the node db under our nodenum
@@ -128,8 +163,12 @@ void NodeDB::init()
info->user = owner;
info->has_user = true;
// 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);
@@ -143,9 +182,14 @@ void NodeDB::init()
*/
void NodeDB::pickNewNodeNum()
{
// FIXME not the right way to guess node numes
uint8_t r = ourMacAddr[5];
if (r == 0xff || r < NUM_RESERVED)
NodeNum r = myNodeInfo.my_node_num;
// If we don't have a nodenum at app - pick an initial nodenum based on the macaddr
if (r == 0)
r = sizeof(NodeNum) == 1 ? ourMacAddr[5]
: ((ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5]);
if (r == NODENUM_BROADCAST || r < NUM_RESERVED)
r = NUM_RESERVED; // don't pick a reserved node number
NodeInfo *found;
@@ -166,13 +210,7 @@ void NodeDB::loadFromDisk()
#ifdef FS
static DeviceState scratch;
if (!FS.begin(true)) // FIXME - do this in main?
{
DEBUG_MSG("ERROR SPIFFS Mount Failed\n");
// FIXME - report failure to phone
}
File f = FS.open(preffile);
auto f = FS.open(preffile);
if (f) {
DEBUG_MSG("Loading saved preferences\n");
pb_istream_t stream = {&readcb, &f, DeviceState_size};
@@ -206,7 +244,7 @@ void NodeDB::loadFromDisk()
void NodeDB::saveToDisk()
{
#ifdef FS
File f = FS.open(preftmp, "w");
auto f = FS.open(preftmp, FILE_O_WRITE);
if (f) {
DEBUG_MSG("Writing preferences\n");
@@ -218,15 +256,18 @@ void NodeDB::saveToDisk()
if (!pb_encode(&stream, DeviceState_fields, &devicestate)) {
DEBUG_MSG("Error: can't write protobuf %s\n", PB_GET_ERROR(&stream));
// FIXME - report failure to phone
f.close();
} else {
// Success - replace the old file
f.close();
// brief window of risk here ;-)
if (!FS.remove(preffile))
DEBUG_MSG("Warning: Can't remove old pref file\n");
if (!FS.rename(preftmp, preffile))
DEBUG_MSG("Error: can't rename new pref file\n");
}
f.close();
// brief window of risk here ;-)
if (!FS.remove(preffile))
DEBUG_MSG("Warning: Can't remove old pref file\n");
if (!FS.rename(preftmp, preffile))
DEBUG_MSG("Error: can't rename new pref file\n");
} else {
DEBUG_MSG("ERROR: can't write prefs\n"); // FIXME report to app
}
@@ -246,7 +287,7 @@ const NodeInfo *NodeDB::readNextInfo()
/// Given a node, return how many seconds in the past (vs now) that we last heard from it
uint32_t sinceLastSeen(const NodeInfo *n)
{
uint32_t now = gps.getTime();
uint32_t now = getTime();
uint32_t last_seen = n->position.time;
int delta = (int)(now - last_seen);
@@ -274,8 +315,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;
@@ -289,19 +330,24 @@ void NodeDB::updateFrom(const MeshPacket &mp)
info->position.time = mp.rx_time;
}
if (p.has_position) {
info->snr = mp.rx_snr; // keep the most recent SNR we received for this node.
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=0%0x, msg=%.*s\n", mp.from, p.data.payload.size, p.data.payload.bytes);
DEBUG_MSG("Received text msg from=0x%0x, id=%d, msg=%.*s\n", mp.from, mp.id, p.data.payload.size,
p.data.payload.bytes);
if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum()) {
// We only store/display messages destined for us.
devicestate.rx_text_message = mp;
@@ -310,9 +356,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,
@@ -330,6 +377,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"
#include "mesh-pb-constants.h"
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;
}

71
src/mesh/PacketHistory.h Normal file
View File

@@ -0,0 +1,71 @@
#pragma once
#include "Router.h"
#include <queue>
#include <unordered_set>
using namespace std;
/// We clear our old flood record five minute after we see the last of it
#define FLOOD_EXPIRE_TIME (5 * 60 * 1000L)
/**
* A record of a recent message broadcast
*/
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);
};

260
src/mesh/PhoneAPI.cpp Normal file
View File

@@ -0,0 +1,260 @@
#include "PhoneAPI.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include <assert.h>
PhoneAPI::PhoneAPI()
{
assert(FromRadio_size <= MAX_TO_FROM_RADIO_SIZE);
assert(ToRadio_size <= MAX_TO_FROM_RADIO_SIZE);
}
void PhoneAPI::init()
{
observe(&service.fromNumChanged);
}
void PhoneAPI::checkConnectionTimeout()
{
if (isConnected) {
bool newConnected = (millis() - lastContactMsec < radioConfig.preferences.phone_timeout_secs * 1000L);
if (!newConnected) {
isConnected = false;
onConnectionChanged(isConnected);
}
}
}
/**
* Handle a ToRadio protobuf
*/
void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
{
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // As long as the phone keeps talking to us, don't let the radio go to sleep
lastContactMsec = millis();
if (!isConnected) {
isConnected = true;
onConnectionChanged(isConnected);
}
// return (lastContactMsec != 0) &&
if (pb_decode_from_bytes(buf, bufLength, ToRadio_fields, &toRadioScratch)) {
switch (toRadioScratch.which_variant) {
case ToRadio_packet_tag: {
// If our phone is sending a position, see if we can use it to set our RTC
MeshPacket &p = toRadioScratch.variant.packet;
service.handleToRadio(p);
break;
}
case ToRadio_want_config_id_tag:
config_nonce = toRadioScratch.variant.want_config_id;
DEBUG_MSG("Client wants config, nonce=%u\n", config_nonce);
state = STATE_SEND_MY_INFO;
DEBUG_MSG("Reset nodeinfo read pointer\n");
nodeInfoForPhone = NULL; // Don't keep returning old nodeinfos
nodeDB.resetReadPointer(); // FIXME, this read pointer should be moved out of nodeDB and into this class - because
// this will break once we have multiple instances of PhoneAPI running independently
break;
case ToRadio_set_owner_tag:
DEBUG_MSG("Client is setting owner\n");
handleSetOwner(toRadioScratch.variant.set_owner);
break;
case ToRadio_set_radio_tag:
DEBUG_MSG("Client is setting radio\n");
handleSetRadio(toRadioScratch.variant.set_radio);
break;
default:
DEBUG_MSG("Error: unexpected ToRadio variant\n");
break;
}
} else {
DEBUG_MSG("Error: ignoring malformed toradio\n");
}
}
/**
* Get the next packet we want to send to the phone, or NULL if no such packet is available.
*
* We assume buf is at least FromRadio_size bytes long.
*
* Our sending states progress in the following sequence (the client app ASSUMES THIS SEQUENCE, DO NOT CHANGE IT):
* STATE_SEND_MY_INFO, // send our my info record
STATE_SEND_RADIO,
STATE_SEND_NODEINFO, // states progress in this order as the device sends to to the client
STATE_SEND_COMPLETE_ID,
STATE_SEND_PACKETS // send packets or debug strings
*/
size_t PhoneAPI::getFromRadio(uint8_t *buf)
{
if (!available())
return false;
// In case we send a FromRadio packet
memset(&fromRadioScratch, 0, sizeof(fromRadioScratch));
// Advance states as needed
switch (state) {
case STATE_SEND_NOTHING:
break;
case STATE_SEND_MY_INFO:
fromRadioScratch.which_variant = FromRadio_my_info_tag;
fromRadioScratch.variant.my_info = myNodeInfo;
state = STATE_SEND_RADIO;
break;
case STATE_SEND_RADIO:
fromRadioScratch.which_variant = FromRadio_radio_tag;
fromRadioScratch.variant.radio = radioConfig;
state = STATE_SEND_NODEINFO;
break;
case STATE_SEND_NODEINFO: {
const NodeInfo *info = nodeInfoForPhone;
nodeInfoForPhone = NULL; // We just consumed a nodeinfo, will need a new one next time
if (info) {
DEBUG_MSG("Sending nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", info->num, info->position.time, info->user.id,
info->user.long_name);
fromRadioScratch.which_variant = FromRadio_node_info_tag;
fromRadioScratch.variant.node_info = *info;
// Stay in current state until done sending nodeinfos
} else {
DEBUG_MSG("Done sending nodeinfos\n");
state = STATE_SEND_COMPLETE_ID;
// Go ahead and send that ID right now
return getFromRadio(buf);
}
break;
}
case STATE_SEND_COMPLETE_ID:
fromRadioScratch.which_variant = FromRadio_config_complete_id_tag;
fromRadioScratch.variant.config_complete_id = config_nonce;
config_nonce = 0;
state = STATE_SEND_PACKETS;
break;
case STATE_LEGACY: // Treat as the same as send packets
case STATE_SEND_PACKETS:
// Do we have a message from the mesh?
if (packetForPhone) {
// Encapsulate as a FromRadio packet
fromRadioScratch.which_variant = FromRadio_packet_tag;
fromRadioScratch.variant.packet = *packetForPhone;
service.releaseToPool(packetForPhone); // we just copied the bytes, so don't need this buffer anymore
packetForPhone = NULL;
}
break;
default:
assert(0); // unexpected state - FIXME, make an error code and reboot
}
// Do we have a message from the mesh?
if (fromRadioScratch.which_variant != 0) {
// Encapsulate as a FromRadio packet
DEBUG_MSG("encoding toPhone packet to phone variant=%d", fromRadioScratch.which_variant);
size_t numbytes = pb_encode_to_bytes(buf, FromRadio_size, FromRadio_fields, &fromRadioScratch);
DEBUG_MSG(", %d bytes\n", numbytes);
return numbytes;
}
DEBUG_MSG("no FromRadio packet available\n");
return 0;
}
/**
* Return true if we have data available to send to the phone
*/
bool PhoneAPI::available()
{
switch (state) {
case STATE_SEND_NOTHING:
return false;
case STATE_SEND_MY_INFO:
return true;
case STATE_SEND_NODEINFO:
if (!nodeInfoForPhone)
nodeInfoForPhone = nodeDB.readNextInfo();
return true; // Always say we have something, because we might need to advance our state machine
case STATE_SEND_RADIO:
return true;
case STATE_SEND_COMPLETE_ID:
return true;
case STATE_LEGACY: // Treat as the same as send packets
case STATE_SEND_PACKETS:
// Try to pull a new packet from the service (if we haven't already)
if (!packetForPhone)
packetForPhone = service.getForPhone();
return !!packetForPhone;
default:
assert(0); // unexpected state - FIXME, make an error code and reboot
}
return false;
}
//
// The following routines are only public for now - until the rev1 bluetooth API is removed
//
void PhoneAPI::handleSetOwner(const User &o)
{
int changed = 0;
if (*o.long_name) {
changed |= strcmp(owner.long_name, o.long_name);
strcpy(owner.long_name, o.long_name);
}
if (*o.short_name) {
changed |= strcmp(owner.short_name, o.short_name);
strcpy(owner.short_name, o.short_name);
}
if (*o.id) {
changed |= strcmp(owner.id, o.id);
strcpy(owner.id, o.id);
}
if (changed) // If nothing really changed, don't broadcast on the network or write to flash
service.reloadOwner();
}
void PhoneAPI::handleSetRadio(const RadioConfig &r)
{
radioConfig = r;
service.reloadConfig();
}
/**
* Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool
*/
void PhoneAPI::handleToRadioPacket(MeshPacket *p) {}
/// If the mesh service tells us fromNum has changed, tell the phone
int PhoneAPI::onNotify(uint32_t newValue)
{
checkConnectionTimeout(); // a handy place to check if we've heard from the phone (since the BLE version doesn't call this
// from idle)
if (state == STATE_SEND_PACKETS || state == STATE_LEGACY) {
DEBUG_MSG("Telling client we have new packets %u\n", newValue);
onNowHasData(newValue);
} else
DEBUG_MSG("(Client not yet interested in packets)\n");
return 0;
}

112
src/mesh/PhoneAPI.h Normal file
View File

@@ -0,0 +1,112 @@
#pragma once
#include "Observer.h"
#include "mesh-pb-constants.h"
#include "mesh.pb.h"
#include <string>
// Make sure that we never let our packets grow too large for one BLE packet
#define MAX_TO_FROM_RADIO_SIZE 512
/**
* Provides our protobuf based API which phone/PC clients can use to talk to our device
* over UDP, bluetooth or serial.
*
* Subclass to customize behavior for particular type of transport (BLE, UDP, TCP, serial)
*
* Eventually there should be once instance of this class for each live connection (because it has a bit of state
* for that connection)
*/
class PhoneAPI
: public Observer<uint32_t> // FIXME, we shouldn't be inheriting from Observer, instead use CallbackObserver as a member
{
enum State {
STATE_LEGACY, // Temporary default state - until Android apps are all updated, uses the old BLE API
STATE_SEND_NOTHING, // (Eventual) Initial state, don't send anything until the client starts asking for config
STATE_SEND_MY_INFO, // send our my info record
STATE_SEND_RADIO,
// STATE_SEND_OWNER, no need to send Owner specially, it is just part of the nodedb
STATE_SEND_NODEINFO, // states progress in this order as the device sends to to the client
STATE_SEND_COMPLETE_ID,
STATE_SEND_PACKETS // send packets or debug strings
};
State state = STATE_LEGACY;
/**
* Each packet sent to the phone has an incrementing count
*/
uint32_t fromRadioNum = 0;
/// We temporarily keep the packet here between the call to available and getFromRadio. We will free it after the phone
/// downloads it
MeshPacket *packetForPhone = NULL;
/// We temporarily keep the nodeInfo here between the call to available and getFromRadio
const NodeInfo *nodeInfoForPhone = NULL;
ToRadio toRadioScratch; // this is a static scratch object, any data must be copied elsewhere before returning
/// Use to ensure that clients don't get confused about old messages from the radio
uint32_t config_nonce = 0;
/** the last msec we heard from the client on the other side of this link */
uint32_t lastContactMsec = 0;
bool isConnected = false;
public:
PhoneAPI();
/// Do late init that can't happen at constructor time
virtual void init();
/**
* Handle a ToRadio protobuf
*/
virtual void handleToRadio(const uint8_t *buf, size_t len);
/**
* Get the next packet we want to send to the phone
*
* We assume buf is at least FromRadio_size bytes long.
* Returns number of bytes in the FromRadio packet (or 0 if no packet available)
*/
size_t getFromRadio(uint8_t *buf);
/**
* Return true if we have data available to send to the phone
*/
bool available();
//
// The following routines are only public for now - until the rev1 bluetooth API is removed
//
void handleSetOwner(const User &o);
void handleSetRadio(const RadioConfig &r);
protected:
/// Our fromradio packet while it is being assembled
FromRadio fromRadioScratch;
/// Hookable to find out when connection changes
virtual void onConnectionChanged(bool connected) {}
/// If we haven't heard from the other side in a while then say not connected
void checkConnectionTimeout();
/**
* Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies)
*/
virtual void onNowHasData(uint32_t fromRadioNum) {}
private:
/**
* Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool
*/
void handleToRadioPacket(MeshPacket *p);
/// If the mesh service tells us fromNum has changed, tell the phone
virtual int onNotify(uint32_t newValue);
};

126
src/mesh/RF95Interface.cpp Normal file
View File

@@ -0,0 +1,126 @@
#include "RF95Interface.h"
#include "MeshRadio.h" // kinda yucky, but we need to know which region we are in
#include "RadioLibRF95.h"
#include <configuration.h>
#define MAX_POWER 17
// if we use 20 we are limited to 1% duty cycle or hw might overheat. For continuous operation set a limit of 17
RF95Interface::RF95Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, SPIClass &spi)
: RadioLibInterface(cs, irq, rst, 0, spi)
{
// FIXME - we assume devices never get destroyed
}
/// Initialise the Driver transport hardware and software.
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
bool RF95Interface::init()
{
RadioLibInterface::init();
applyModemConfig();
if (power > MAX_POWER) // This chip has lower power limits than some
power = MAX_POWER;
iface = lora = new RadioLibRF95(&module);
int res = lora->begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength);
DEBUG_MSG("LORA init result %d\n", res);
if (res == ERR_NONE)
res = lora->setCRC(SX126X_LORA_CRC_ON);
if (res == ERR_NONE)
startReceive(); // start receiving
return res == ERR_NONE;
}
void INTERRUPT_ATTR RF95Interface::disableInterrupt()
{
lora->clearDio0Action();
}
bool RF95Interface::reconfigure()
{
applyModemConfig();
// set mode to standby
setStandby();
// configure publicly accessible settings
int err = lora->setSpreadingFactor(sf);
assert(err == ERR_NONE);
err = lora->setBandwidth(bw);
assert(err == ERR_NONE);
err = lora->setCodingRate(cr);
assert(err == ERR_NONE);
err = lora->setSyncWord(syncWord);
assert(err == ERR_NONE);
err = lora->setCurrentLimit(currentLimit);
assert(err == ERR_NONE);
err = lora->setPreambleLength(preambleLength);
assert(err == ERR_NONE);
err = lora->setFrequency(freq);
assert(err == ERR_NONE);
if (power > MAX_POWER) // This chip has lower power limits than some
power = MAX_POWER;
err = lora->setOutputPower(power);
assert(err == ERR_NONE);
startReceive(); // restart receiving
return ERR_NONE;
}
/**
* Add SNR data to received messages
*/
void RF95Interface::addReceiveMetadata(MeshPacket *mp)
{
mp->rx_snr = lora->getSNR();
}
void RF95Interface::setStandby()
{
int err = lora->standby();
assert(err == ERR_NONE);
isReceiving = false; // If we were receiving, not any more
disableInterrupt();
completeSending(); // If we were sending, not anymore
}
void RF95Interface::startReceive()
{
setStandby();
int err = lora->startReceive();
assert(err == ERR_NONE);
isReceiving = true;
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
enableInterrupt(isrRxLevel0);
}
/** Could we send right now (i.e. either not actively receving or transmitting)? */
bool RF95Interface::isActivelyReceiving()
{
return lora->isReceiving();
}
bool RF95Interface::sleep()
{
// put chipset into sleep mode
disableInterrupt();
lora->sleep();
return true;
}

55
src/mesh/RF95Interface.h Normal file
View File

@@ -0,0 +1,55 @@
#pragma once
#include "MeshRadio.h" // kinda yucky, but we need to know which region we are in
#include "RadioLibInterface.h"
#include "RadioLibRF95.h"
/**
* Our new not radiohead adapter for RF95 style radios
*/
class RF95Interface : public RadioLibInterface
{
RadioLibRF95 *lora; // Either a RFM95 or RFM96 depending on what was stuffed on this board
public:
RF95Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, SPIClass &spi);
/// Initialise the Driver transport hardware and software.
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
virtual bool init();
/// Apply any radio provisioning changes
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
virtual bool reconfigure();
/// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep.
virtual bool sleep();
protected:
/**
* Glue functions called from ISR land
*/
virtual void disableInterrupt();
/**
* Enable a particular ISR callback glue function
*/
virtual void enableInterrupt(void (*callback)()) { lora->setDio0Action(callback); }
/** are we actively receiving a packet (only called during receiving state) */
virtual bool isActivelyReceiving();
/**
* Start waiting to receive a message
*/
virtual void startReceive();
/**
* Add SNR data to received messages
*/
virtual void addReceiveMetadata(MeshPacket *mp);
virtual void setStandby();
};

129
src/mesh/RadioInterface.cpp Normal file
View File

@@ -0,0 +1,129 @@
#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 || sizeof(PacketHeader) == 16); // 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");
packetPool.release(p);
return ERRNO_OK;
}
void RadioInterface::deliverToReceiver(MeshPacket *p)
{
assert(rxDest);
assert(rxDest->enqueue(p, 0)); // NOWAIT - fixme, if queue is full, delete older messages
}
/***
* given a packet set sendingPacket and decode the protobufs into radiobuf. Returns # of payload bytes to send
*/
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->which_payload == MeshPacket_encrypted_tag); // It should have already been encoded by now
lastTxStart = millis();
PacketHeader *h = (PacketHeader *)radiobuf;
h->from = p->from;
h->to = p->to;
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);
memcpy(radiobuf + sizeof(PacketHeader), p->encrypted.bytes, p->encrypted.size);
sendingPacket = p;
return p->encrypted.size + sizeof(PacketHeader);
}

165
src/mesh/RadioInterface.h Normal file
View File

@@ -0,0 +1,165 @@
#pragma once
#include "MemoryPool.h"
#include "MeshTypes.h"
#include "Observer.h"
#include "PointerQueue.h"
#include "WorkerThread.h"
#include "mesh.pb.h"
#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission
#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 {
NodeNum to, from; // can be 1 byte or four bytes
PacketId id; // can be 1 byte or 4 bytes
/**
* 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 {
Bw125Cr45Sf128 = 0, ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range
Bw500Cr45Sf128, ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range
Bw31_25Cr48Sf512, ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range
Bw125Cr48Sf4096, ///< Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range
} ModemConfigChoice;
/**
* Basic operations all radio chipsets must implement.
*
* This defines the SOLE API for talking to radios (because soon we will have alternate radio implementations)
*/
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;
uint32_t lastTxStart = 0L;
/**
* A temporary buffer used for sending/receving packets, sized to hold the biggest buffer we might need
* */
uint8_t radiobuf[MAX_RHPACKETLEN];
/**
* Enqueue a received packet for the registered receiver
*/
void deliverToReceiver(MeshPacket *p);
public:
float freq = 915.0; // FIXME, init all these params from user setings
int8_t power = 17;
ModemConfigChoice modemConfig;
/** 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
*/
RadioInterface();
/**
* Set where to deliver received packets. This method should only be used by the Router class
*/
void setReceiver(PointerQueue<MeshPacket> *_rxDest) { rxDest = _rxDest; }
/**
* Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving)
*
* This method must be used before putting the CPU into deep or light sleep.
*/
virtual bool canSleep() { return true; }
/// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep.
virtual bool sleep() { return true; }
/**
* Send a packet (possibly by enquing in a private fifo). 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) = 0;
// methods from radiohead
/// Initialise the Driver transport hardware and software.
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
virtual bool init();
/// Apply any radio provisioning changes
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
virtual bool reconfigure() = 0;
protected:
/***
* given a packet set sendingPacket and decode the protobufs into radiobuf. Returns # of bytes to send (including the
* PacketHeader & payload).
*
* Used as the first step of
*/
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
{
public:
virtual ErrorCode send(MeshPacket *p);
// methods from radiohead
/// Initialise the Driver transport hardware and software.
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
virtual bool init() { return true; }
};

View File

@@ -0,0 +1,310 @@
#include "RadioLibInterface.h"
#include "MeshTypes.h"
#include "mesh-pb-constants.h"
#include <configuration.h>
#include <pb_decode.h>
#include <pb_encode.h>
// FIXME, we default to 4MHz SPI, SPI mode 0, check if the datasheet says it can really do that
static SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
SPIClass &spi, PhysicalLayer *_iface)
: PeriodicTask(0), module(cs, irq, rst, busy, spi, spiSettings), iface(_iface)
{
assert(!instance); // We assume only one for now
instance = this;
}
bool RadioLibInterface::init()
{
setup(); // init our timer
return RadioInterface::init();
}
#ifndef NO_ESP32
// ESP32 doesn't use that flag
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR()
#else
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x)
#endif
void INTERRUPT_ATTR RadioLibInterface::isrLevel0Common(PendingISR cause)
{
instance->disableInterrupt();
instance->pending = cause;
BaseType_t xHigherPriorityTaskWoken;
instance->notifyFromISR(&xHigherPriorityTaskWoken, cause, eSetValueWithOverwrite);
/* Force a context switch if xHigherPriorityTaskWoken is now set to pdTRUE.
The macro used to do this is dependent on the port and may be called
portEND_SWITCHING_ISR. */
YIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
void INTERRUPT_ATTR RadioLibInterface::isrRxLevel0()
{
isrLevel0Common(ISR_RX);
}
void INTERRUPT_ATTR RadioLibInterface::isrTxLevel0()
{
isrLevel0Common(ISR_TX);
}
/** Our ISR code currently needs this to find our active instance
*/
RadioLibInterface *RadioLibInterface::instance;
/**
* Convert our modemConfig enum into wf, sf, etc...
*/
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;
cr = 5;
sf = 7;
break;
case Bw500Cr45Sf128: ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range
bw = 500;
cr = 5;
sf = 7;
break;
case Bw31_25Cr48Sf512: ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range
bw = 31.25;
cr = 8;
sf = 9;
break;
case Bw125Cr48Sf4096:
bw = 125;
cr = 8;
sf = 12;
break;
default:
assert(0); // Unknown enum
}
}
/** Could we send right now (i.e. either not actively receving or transmitting)? */
bool RadioLibInterface::canSendImmediately()
{
// We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one).
// To do otherwise would be doubly bad because not only would we drop the packet that was on the way in,
// we almost certainly guarantee no one outside will like the packet we are sending.
bool busyTx = sendingPacket != NULL;
bool busyRx = isReceiving && isActivelyReceiving();
if (busyTx || busyRx) {
if (busyTx)
DEBUG_MSG("Can not send yet, busyTx\n");
if (busyRx)
DEBUG_MSG("Can not send yet, busyRx\n");
return false;
} else
return true;
}
/// Send a packet (possibly by enquing in a private fifo). This routine will
/// later free() the packet to pool. This routine is not allowed to stall because it is called from
/// bluetooth comms code. If the txmit queue is empty it might return an error
ErrorCode RadioLibInterface::send(MeshPacket *p)
{
DEBUG_MSG("enqueuing for send on mesh fr=0x%x,to=0x%x,id=%d (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, txGood,
rxGood, rxBad);
ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN;
if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks
packetPool.release(p);
return res;
}
// We want all sending/receiving to be done by our daemon thread, We use a delay here because this packet might have been sent
// in response to a packet we just received. So we want to make sure the other side has had a chance to reconfigure its radio
startTransmitTimer(true);
return res;
}
bool RadioLibInterface::canSleep()
{
bool res = txQueue.isEmpty();
if (!res) // only print debug messages if we are vetoing sleep
DEBUG_MSG("radio wait to sleep, txEmpty=%d\n", txQueue.isEmpty());
return res;
}
/** At the low end we want to pick a delay large enough that anyone who just completed sending (some other node)
* has had enough time to switch their radio back into receive mode.
*/
#define MIN_TX_WAIT_MSEC 100
/**
* At the high end, this value is used to spread node attempts across time so when they are replying to a packet
* they don't both check that the airwaves are clear at the same moment. As long as they are off by some amount
* one of the two will be first to start transmitting and the other will see that. I bet 500ms is more than enough
* to guarantee this.
*/
#define MAX_TX_WAIT_MSEC 2000 // stress test would still fail occasionally with 1000
/** radio helper thread callback.
We never immediately transmit after any operation (either rx or tx). Instead we should start receiving and
wait a random delay of 50 to 200 ms to make sure we are not stomping on someone else. The 50ms delay at the beginning ensures all
possible listeners have had time to finish processing the previous packet and now have their radio in RX state. The up to 200ms
random delay gives a chance for all possible senders to have high odds of detecting that someone else started transmitting first
and then they will wait until that packet finishes.
NOTE: the large flood rebroadcast delay might still be needed even with this approach. Because we might not be able to hear other
transmitters that we are potentially stomping on. Requires further thought.
FIXME, the MIN_TX_WAIT_MSEC and MAX_TX_WAIT_MSEC values should be tuned via logic analyzer later.
*/
void RadioLibInterface::loop()
{
pending = ISR_NONE;
switch (notification) {
case ISR_TX:
handleTransmitInterrupt();
startReceive();
startTransmitTimer();
break;
case ISR_RX:
handleReceiveInterrupt();
startReceive();
startTransmitTimer();
break;
case TRANSMIT_DELAY_COMPLETED:
// If we are not currently in receive mode, then restart the timer and try again later (this can happen if the main thread
// has placed the unit into standby) FIXME, how will this work if the chipset is in sleep mode?
if (!txQueue.isEmpty()) {
if (!canSendImmediately()) {
startTransmitTimer(); // try again in a little while
} else {
// Send any outgoing packets we have ready
MeshPacket *txp = txQueue.dequeuePtr(0);
assert(txp);
startSend(txp);
}
}
break;
default:
assert(0); // We expected to receive a valid notification from the ISR
}
}
void RadioLibInterface::doTask()
{
disable(); // Don't call this callback again
// We use without overwrite, so that if there is already an interrupt pending to be handled, that gets handle properly (the
// ISR handler will restart our timer)
notify(TRANSMIT_DELAY_COMPLETED, eSetValueWithoutOverwrite);
}
void RadioLibInterface::startTransmitTimer(bool withDelay)
{
// If we have work to do and the timer wasn't already scheduled, schedule it now
if (getPeriod() == 0 && !txQueue.isEmpty()) {
uint32_t delay =
!withDelay ? 1 : random(MIN_TX_WAIT_MSEC, MAX_TX_WAIT_MSEC); // See documentation for loop() wrt these values
// DEBUG_MSG("xmit timer %d\n", delay);
setPeriod(delay);
}
}
void RadioLibInterface::handleTransmitInterrupt()
{
// DEBUG_MSG("handling lora TX interrupt\n");
assert(sendingPacket); // Were we sending? - FIXME, this was null coming out of light sleep due to RF95 ISR!
completeSending();
}
void RadioLibInterface::completeSending()
{
if (sendingPacket) {
txGood++;
DEBUG_MSG("Completed sending to=0x%x, id=%u\n", sendingPacket->to, sendingPacket->id);
// We are done sending that packet, release it
packetPool.release(sendingPacket);
sendingPacket = NULL;
// DEBUG_MSG("Done with send\n");
}
}
void RadioLibInterface::handleReceiveInterrupt()
{
assert(isReceiving);
isReceiving = false;
// read the number of actually received bytes
size_t length = iface->getPacketLength();
int state = iface->readData(radiobuf, length);
if (state != ERR_NONE) {
DEBUG_MSG("ignoring received packet due to error=%d\n", state);
rxBad++;
} else {
// Skip the 4 headers that are at the beginning of the rxBuf
int32_t payloadLen = length - sizeof(PacketHeader);
const uint8_t *payload = radiobuf + sizeof(PacketHeader);
// check for short packets
if (payloadLen < 0) {
DEBUG_MSG("ignoring received packet too short\n");
rxBad++;
} else {
const PacketHeader *h = (PacketHeader *)radiobuf;
rxGood++;
// Note: we deliver _all_ packets to our router (i.e. our interface is intentionally promiscuous).
// This allows the router and other apps on our node to sniff packets (usually routing) between other
// nodes.
MeshPacket *mp = packetPool.allocZeroed();
mp->from = h->from;
mp->to = h->to;
mp->id = h->id;
assert(HOP_MAX <= PACKET_FLAGS_HOP_MASK); // If hopmax changes, carefully check this code
mp->hop_limit = h->flags & PACKET_FLAGS_HOP_MASK;
mp->want_ack = !!(h->flags & PACKET_FLAGS_WANT_ACK_MASK);
addReceiveMetadata(mp);
mp->which_payload = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point
assert(payloadLen <= sizeof(mp->encrypted.bytes));
memcpy(mp->encrypted.bytes, payload, payloadLen);
mp->encrypted.size = payloadLen;
DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id);
deliverToReceiver(mp);
}
}
}
/** start an immediate transmit */
void RadioLibInterface::startSend(MeshPacket *txp)
{
DEBUG_MSG("Starting low level send from=0x%x, 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);
int res = iface->startTransmit(radiobuf, numbytes);
assert(res == ERR_NONE);
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
enableInterrupt(isrTxLevel0);
}

View File

@@ -0,0 +1,145 @@
#pragma once
#include "PeriodicTask.h"
#include "RadioInterface.h"
#include <RadioLib.h>
// ESP32 has special rules about ISR code
#ifdef ARDUINO_ARCH_ESP32
#define INTERRUPT_ATTR IRAM_ATTR
#else
#define INTERRUPT_ATTR
#endif
class RadioLibInterface : public RadioInterface, private PeriodicTask
{
/// Used as our notification from the ISR
enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX, TRANSMIT_DELAY_COMPLETED };
volatile PendingISR pending = ISR_NONE;
/**
* Raw ISR handler that just calls our polymorphic method
*/
static void isrTxLevel0(), isrLevel0Common(PendingISR code);
/**
* Debugging counts
*/
uint32_t rxBad = 0, rxGood = 0, txGood = 0;
protected:
float bw = 125;
uint8_t sf = 9;
uint8_t cr = 7;
/**
* FIXME, use a meshtastic sync word, but hashed with the Channel name. Currently picking the same default
* the RF95 used (0x14). Note: do not use 0x34 - that is reserved for lorawan
*/
uint8_t syncWord = SX126X_SYNC_WORD_PRIVATE;
float currentLimit = 100; // FIXME
uint16_t preambleLength = 32; // 8 is default, but FIXME use longer to increase the amount of sleep time when receiving
Module module; // The HW interface to the radio
/**
* provides lowest common denominator RadioLib API
*/
PhysicalLayer *iface;
/// 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
*/
virtual void disableInterrupt() = 0;
/**
* Enable a particular ISR callback glue function
*/
virtual void enableInterrupt(void (*)()) = 0;
public:
RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi,
PhysicalLayer *iface = NULL);
virtual ErrorCode send(MeshPacket *p);
/**
* Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving)
*
* This method must be used before putting the CPU into deep or light sleep.
*/
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);
/** if we have something waiting to send, start a short random timer so we can come check for collision before actually doing
* the transmit
*
* If the timer was already running, we just wait for that one to occur.
* */
void startTransmitTimer(bool withDelay = true);
void handleTransmitInterrupt();
void handleReceiveInterrupt();
static void timerCallback(void *p1, uint32_t p2);
virtual void doTask();
protected:
/// Initialise the Driver transport hardware and software.
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
virtual bool init();
/**
* Convert our modemConfig enum into wf, sf, etc...
*
* These paramaters will be pull from the channelSettings global
*/
virtual void applyModemConfig();
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
virtual bool canSendImmediately();
/** are we actively receiving a packet (only called during receiving state) */
virtual bool isActivelyReceiving() = 0;
/**
* Raw ISR handler that just calls our polymorphic method
*/
static void isrRxLevel0();
/**
* If a send was in progress finish it and return the buffer to the pool */
void completeSending();
/**
* Add SNR data to received messages
*/
virtual void addReceiveMetadata(MeshPacket *mp) = 0;
virtual void loop(); // Idle processing
virtual void setStandby() = 0;
};

68
src/mesh/RadioLibRF95.cpp Normal file
View File

@@ -0,0 +1,68 @@
#include "RadioLibRF95.h"
#define RF95_CHIP_VERSION 0x12
#define RF95_ALT_VERSION 0x11 // Supposedly some versions of the chip have id 0x11
RadioLibRF95::RadioLibRF95(Module *mod) : SX1278(mod) {}
int16_t RadioLibRF95::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power, uint8_t currentLimit,
uint16_t preambleLength, uint8_t gain)
{
// execute common part
int16_t state = SX127x::begin(RF95_CHIP_VERSION, syncWord, currentLimit, preambleLength);
if (state != ERR_NONE)
state = SX127x::begin(RF95_ALT_VERSION, syncWord, currentLimit, preambleLength);
RADIOLIB_ASSERT(state);
// configure settings not accessible by API
state = config();
RADIOLIB_ASSERT(state);
// configure publicly accessible settings
state = setFrequency(freq);
RADIOLIB_ASSERT(state);
state = setBandwidth(bw);
RADIOLIB_ASSERT(state);
state = setSpreadingFactor(sf);
RADIOLIB_ASSERT(state);
state = setCodingRate(cr);
RADIOLIB_ASSERT(state);
state = setOutputPower(power);
RADIOLIB_ASSERT(state);
state = setGain(gain);
return (state);
}
int16_t RadioLibRF95::setFrequency(float freq)
{
// RADIOLIB_CHECK_RANGE(freq, 862.0, 1020.0, ERR_INVALID_FREQUENCY);
// set frequency
return (SX127x::setFrequencyRaw(freq));
}
#define RH_RF95_MODEM_STATUS_CLEAR 0x10
#define RH_RF95_MODEM_STATUS_HEADER_INFO_VALID 0x08
#define RH_RF95_MODEM_STATUS_RX_ONGOING 0x04
#define RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED 0x02
#define RH_RF95_MODEM_STATUS_SIGNAL_DETECTED 0x01
bool RadioLibRF95::isReceiving()
{
// 0x0b == Look for header info valid, signal synchronized or signal detected
uint8_t reg = readReg(SX127X_REG_MODEM_STAT);
// Serial.printf("reg %x\n", reg);
return (reg & (RH_RF95_MODEM_STATUS_SIGNAL_DETECTED | RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED |
RH_RF95_MODEM_STATUS_HEADER_INFO_VALID)) != 0;
}
uint8_t RadioLibRF95::readReg(uint8_t addr)
{
return _mod->SPIreadRegister(addr);
}

73
src/mesh/RadioLibRF95.h Normal file
View File

@@ -0,0 +1,73 @@
#pragma once
#include <RadioLib.h>
/*!
\class RFM95
\brief Derived class for %RFM95 modules. Overrides some methods from SX1278 due to different parameter ranges.
*/
class RadioLibRF95: public SX1278 {
public:
// constructor
/*!
\brief Default constructor. Called from Arduino sketch when creating new LoRa instance.
\param mod Instance of Module that will be used to communicate with the %LoRa chip.
*/
RadioLibRF95(Module* mod);
// basic methods
/*!
\brief %LoRa modem initialization method. Must be called at least once from Arduino sketch to initialize the module.
\param freq Carrier frequency in MHz. Allowed values range from 868.0 MHz to 915.0 MHz.
\param bw %LoRa link bandwidth in kHz. Allowed values are 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250 and 500 kHz.
\param sf %LoRa link spreading factor. Allowed values range from 6 to 12.
\param cr %LoRa link coding rate denominator. Allowed values range from 5 to 8.
\param syncWord %LoRa sync word. Can be used to distinguish different networks. Note that value 0x34 is reserved for LoRaWAN networks.
\param power Transmission output power in dBm. Allowed values range from 2 to 17 dBm.
\param currentLimit Trim value for OCP (over current protection) in mA. Can be set to multiplies of 5 in range 45 to 120 mA and to multiples of 10 in range 120 to 240 mA.
Set to 0 to disable OCP (not recommended).
\param preambleLength Length of %LoRa transmission preamble in symbols. The actual preamble length is 4.25 symbols longer than the set number.
Allowed values range from 6 to 65535.
\param gain Gain of receiver LNA (low-noise amplifier). Can be set to any integer in range 1 to 6 where 1 is the highest gain.
Set to 0 to enable automatic gain control (recommended).
\returns \ref status_codes
*/
int16_t begin(float freq = 915.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = SX127X_SYNC_WORD, int8_t power = 17, uint8_t currentLimit = 100, uint16_t preambleLength = 8, uint8_t gain = 0);
// configuration methods
/*!
\brief Sets carrier frequency. Allowed values range from 868.0 MHz to 915.0 MHz.
\param freq Carrier frequency to be set in MHz.
\returns \ref status_codes
*/
int16_t setFrequency(float freq);
// Return true if we are actively receiving a message currently
bool isReceiving();
/// For debugging
uint8_t readReg(uint8_t addr);
#ifndef RADIOLIB_GODMODE
private:
#endif
};

196
src/mesh/ReliableRouter.cpp Normal file
View File

@@ -0,0 +1,196 @@
#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);
}
bool ReliableRouter::shouldFilterReceived(const MeshPacket *p)
{
if (p->to == NODENUM_BROADCAST && p->from == getNodeNum()) {
DEBUG_MSG("Received someone rebroadcasting for us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
// We are seeing someone rebroadcast one of our broadcast attempts.
// If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for
// the original sending process.
if (stopRetransmission(p->from, p->id)) {
DEBUG_MSG("Someone is retransmitting for us, generate implicit ack\n");
sendAckNak(true, p->from, p->id);
}
}
return FloodingRouter::shouldFilterReceived(p);
}
/**
* If we receive a want_ack packet (do not check for wasSeenRecently), send back an ack (this might generate multiple ack sends in
* case the our first ack gets lost)
*
* 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::sniffReceived(const MeshPacket *p)
{
NodeNum ourNode = getNodeNum();
if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability
// - not DSR routing)
if (p->want_ack) {
sendAckNak(true, p->from, p->id);
}
// 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 intentionally don't check wasSeenRecently, because it is harmless to delete non existent retransmission records
if (ackId || nakId) {
if (ackId) {
DEBUG_MSG("Received a ack=%d, stopping retransmissions\n", ackId);
stopRetransmission(p->to, ackId);
} else {
DEBUG_MSG("Received a nak=%d, stopping retransmissions\n", nakId);
stopRetransmission(p->to, nakId);
}
}
}
// handle the packet as normal
FloodingRouter::sniffReceived(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();
}
PendingPacket *ReliableRouter::findPendingPacket(GlobalPacketId key)
{
auto old = pending.find(key); // If we have an old record, someone messed up because id got reused
if (old != pending.end()) {
return &old->second;
} else
return NULL;
}
/**
* Stop any retransmissions we are doing of the specified node/packet ID pair
*/
bool ReliableRouter::stopRetransmission(NodeNum from, PacketId id)
{
auto key = GlobalPacketId(from, id);
return stopRetransmission(key);
}
bool ReliableRouter::stopRetransmission(GlobalPacketId key)
{
auto old = findPendingPacket(key);
if (old) {
auto numErased = pending.erase(key);
assert(numErased == 1);
packetPool.release(old->packet);
return true;
} else
return false;
}
/**
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
*/
PendingPacket *ReliableRouter::startRetransmission(MeshPacket *p)
{
auto id = GlobalPacketId(p);
auto rec = PendingPacket(p);
stopRetransmission(p->from, p->id);
pending[id] = rec;
return &pending[id];
}
/**
* 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);
// Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived - which
// allows the DSR version to still be able to look at the PendingPacket
stopRetransmission(it->first);
} else {
DEBUG_MSG("Sending reliable retransmission fr=0x%x,to=0x%x,id=%d, tries left=%d\n", p.packet->from, p.packet->to,
p.packet->id, p.numRetransmissions);
// 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();
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More