Compare commits

...

286 Commits
0.0.4 ... 0.4.3

Author SHA1 Message Date
Kevin Hester
fab616a15e Merge pull request #101 from geeksville/master
0.4.3 Fix #92: omg - for the last couple of weeks the official builds…
2020-04-19 08:39:51 -07:00
geeksville
2419ebb04e 0.4.3 Fix #92: omg - for the last couple of weeks the official builds were
all using US frequencies.  This build fixes this (and makes the build
system cleaner in general).

If you are building your own builds in the IDE you'll need to start
setting an environment variable called COUNTRY to your two letter
country code (or leave unset to get US frequencies).  See new comment
in platformio.ini.
2020-04-19 08:33:59 -07:00
Kevin Hester
88c576798b Merge pull request #100 from geeksville/removeradiohead
Removeradiohead
2020-04-18 18:42:25 -07:00
geeksville
db766f18ed Fix #99: move spi ISR operations into helper thread. SPI from ISR is bad! 2020-04-18 14:56:09 -07:00
geeksville
f9a805e3d4 Merge remote-tracking branch 'root/master' into removeradiohead 2020-04-18 09:22:38 -07:00
geeksville
e5f9a752d8 fix comments and cleanup ISR code 2020-04-18 09:22:26 -07:00
geeksville
20b41836e2 clarify log msg 2020-04-18 09:22:08 -07:00
Kevin Hester
c55df4d1cc Merge pull request #98 from geeksville/removeradiohead
Removeradiohead
2020-04-18 08:58:12 -07:00
Kevin Hester
908b1020c0 Merge branch 'master' into removeradiohead 2020-04-18 08:51:00 -07:00
geeksville
78470ed3f5 fix #97, we need the RF95 IRQ to be level triggered, or we have slim chance of missing events 2020-04-18 08:48:03 -07:00
geeksville
4ce7df295e don't poll for completion so quickly - the log messages scare people 2020-04-18 08:39:05 -07:00
Kevin Hester
598abb0d23 Merge pull request #96 from geeksville/removeradiohead
Removeradiohead
2020-04-17 18:55:23 -07:00
geeksville
184eac6281 0.4.2 2020-04-17 18:51:46 -07:00
geeksville
176532f55f autoformat per formatting rules 2020-04-17 18:50:07 -07:00
geeksville
8eb3045451 Fix #85, we were stalling sometimes on send while in ISR which is NEVER legal 2020-04-17 18:49:54 -07:00
geeksville
2fe145aed9 debugging goo 2020-04-17 18:48:37 -07:00
Kevin Hester
ab61e64056 Merge pull request #94 from geeksville/removeradiohead
LOTS of changes:
2020-04-17 14:37:03 -07:00
geeksville
5b17417e0c debugging GPIO wake on heltec- seems fine. 2020-04-17 14:30:42 -07:00
geeksville
04a83fd6b7 properly detach observers at destruction 2020-04-17 13:24:38 -07:00
geeksville
7730bd762a be less chatty about sleep 2020-04-17 13:18:33 -07:00
geeksville
62286fff52 0.4.1 release 2020-04-17 13:05:16 -07:00
geeksville
25cca0628d more debug output 2020-04-17 12:46:57 -07:00
geeksville
65406eaa08 mesh flooding seems to work pretty well! 2020-04-17 12:41:01 -07:00
geeksville
ea24394110 add first cut of mesh naive flooding 2020-04-17 11:52:20 -07:00
geeksville
6afeb3e456 ok - new router seems to approximately work 2020-04-17 10:38:44 -07:00
geeksville
f108c576a7 massive WIP updates to create a clean Router abstraction for mesh 2020-04-17 09:48:54 -07:00
geeksville
6eb74415ab protobuf changes as part of getting ready for mesh again 2020-04-16 17:32:36 -07:00
geeksville
0d14b69a24 remove disasterradio experiment 2020-04-16 17:30:46 -07:00
geeksville
d4eb47e837 doc updates 2020-04-16 17:30:33 -07:00
geeksville
86716c4397 remove tbeam0.7 until someone who has the hardware can debug it 2020-04-16 09:05:53 -07:00
geeksville
7bbcf101c1 Merge remote-tracking branch 'root/master' into removeradiohead 2020-04-15 18:52:04 -07:00
Kevin Hester
8e495e417c Merge pull request #91 from geeksville/master
add production android fingerprints
2020-04-15 18:41:17 -07:00
Kevin Hester
a6e0d6d751 Merge branch 'master' into master 2020-04-15 18:33:22 -07:00
geeksville
cff255a397 add production android fingerprints 2020-04-15 18:20:45 -07:00
Kevin Hester
3336e821ad Merge pull request #90 from geeksville/master
fighting github pages
2020-04-15 18:05:28 -07:00
geeksville
d75d603218 Merge remote-tracking branch 'root/master' 2020-04-15 18:02:28 -07:00
geeksville
bf5be49186 It helps if I use the right filename when I'm remote debugging githubpages 2020-04-15 18:01:43 -07:00
geeksville
39ff26aa45 Merge branch 'master' into removeradiohead 2020-04-15 18:00:24 -07:00
Kevin Hester
02b5efed96 Merge pull request #89 from geeksville/master
add .well_known so that hopefully I can prove I own this domain to an…
2020-04-15 17:50:19 -07:00
geeksville
0a07c5692c add .well_known so that hopefully I can prove I own this domain to android 2020-04-15 17:46:34 -07:00
geeksville
2464784f00 todo updates 2020-04-15 14:51:17 -07:00
Kevin Hester
a8f5ff1fbd Merge pull request #87 from geeksville/cleanupblue
Refactoring to cleanup the relationship between bluetooth and everything else
2020-04-15 14:19:38 -07:00
geeksville
0a6af936ed Get build (kinda, not really) running on a NRF52
Lots of NO_ESP32 to remove later...
2020-04-14 20:22:27 -07:00
geeksville
0b62083e35 wip - plan 2020-04-14 16:45:26 -07:00
geeksville
5ca149fac9 move radiointerface into lib 2020-04-14 14:36:26 -07:00
geeksville
80c69c28cd move pool/queue management into the rf95 lib 2020-04-14 13:20:36 -07:00
geeksville
5904d66111 Move Custom95 in with the rest of the RH code, to be ready to refactor 2020-04-14 12:38:42 -07:00
geeksville
fd17193d5e Strip out all the parts of Radiohead (most of it) that we don't need 2020-04-14 12:31:29 -07:00
geeksville
5c379c4a98 missing newline 2020-04-14 11:44:35 -07:00
geeksville
9c5d626e7d Merge remote-tracking branch 'mine/cleanupblue' into cleanupblue 2020-04-14 11:41:16 -07:00
geeksville
4757b6807e lots of changes:
* preflightSleep, notifySleep, notifyDeepSleep now allow arbitrary
drivers/devices/software to register for sleep notification.
* Use the proceeding to clean up MeshRadio - now the mesh radio is more
like an independent driver that doesn't care so much about other systems
* clean up MeshService so that it can work with zero MeshRadios added.
This is a prelude to supporting boards with multiple interfaces (wifi,
extra LORA radios etc) and allows development/testing in sim with a bare
ESP32 board
* Remove remaining ESP32 dependencies from the bare simulation target
this allows running on anything that implements the arduino API
2020-04-14 11:40:49 -07:00
geeksville
ac7f3cd603 fix bin paths 2020-04-12 10:54:27 -07:00
Kevin Hester
04ad8bb533 Merge branch 'master' into cleanupblue 2020-04-10 21:11:46 -07:00
Kevin Hester
5924690ce3 Merge pull request #86 from BeardyWalrus/master
pinouts are swapped for TTGO-Lora32 V1 and V2 boards.
2020-04-10 21:11:30 -07:00
Kevin Hester
3ac3baf1c1 Merge branch 'master' into master 2020-04-10 21:09:12 -07:00
Kevin Hester
aa88e2bb1f Merge pull request #83 from geeksville/master
misc minor fix queue
2020-04-10 21:08:37 -07:00
geeksville
640cb3bf7f allow observers to return an error code to abort further processing
Will allow me to use observers to generalize the various hooks
that need to run to preflight sleep entry.
2020-04-10 12:40:44 -07:00
geeksville
6ad451eb5f move bluetooth code into something that is architecture specific...
because the ESP32 implementation will be different from NRF52
to make this possible I needed to decouple knowlege about bluetooth from
our mesh service.  Instead mesh service now uses the Obserable pattern
to let any interested consumer get notified of important mesh changes
(currently that is only bluetooth, but really we should do the same
thing for decoupling the GUI 'app' from the mesh service)

@girtsf would you mind reviewing my Observer changes? I haven't written
C++ code in a long time ;-)
2020-04-10 12:18:48 -07:00
geeksville
93a06906cb begin splitting up source files, so we can have a tree of sources...
unique to each architecture.  For now, we have "esp32" and "bare"

esp32 is the old esp stuff

bare is an target suitable for emulation that doesn't require any
particular hardware to run (no bluetooth, no i2c devices, no spi devices)
2020-04-10 12:15:35 -07:00
BeardyWalrus
3754950a0e pinouts are swapped for TTGO-Lora32 V1 and V2 boards.
discovered by eriktheV-king in discourse
2020-04-09 20:44:15 -04:00
geeksville
06a17885eb need need to link against the axp202 library on devices that don't have it 2020-04-07 21:27:08 -07:00
geeksville
33624b7753 todo update 2020-04-07 21:26:50 -07:00
Kevin Hester
2d6b9320a5 Merge pull request #82 from android606/screen-timeout-increase
Increase screen timeout to 5 minutes
2020-04-07 21:11:09 -07:00
Andrew
6b35454570 Merge pull request #1 from android606/meshtastic-esp32#80
Fix Meshtastic#80: Increase screen timeout to 5 minutes
2020-04-06 22:57:24 -07:00
Andrew
753a57230d Fix Meshtastic#80: Increase screen timeout to 5 minutes 2020-04-06 22:53:10 -07:00
Kevin Hester
9c3e63e6b5 Merge pull request #81 from Dafeman/patch-1
Update README.md
2020-04-06 20:28:43 -07:00
Kevin Hester
4d4d7b38cc Merge branch 'master' into patch-1 2020-04-06 20:26:17 -07:00
Kevin Hester
23f148f8ce Merge pull request #79 from geeksville/master
kevin's misc bugfix queue ;-)
2020-04-06 20:26:04 -07:00
Dafeman
d150549754 Update README.md
Added GUI install instructions and minor other formatting changes.
2020-04-07 14:37:00 +12:00
geeksville
441c4c7b3c Merge remote-tracking branch 'root/master' 2020-04-06 09:40:01 -07:00
geeksville
c22df18e28 fix collision avoidance for transmit - substantially improves lora tx success 2020-04-06 09:39:44 -07:00
Kevin Hester
e32b41938a Merge pull request #78 from geeksville/master
Fix #77: bluetooth is supposed to be on in DARK state
2020-04-05 14:41:52 -07:00
geeksville
ef395a1596 0.2.3 2020-04-05 14:39:42 -07:00
geeksville
af35f3006c force other devs to update for
fix https://github.com/meshtastic/Meshtastic-esp32/issues/76
2020-04-05 14:13:04 -07:00
geeksville
13ac686c96 minor doc cleanups 2020-04-05 13:58:38 -07:00
geeksville
05a0266fc4 Fix #77: bluetooth is supposed to be on in DARK state 2020-04-05 13:09:46 -07:00
Kevin Hester
3acbf31198 Merge pull request #75 from geeksville/master
fix bugs I noticed while testing a virgin tbeam
2020-04-04 20:55:06 -07:00
geeksville
2e82bf3ca1 Merge remote-tracking branch 'root/master' 2020-04-04 20:51:31 -07:00
geeksville
63e1a3f47e always set time from GPS if we can 2020-04-04 19:16:30 -07:00
geeksville
07a8972aea NEO-6M gps with empty backup batteries give super invalid times 2020-04-04 18:47:41 -07:00
geeksville
d1cb45aa5d string typo 2020-04-04 18:46:19 -07:00
Kevin Hester
1b3610d0fa Merge pull request #74 from geeksville/master
begin cleanup on radio abstraction - so we can support different radio chips and libraries
2020-04-02 08:10:12 -07:00
geeksville
2fcdc2c09f Merge remote-tracking branch 'root/master' 2020-04-01 11:51:15 -07:00
geeksville
da74803ffb begin cleanup on radio abstraction, details below:
* to allow changing to new mesh transport
* to allow a different chipset for the radio
* to allow testing on hardware with a SimRadio
* new "bare" build env for a devboard with virtually no hardware
* make buttons optional
2020-03-31 21:56:35 -07:00
Kevin Hester
890511615f Merge pull request #72 from geeksville/master
kevin's monday bug queue ;-)
2020-03-31 10:40:13 -07:00
geeksville
17de6f9532 fix #73: allow hw-model to be longer (16 bytes including terminator) 2020-03-30 19:58:06 -07:00
Kevin Hester
2a15195c32 Merge branch 'master' into master 2020-03-30 18:52:29 -07:00
geeksville
1b050a6c3d stop distributing the (enormous) elf files in the zip file 2020-03-30 18:51:15 -07:00
geeksville
1da62e5ba1 oops - we were previously not marking these protobuf structs as valid 2020-03-30 17:02:41 -07:00
geeksville
feb6f2e59e extra debug output for radio config, and bump up bax handles for BLE 2020-03-30 17:02:09 -07:00
geeksville
7dabad1b70 don't let phones send down fatally invalid RadioConfig records
and if a phone did in the past, fixup the garbage we saved in flash
2020-03-30 16:10:11 -07:00
geeksville
a73f466473 Add GPL statement 2020-03-30 13:46:46 -07:00
geeksville
1fd92cf8eb fix build for Windows based on tip from @moinars 2020-03-29 12:33:14 -07:00
Kevin Hester
dd671ceca6 Merge pull request #71 from geeksville/master
add OS-X instructions from @android606
2020-03-29 11:50:13 -07:00
geeksville
8a76931b37 add OS-X instructions from @android606 2020-03-29 11:40:18 -07:00
Kevin Hester
b071eecce1 Merge pull request #70 from geeksville/fix68
Fix68
2020-03-29 11:16:15 -07:00
geeksville
81734f75c8 fix review comments (don't let commands queue up if we are missing a display) 2020-03-29 11:13:53 -07:00
geeksville
11d57e721a fix #68 (@girtsf, pls review - ps: no worries ;-) )
// We don't set useDisplay until setup() is called, because some boards have a declaration of this object but the device
// is never found when probing i2c and therefore we don't call setup and never want to do (invalid) accesses to this device.
2020-03-29 11:00:25 -07:00
geeksville
9b0e329bb9 update credits 2020-03-28 20:33:21 -07:00
Kevin Hester
450fb7bc35 Merge pull request #67 from geeksville/master
kevin's minor work items saturday queue
2020-03-28 20:22:36 -07:00
geeksville
c1f8c8cca4 0.2.0 2020-03-28 20:10:37 -07:00
geeksville
ea250d9cd3 add initial guess at TBEAM 0.7 hardware support 2020-03-28 15:31:22 -07:00
geeksville
be468a2183 add reasonable guesses for TTGO LORA V1 and V2 boards - thanks @sensorsiot
for the pinouts
2020-03-28 14:45:33 -07:00
Kevin Hester
a47d6c4d68 Merge pull request #65 from geeksville/master
kevin's friday minor bugfix queue
2020-03-28 13:26:30 -07:00
geeksville
5386a5b224 update build instructions 2020-03-28 13:17:07 -07:00
geeksville
a350b3795b remove unused file 2020-03-28 13:16:54 -07:00
geeksville
cf2aa37635 clean up configuration.h and add support for ttgo-lora-v1 boards 2020-03-27 16:55:19 -07:00
geeksville
d1387be015 Merge remote-tracking branch 'root/master'
# Conflicts:
#	src/main.cpp
#	src/screen.cpp
2020-03-27 14:18:07 -07:00
Kevin Hester
f8857ad45b Merge pull request #63 from girtsf/debug-screen-b49
fix #49: make debug screen show real data
2020-03-27 14:11:56 -07:00
geeksville
d831beab3d moving build selection into platformio.ini rather than nasty #defines. thanks to @sensorslot
for the pointer to https://github.com/arendst/Tasmota - where I just borrowed heavily ;-)
2020-03-27 14:03:58 -07:00
geeksville
5c4ae6c042 now that axp192 interrups work, no need to poll over i2c. #48 2020-03-27 14:03:58 -07:00
geeksville
a0c97825e8 always use gps.isConnected to check for GPS, it is the only thing
guaranteed to be fresh and accurate
2020-03-27 12:32:18 -07:00
geeksville
cc3bac7ea0 Fix AXP192 handling by @spattinson. yay! fix #48
Also - now that he fixed that, we can leave PMU interrupts on across sleep

Hopefully the following line will properly credit him in the magic github
universe...

Co-authored-by: spattinson <spattinson@users.noreply.github.com>
2020-03-27 12:29:51 -07:00
geeksville
ce21859ada toto updates 2020-03-27 12:08:05 -07:00
Girts Folkmanis
54cd082bfe fix #49: make debug screen show real data
* Break out debug screen to a separate class and make it thread-safe.
* Break out power state to a separate class.
* Show battery voltage, charging & USB status on debug screen.
* Show GPS lock / no lock
* Fix an off-by-one that I introduced earlier in `drawRows`.
2020-03-26 22:17:47 -07:00
Kevin Hester
4c35d1f207 Merge pull request #58 from geeksville/master
kevins bug fixes for weds
2020-03-25 16:24:56 -07:00
geeksville
4d54df4c9b 0.1.10 2020-03-25 16:21:41 -07:00
geeksville
aa9aca2b88 Merge remote-tracking branch 'root/master' 2020-03-25 13:38:26 -07:00
geeksville
a579bbcb50 heltec style devices were not automatically setting their time from the mesh
due to a bug in the init of has_gps.  Now that we probe for gps we should
default has_gps to false until we hear from it
2020-03-25 13:36:54 -07:00
geeksville
12e67d3b30 add some debug output 2020-03-25 13:35:49 -07:00
geeksville
50d724780a make user presses ask other nodes for their latest status
see related bug
https://github.com/meshtastic/Meshtastic-esp32/issues/59
2020-03-25 13:09:12 -07:00
geeksville
45babab8c4 generalize the serial console script 2020-03-25 12:35:35 -07:00
geeksville
3443e60718 never loop waiting on hardware without some sort of timeout ;-)
related to https://github.com/meshtastic/Meshtastic-esp32/issues/53
2020-03-25 12:25:46 -07:00
geeksville
65128a04c9 my new watchdog related to the the send tx bug was not quite complete
https://github.com/meshtastic/Meshtastic-esp32/issues/53
2020-03-25 11:45:18 -07:00
geeksville
7210c1ae5e emphasize the forum link
and ... alas, do a one time autoformat from the IDE.
2020-03-25 08:13:07 -07:00
Kevin Hester
9075501917 Merge pull request #56 from geeksville/master
misc changes as kevin bangs through the post illness bug-queue
2020-03-24 15:30:04 -07:00
geeksville
a3b70e7538 0.1.9 2020-03-24 15:23:50 -07:00
geeksville
b4b8abe6ec omg I was not setting the correct flag to tell phone we had gps 2020-03-24 15:20:24 -07:00
geeksville
d647be73df oops nasty bug probably responsible for ble mutex seeming bugs #33
Was calling the wrong superclass method and therfore not properly
populating radio
2020-03-24 15:16:32 -07:00
geeksville
42d7966858 add instructions on how to reinstall 2020-03-24 15:13:28 -07:00
geeksville
f4d368e1f4 fix #27 - add a device-install.sh script to the release 2020-03-24 14:48:52 -07:00
geeksville
3a756b0e08 keep the elf files in the zip package, useful with @girtsf tool 2020-03-24 13:58:17 -07:00
geeksville
34ead2d68e add support for reporting device errors up through the phone to analytics
related to https://github.com/meshtastic/Meshtastic-esp32/issues/53
2020-03-24 13:33:24 -07:00
geeksville
3f1161b68b bug #53 - report the error on console and fixup (will add analytics in
a separate call)
2020-03-24 13:04:28 -07:00
Kevin Hester
f108e24bc1 Merge pull request #51 from girtsf/fix-upside-down-screen
screen.cpp: flip the display 180
2020-03-24 11:09:06 -07:00
Kevin Hester
1f83f7d9df Merge branch 'master' into fix-upside-down-screen 2020-03-24 10:55:56 -07:00
Kevin Hester
756c7ca7b3 Merge pull request #55 from girtsf/fix-ble
fix #52: bluetooth not pairing
2020-03-24 10:55:00 -07:00
Kevin Hester
79f7bf77c9 Merge branch 'master' into fix-ble 2020-03-24 10:52:10 -07:00
Girts Folkmanis
dee3e530de fix #52: bluetooth not pairing
Silly type error on my part - PIN was always truncated to lower 8 bits.
😬

Tested: Pairing now works from both nRF Connect and phone.
2020-03-22 19:18:49 -07:00
Girts Folkmanis
3e44c2c3e1 screen.cpp: flip the display 180 2020-03-19 20:15:51 -07:00
Kevin Hester
3886665041 Merge pull request #47 from geeksville/master
update todo list
2020-03-18 20:37:46 -07:00
geeksville
c9b269c3c0 Merge remote-tracking branch 'root/master' 2020-03-18 19:36:37 -07:00
geeksville
eb51c92d08 update todo list 2020-03-18 19:28:55 -07:00
Kevin Hester
992b525588 Merge pull request #46 from geeksville/master
a quick window to sneak in and fix formatting
2020-03-18 19:19:18 -07:00
geeksville
25288d8ed6 Merge remote-tracking branch 'root/master' 2020-03-18 19:16:56 -07:00
geeksville
32ac5ac9ae reformat everything
using @girtsf clang-format prefs settings.  This should allow us to turn
on auto format in our editors without causing spurious file changes.
2020-03-18 19:15:51 -07:00
Kevin Hester
ef5cdefca6 Merge pull request #44 from geeksville/master
various minor commits based on bugs I see while testing app
2020-03-18 18:55:57 -07:00
geeksville
f6f9dfa463 0.1.8 2020-03-18 18:53:55 -07:00
geeksville
2161ce21df the firmware version xml file should not be checked in, it is used directly
by the android build and derived from version.sh
2020-03-18 18:53:42 -07:00
geeksville
534691f0c2 Merge remote-tracking branch 'root/master'
# Conflicts:
#	src/main.cpp
#	src/screen.cpp
#	src/screen.h
2020-03-18 18:44:12 -07:00
Kevin Hester
6bc8e1b10a Merge pull request #45 from girtsf/screen-cpp-refactor
Screen cleanups and refactoring
2020-03-18 18:35:51 -07:00
geeksville
c8b95f7691 oops - I broke compass display with my gps changes and didn't notice till
testing with two gps equipped devices.  fixed.
2020-03-18 18:34:22 -07:00
Girts Folkmanis
daf8594b99 Screen cleanups and refactoring
Work towards separating out how Screen interacts with other stuff.
* `Screen` should now be thread-safe. All commands to it are put in a
  queue and handled in `doTask` from the `loop()` task.
* Break dependency from `BluetoothUtil` to `Screen` by changing the
  pairing request into a callback.
* All accesses to screen now happen through the class.
* Fix `drawRows` so that the text scrolls along with frame animations.
* Remove example code that wasn't used.
2020-03-18 18:11:35 -07:00
Girts Folkmanis
5b54fd6359 screen.cpp: reformat with clang-format 2020-03-18 17:16:19 -07:00
geeksville
53765298e1 add a real BOOT state, to avoid glitch from redrawing bootscreen twice
also its the right thing to do ;-)
2020-03-18 15:00:17 -07:00
geeksville
0d94458c4e bump preferences # 2020-03-18 14:59:30 -07:00
geeksville
5e55695862 fix build warning 2020-03-18 14:51:54 -07:00
Kevin Hester
c9e2e6c386 Merge pull request #43 from geeksville/one-wire-gps
fixes to make one-wire gpses work and cope with tbeams with crummy rx buffers
2020-03-18 14:02:46 -07:00
geeksville
dbbb62f63e fix press to properly force any node we are watching to send us a new
position report
2020-03-18 13:51:32 -07:00
geeksville
79ce7d929c send dynamic probed GPS status to the phone 2020-03-18 13:29:22 -07:00
geeksville
33437b5246 oops - I accidentally shadowed a variable I didn't want to shadow ;-) 2020-03-18 09:37:38 -07:00
geeksville
f4bacb9d87 some tbeams have occasional crap sitting in their gps rx buffer at boot? 2020-03-18 09:29:20 -07:00
geeksville
0ac218b06d allow gpses which only have the RX pin connected to also work.
(and because I'm lazy, let the autoreformat rule work on this file)
2020-03-18 09:21:28 -07:00
Kevin Hester
2ce1b4bb2c Merge pull request #42 from geeksville/master
release 0.1.7
2020-03-17 20:02:01 -07:00
geeksville
f002bee4f4 release 0.1.7 2020-03-17 19:50:26 -07:00
Kevin Hester
9c69326ed9 Merge pull request #39 from geeksville/bug17
Bug17
2020-03-17 19:42:44 -07:00
Kevin Hester
7a617d5378 Merge branch 'master' into bug17 2020-03-17 19:40:25 -07:00
Kevin Hester
90edae1ce0 Merge pull request #41 from geeksville/new-oled
fix #40 force an extra redraw for the bootscreen,
2020-03-17 19:39:32 -07:00
geeksville
2134b4db9b fix #40 force an extra redraw for the bootscreen,
some clones drop the first draw cmd
2020-03-17 16:36:48 -07:00
geeksville
7b1ffb5c09 #17 WIP add tool to capture the system portion of flash
based on initial encouraging feedback from @illperipherals

if more people report success, we'll need to change the install instructions
to:

esptool.py --port COM10 --baud 921600 erase_flash
esptool.py --port COM10 --baud 921600 write_flash 0x1000 system-info.bin
esptool.py --port COM10 --baud 921600 write_flash 0x10000 firmware-HELTEC-US-0.1.6.bin
2020-03-17 11:17:58 -07:00
geeksville
535f07d927 add a tbeam program script 2020-03-17 11:16:22 -07:00
Kevin Hester
bfad136137 Merge pull request #31 from geeksville/master
@girtsf can you review these changes and blessmotize if they seem good?
2020-03-16 09:37:31 -07:00
geeksville
36bee8fa53 Merge remote-tracking branch 'root/master' 2020-03-16 09:30:24 -07:00
Kevin Hester
99cb0b3855 Merge pull request #37 from girtsf/fix-build-2
fix build: add missing include to screen.cpp
2020-03-16 09:30:14 -07:00
Girts Folkmanis
41c95eaff7 fix build: add missing include to screen.cpp 2020-03-16 09:26:40 -07:00
geeksville
ca8a25f585 Merge remote-tracking branch 'root/master' 2020-03-16 09:03:21 -07:00
Kevin Hester
51a8700391 Merge pull request #35 from girtsf/underp-lock-paths
underp include paths in lock.h
2020-03-16 09:02:54 -07:00
Kevin Hester
f099a31a29 Merge pull request #34 from girtsf/patch-1
run CI on pull requests as well
2020-03-16 09:01:28 -07:00
Kevin Hester
52dbc4e15d Merge pull request #36 from girtsf/various-cleanups
Various cleanups
2020-03-16 09:00:42 -07:00
Girts Folkmanis
8cabb3ea3d add .clang-format file
Tried to infer the style from existing files.
2020-03-15 19:29:55 -07:00
Girts Folkmanis
7a4a1af332 TypedQueue: make functions return bools instead of BaseType_t
Minor cleanup to hide away some FreeRTOS bits.

Note: I believe src/CustomRF95.cpp:62 had a bug where it had the
condition inverted.
2020-03-15 19:29:04 -07:00
Girts Folkmanis
90ecdf229e add locks to PeriodicTask 2020-03-15 19:29:00 -07:00
geeksville
24ac907780 auto generate channel numbers from name
NOTE: All radios on a channel will need to be updated to this release
before they can talk together again.
2020-03-15 17:51:57 -07:00
geeksville
5037fb830e fix build (and autoformat in visual studio code) 2020-03-15 17:50:48 -07:00
Girts Folkmanis
79f1346359 underp include paths in lock.h
Had the casing wrong, but could get away with it on a mac.
2020-03-15 17:43:42 -07:00
Girts
35dada683a run CI on pull requests as well 2020-03-15 17:42:48 -07:00
geeksville
30a431788d we now do bidirectional comms to GPS at startup, so we can always trust isConnected 2020-03-15 16:57:21 -07:00
geeksville
d0b8adab75 In my work for #11 I accidentially created a serious bug on Heltec...
devices.  It caused bogus i2c transactions when device would go to sleep.
Fixed now, also, I now treat GPS usage uniformly between TBEAM and HELTEC
we always probe for and use the GPS if we find it.

Which means for the extra nerds
(someone requested this, I'm sorry - I don't remember who) you can now
optionally attach an external GPS to HELTECs if you want.  The pins are:

 #define GPS_RX_PIN 34
 #define GPS_TX_PIN 12

(@girtsf, sorry about including formatting changes in this PR, apparently
I had my IDE set to not autoreformat until just now
2020-03-15 16:57:21 -07:00
geeksville
74f7b7b622 print extra info the next time this error occurs 2020-03-15 16:57:21 -07:00
geeksville
9ec8562ce7 fix old geeksville link (though github provides redirects) 2020-03-15 16:57:21 -07:00
Kevin Hester
0cbcb7a9bd Merge pull request #32 from girtsf/add-lock-etc
add a Lock, LockGuard and printThreadInfo
2020-03-15 16:55:48 -07:00
Girts Folkmanis
2874b22d6c add a Lock, LockGuard and printThreadInfo
* `Lock`: trivial wrapper for FreeRTOS binary semaphores
* `LockGuard`: RAII wrapper for using `Lock`
* `printThreadInfo`: helper for showing which core/FreeRTOS task we are
  running under
2020-03-15 16:52:19 -07:00
Kevin Hester
3c9be48445 Merge pull request #30 from girtsf/fix-build
fix the build: remove includes for TinyGPS that's not longer used or …
2020-03-15 14:30:04 -07:00
Girts Folkmanis
b5201f928b fix the build: remove includes for TinyGPS that's not longer used or in deps 2020-03-15 13:27:00 -07:00
Kevin Hester
2591859df5 Merge pull request #29 from girtsf/exception-decoder
check in script to decode backtraces
2020-03-15 12:38:44 -07:00
Girts Folkmanis
c411db111b check in script to decode backtraces 2020-03-15 12:29:15 -07:00
geeksville
0d62a2be85 release 0.1.6 2020-03-14 18:48:15 -07:00
geeksville
9c971d3686 Finally - new sexy ultra low power/low latency GPS code is in for TBEAM 2020-03-14 18:45:24 -07:00
geeksville
3c1357b732 ublox is better but still not perfect 2020-03-14 16:31:12 -07:00
geeksville
acd5e5d29d use power save mode on gps 2020-03-14 13:33:53 -07:00
geeksville
64109b25f2 Switch to native ublox api for GPS position and time 2020-03-14 12:46:24 -07:00
geeksville
e93bc76ac9 Merge branch 'master' into gps-sleep-mode 2020-03-13 22:45:40 -07:00
geeksville
1107c6d23d Fix serious bug: button presses were not waking from light-sleep on TBEAM
Root cause seems to be the axp192 interrupt, which on some boards fires
during sleep.  I'm not sure why, but we don't need this yet, so leaving
masked during sleep.
2020-03-13 22:42:43 -07:00
geeksville
9b2155402d todo update 2020-03-13 22:41:12 -07:00
geeksville
35cf8a4859 power debugging 2020-03-13 22:38:58 -07:00
geeksville
76f21dfd6e wip 2020-03-13 22:34:44 -07:00
geeksville
b101dc2c88 wip 2020-03-13 20:48:37 -07:00
geeksville
9bbd658b9d begin testing native ublox api 2020-03-13 20:30:48 -07:00
geeksville
4999da0824 Merge branch 'dev' into gps-sleep-mode
# Conflicts:
#	docs/software/TODO.md
#	platformio.ini
2020-03-13 18:48:38 -07:00
geeksville
a783341df1 begin adding temporary support for both NEMA and UBLOX gpses 2020-03-13 18:44:14 -07:00
geeksville
04258755e7 change from gitter to discourse. 2020-03-13 15:06:51 -07:00
geeksville
ea711ece1f new firmware released 2020-03-13 15:03:47 -07:00
geeksville
e93187b630 0.1.5 add support for EU 433MHz radios thanks to @astro-arphid 2020-03-12 09:34:10 -07:00
geeksville
eca3cda8b9 fix typos 2020-03-12 09:31:31 -07:00
Kevin Hester
2874adfd69 Merge pull request #21 from astro-arphid/master
#13 - add support for 433MHz Heltec boards (thanks to @astro-arphid)
2020-03-12 09:29:54 -07:00
astro-arphid
91227b25df #13 - add support for 433MHz Heltec boards 2020-03-12 06:05:11 +00:00
geeksville
4dfac03402 add note about 3d printed cases 2020-03-11 10:59:02 -07:00
geeksville
9bc895eb57 fix formatting 2020-03-11 10:55:58 -07:00
geeksville
474026d7fe add a faq 2020-03-11 10:51:38 -07:00
geeksville
9995d2a7ab oops copypasta error fix #19. Thanks @iveskins! 2020-03-10 19:33:16 -07:00
geeksville
3028ac3f24 add note about dash7 2020-03-10 19:32:45 -07:00
geeksville
8605950eef add credits list 2020-03-10 11:43:45 -07:00
Kevin Hester
0f856dea7b Merge pull request #18 from claesg/master
Updated README for 3D cases for T-Beam.
2020-03-10 11:39:17 -07:00
claes
2a2bc5b1d6 Updated README for 3D cases for T-Beam.
T-Beam V1 has moved the antenna connector a bit to the right compared
to the V0. This makes the case not fit.
Added 2 more links for T-Beam cases.
2020-03-10 09:31:32 +01:00
geeksville
f87cb00940 show reason for last reset 2020-03-08 14:05:12 -07:00
geeksville
a4ebf83b69 minor doc update 2020-03-08 14:03:36 -07:00
geeksville
5dbacbddba version 0.1.13 Fix #15 found by @claesg. Thanks for the detailed report! 2020-03-08 11:07:27 -07:00
geeksville
011864a2d3 Fix #15 Force all devices to update preferences 2020-03-08 11:02:58 -07:00
geeksville
8646d8aeba mesh alg replacement update 2020-03-08 11:02:15 -07:00
geeksville
87782cdf8f progress on re readdition of the mesh router 2020-03-06 19:52:34 -08:00
geeksville
10289ed11a todo updates 2020-03-06 19:47:57 -08:00
geeksville
5def6a66e6 cleanup dev docs a bit more 2020-03-05 19:00:00 -08:00
geeksville
2d3bb4bb75 fix table formatting 2020-03-05 18:45:48 -08:00
geeksville
ffe8bf2be6 Refactor to make some real design docs 2020-03-05 18:39:24 -08:00
geeksville
ab73033e51 Fix #10. Add note about board types. 2020-03-05 13:18:36 -08:00
geeksville
c66e24aa4d most users are not using the phone app now, don't enter SDS state because of no phone comms 2020-03-04 18:59:10 -08:00
geeksville
651b13b3e5 version 0.1.0 2020-03-04 16:49:59 -08:00
geeksville
951f5d11d5 fix text message display when new text arrives
The problem was we were pulsing the display power briefly down while
reentering the ON state (because the ON states exit rule turned it off).
Instead we now just turn off the screen on entry to DARK or LS states
2020-03-04 16:46:57 -08:00
geeksville
2ce95ed2ce fix typo in SDS sleep time 2020-03-04 16:10:48 -08:00
geeksville
d1b7b6c5c5 oops - I was previously stopping bluetooth before stopping the mesh bluetooth service
which was bad bad
2020-03-04 16:10:36 -08:00
geeksville
425c570757 don't show fake GPS level on devices without one #4 2020-03-04 10:51:29 -08:00
Kevin Hester
46dab2d496 Make devicestate.version something that only the device code cares about 2020-03-03 13:46:11 -08:00
Kevin Hester
f5271f5c79 website updates 2020-03-03 13:37:01 -08:00
Kevin Hester
375804c9e4 (needs testing) fixed the following during a plane flight
* Have state machine properly enter deep sleep based on loss of mesh and phone comms.
* Default to enter deep sleep if no LORA received for two hours (indicates user has probably left the mesh).

Signed-off-by: Kevin Hester <kevinh@geeksville.com>
2020-03-03 13:31:44 -08:00
Kevin Hester
fe5703c684 fix the "blank screen when a text message arrives" bug 2020-03-03 10:15:23 -08:00
Kevin Hester
f1c90a232f fix release test script paths 2020-03-03 09:52:10 -08:00
Kevin Hester
a91fba763a release 0.0.9 2020-03-03 08:29:37 -08:00
Kevin Hester
28588f6730 send hw version info via protobufs 2020-03-03 08:23:58 -08:00
Kevin Hester
d074218049 no need to stop mesh service during sw update, bug was on the android side 2020-03-03 07:38:56 -08:00
Kevin Hester
1f07d6735b fix a nasty sleep bug: we weren't stopping our old mesh service
so it would kinda keep living (along with the recreated one) when we
come back from sleep.
2020-03-03 07:36:35 -08:00
Kevin Hester
bb0b2e72c8 stop using ino files, visual studio doesn't like them 2020-03-03 07:34:05 -08:00
Kevin Hester
30c717ac2f minor doc updates 2020-03-02 15:31:04 -08:00
Kevin Hester
e01680cd4b Change all repo URLs to be meshtastic... instead of geeksville... 2020-03-02 15:13:33 -08:00
Kevin Hester
4bb308c80c Pull protobufs from submodule instead of a symbolic link Fix #2 2020-03-02 09:57:05 -08:00
Kevin Hester
12a68026e7 clarify release of meshpacket back to pool 2020-03-02 09:02:21 -08:00
Kevin Hester
06449e39c1 Prove to google that we own this domain 2020-03-02 09:02:21 -08:00
Kevin Hester
cc6a8c8430 Merge pull request #12 from claesg/master
Updated the README.md with a new radio: TTGO LORA32
2020-03-01 16:25:51 -08:00
claes
9aedec2c5c Updated the README.md with a new radio: TTGO LORA32 2020-03-01 13:00:15 +01:00
Kevin Hester
2221ddfca2 Add note about a mystery bug following light sleep 2020-02-29 19:06:14 -08:00
Kevin Hester
7dc6264fa7 It helps to spell things correctly... 2020-02-29 19:05:59 -08:00
Kevin Hester
f091a9505a Add a pretty CI badge based on @girtsf work 2020-02-29 19:00:46 -08:00
Kevin Hester
57d5a54099 Merge pull request #6 from girtsf/fix-some-warnings
some minor cleanups
2020-02-29 07:10:50 -08:00
Kevin Hester
045a2031a3 todo updates 2020-02-29 07:08:18 -08:00
Girts Folkmanis
0b8d7768fd some minor cleanups
* Suppress warnings about conflicting library names in platformio.ini by
  explicitly picking the libraries by id that we want.
* fix unused static function warning by making it not static ;)
* declare arduino-fsm as dependency for BluetoothOTA. Hopefully this
  fixes the CI builds?

Tested: `pio run` builds.
2020-02-28 19:26:11 -08:00
Kevin Hester
c41f572cf5 update todo list while sitting in airplane ;-) 2020-02-28 12:36:52 -08:00
geeksville
bf26994d4e add misc hw docs/datasheets 2020-02-27 11:25:27 -08:00
geeksville
30ff67d46a fix CI? 2020-02-26 17:37:08 -08:00
geeksville
65e6d029e0 move CI script 2020-02-26 17:22:37 -08:00
geeksville
31e9d34d1e try github actions for CI builds 2020-02-26 17:20:19 -08:00
geeksville
0a796c4937 get ready for 0.0.6 2020-02-26 14:27:00 -08:00
geeksville
877e312833 allow TBEAMs to provide approx GPS time to Heltec devices 2020-02-26 09:00:53 -08:00
geeksville
cace2f4290 update readme for android release 2020-02-25 11:22:42 -08:00
geeksville
7b348f30ac text messages screen not coming up if screen was already on 2020-02-25 10:29:37 -08:00
geeksville
8893be57df kinda ugly but BLE sw update seems reliable again 2020-02-24 18:09:58 -08:00
geeksville
6b696bfdc5 hw vendor strings must match file names for auto update 2020-02-24 11:21:34 -08:00
geeksville
a3df099f60 until my TBEAM is fixed, leave GPS power on in sleep sometimes 2020-02-24 11:21:08 -08:00
geeksville
0ce1bbb758 expose standard ble sw/hw version characteristics from the updater
We also implement the following standard GATT entries because SW update probably needs them:
+
+ESP_GATT_UUID_SW_VERSION_STR/0x2a28
+ESP_GATT_UUID_MANU_NAME/0x2a29
+ESP_GATT_UUID_HW_VERSION_STR/0x2a27
2020-02-24 10:24:21 -08:00
geeksville
bb808c6c52 fix bluetooth pairing UX. enter full ON state when requested 2020-02-24 10:23:07 -08:00
geeksville
15ea13c286 start machine generating curfirmwareversion 2020-02-24 09:55:02 -08:00
geeksville
b0e0ee6934 begin to track firmware versions in a machine readable way 2020-02-24 09:46:32 -08:00
geeksville
f4791137fd keep latest builds in their own directory - so android build can find them 2020-02-24 09:33:41 -08:00
geeksville
51b53f406e include region code in the BLE HW version string. ie 1.0-US 2020-02-24 09:22:34 -08:00
geeksville
bd0f35ad83 fix encoding of sw version numbers in the BLE device info 2020-02-24 09:12:13 -08:00
geeksville
af5e3a0e02 TODO updates - back to Android app for now 2020-02-24 08:47:02 -08:00
geeksville
635e189007 GPS sleep mode WIP - alas this thread will have to be back burner for a
while because my TX wire on my ONLY T-BEAM broke while I was soldering
a jtag connector - so I can only RX from the GPS.

My other 5 boards are still delayed due to corona virus.

So I'll work on other features for now
2020-02-22 20:05:38 -08:00
geeksville
6eba792537 WIP - changing to a ublox aware GPS lib so I can put it in sleep 2020-02-22 19:08:16 -08:00
129 changed files with 10114 additions and 2722 deletions

6
.clang-format Normal file
View File

@@ -0,0 +1,6 @@
Language: Cpp
IndentWidth: 4
ColumnLimit: 130
PointerAlignment: Right
BreakBeforeBraces: Linux
AllowShortFunctionsOnASingleLine: Inline

21
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Continuous Integration
on:
- push
- pull_request
jobs:
main:
name: Main
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Setup Python
uses: actions/setup-python@master
with:
python-version: 3.x
- name: Install Platform IO
run: |
python -m pip install --upgrade pip
pip install -U platformio
- name: Build
run: platformio run

3
.gitmodules vendored Normal file
View File

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

4
.vscode/launch.json vendored
View File

@@ -12,7 +12,7 @@
"type": "platformio-debug",
"request": "launch",
"name": "PIO Debug",
"executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/esp32/firmware.elf",
"executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf",
"toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin",
"preLaunchTask": {
"type": "PlatformIO",
@@ -24,7 +24,7 @@
"type": "platformio-debug",
"request": "launch",
"name": "PIO Debug (skip Pre-Debug)",
"executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/esp32/firmware.elf",
"executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf",
"toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin",
"internalConsoleOptions": "openOnSessionStart"
}

11
.vscode/settings.json vendored
View File

@@ -41,10 +41,17 @@
"utility": "cpp",
"typeinfo": "cpp",
"string": "cpp",
"*.xbm": "cpp"
"*.xbm": "cpp",
"list": "cpp",
"atomic": "cpp",
"memory_resource": "cpp",
"optional": "cpp",
"string_view": "cpp",
"cassert": "cpp"
},
"cSpell.words": [
"Meshtastic",
"descs"
"descs",
"protobufs"
]
}

150
README.md
View File

@@ -1,37 +1,56 @@
# Meshtastic-esp32
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
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
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.
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
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.
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 chat [![Join the chat at https://gitter.im/Meshtastic/community](https://badges.gitter.im/Meshtastic/community.svg)](https://gitter.im/Meshtastic/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge).
This project is currently early-alpha, but 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.
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 two brands of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4000119152086.html) and the [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/). Most users should buy the T-Beam and a 18650 battery (total cost less than $35). Make
sure to buy the frequency range which is legal for your country. For the USA, you should buy the 915MHz version. Getting a version that include a screen
is optional, but highly recommended.
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.
See (meshtastic.org) for 3D printable cases.
## Installing the firmware
Prebuilt binaries for the supported radios is available in our [releases](https://github.com/geeksville/Meshtastic-esp32/releases). Your initial installation has to happen over USB from your Mac, Windows or Linux PC. Once our software is installed, all future software updates happen over bluetooth from your phone.
## Firmware Installation
The instructions currently require a few commmand lines, but it should be pretty straightforward. Please post comments on our group chat if you have problems or successes. Steps to install:
Prebuilt binaries for the supported radios are available in our [releases](https://github.com/meshtastic/Meshtastic-esp32/releases). Your initial installation has to happen over USB from your Mac, Windows or Linux PC. Once our software is installed, all future software updates happen over bluetooth from your phone.
Please post comments on our [group chat](https://meshtastic.discourse.group/) if you have problems or successes.
### Installing from a GUI - Windows and Mac
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.
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).
### Installing from a commandline
These instructions currently require a few commmand lines, but it should be pretty straightforward.
1. Install "pip". Pip is the python package manager we use to get the esptool installer app. Instructions [here](https://www.makeuseof.com/tag/install-pip-for-python/). If you are using OS-X, see these [special instructions](docs/software/install-OSX.md).
2. Run "pip install --upgrade esptool" to get esptool installed on your machine.
3. Connect your radio to your USB port.
4. Confirm that your device is talking to your PC by running "esptool.py chip_id". The Heltec build also works on the TTGO LORA32 radio. You should see something like:
1. Purchase a radio (see above) with the correct frequencies for your country (915MHz for US or JP, 470MHz for CN, 870MHz for EU).
2. Install "pip". Pip is the python package manager we use to get the esptool installer app. Instructions [here](https://www.makeuseof.com/tag/install-pip-for-python/).
3. Run "pip install --upgrade esptool" to get esptool installed on your machine
4. Connect your radio to your USB port
5. Confirm that your device is talking to your PC by running "esptool.py chip_id". You should see something like:
```
mydir$ esptool.py chip_id
esptool.py v2.6
@@ -49,50 +68,105 @@ Warning: ESP32 has no Chip ID. Reading MAC instead.
MAC: 24:6f:28:b5:36:71
Hard resetting via RTS pin...
```
6. Install the correct firmware for your board with "esptool.py write_flash 0x10000 firmware-_board_-_country_.bin". For instance "esptool.py write_flash 0x10000 release/firmware-HELTEC-US-0.0.3.bin". You should see something like this:
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".
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.
You should see something like this:
```
~/development/meshtastic/meshtastic-esp32$ esptool.py write_flash 0x10000 release/firmware-HELTEC-US-0.0.3.bin
kevinh@kevin-server:~/development/meshtastic/meshtastic-esp32/release/latest$ ./device-install.sh firmware-TBEAM-US-0.1.8.bin
Trying to flash firmware-TBEAM-US-0.1.8.bin, but first erasing and writing system information
esptool.py v2.6
Found 2 serial ports
Serial port /dev/ttyUSB0
Connecting......
Connecting........____
Detecting chip type... ESP32
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
MAC: 24:6f:28:b5:36:71
MAC: 24:6f:28:b2:01:6c
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 921600
Changed.
Erasing flash (this may take a while)...
Chip erase completed successfully in 6.1s
Hard resetting via RTS pin...
esptool.py v2.6
Found 2 serial ports
Serial port /dev/ttyUSB0
Connecting.......
Detecting chip type... ESP32
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
MAC: 24:6f:28:b2:01:6c
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 921600
Changed.
Configuring flash size...
Auto-detected Flash size: 8MB
Compressed 1184800 bytes to 652635...
Wrote 1184800 bytes (652635 compressed) at 0x00010000 in 57.6 seconds (effective 164.5 kbit/s)...
Auto-detected Flash size: 4MB
Flash params set to 0x0220
Compressed 61440 bytes to 11950...
Wrote 61440 bytes (11950 compressed) at 0x00001000 in 0.2 seconds (effective 3092.4 kbit/s)...
Hash of data verified.
Leaving...
Hard resetting via RTS pin...
esptool.py v2.6
Found 2 serial ports
Serial port /dev/ttyUSB0
Connecting.....
Detecting chip type... ESP32
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
MAC: 24:6f:28:b2:01:6c
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 921600
Changed.
Configuring flash size...
Auto-detected Flash size: 4MB
Compressed 1223568 bytes to 678412...
Wrote 1223568 bytes (678412 compressed) at 0x00010000 in 10.7 seconds (effective 912.0 kbit/s)...
Hash of data verified.
Leaving...
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.
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
The source code for the (optional) Meshtastic Android app is [here](https://github.com/geeksville/Meshtastic-Android).
Soon our first alpha release of will be released here:
The source code for the (optional) Meshtastic Android app is [here](https://github.com/meshtastic/Meshtastic-Android).
Alpha test builds are current available by opting into our alpha test group. See (www.meshtastic.org) for instructions.
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)
# Development
The following sections are probably only interesting if you want to join us in developing the software.
## 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).
We'd love to have you join us on this merry little project. Please see our [development documents](./docs/software/sw-design.md) and [join us in our discussion forum](https://meshtastic.discourse.group/).
## Build instructions
This project uses the simple PlatformIO build system. You can use the IDE, but for brevity
in these instructions I describe use of their command line tool.
# Credits
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. Plug the radio into your USB port
4. Type "pio run -t upload" (This command will fetch dependencies, build the project and install it on the board via USB)
5. 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
This project is run by volunteers. Past contributors include:
- @astro-arphid: Added support for 433MHz radios in europe.
- @claesg: Various documentation fixes and 3D print enclosures
- @girtsf: Lots of improvements
- @spattinson: Fixed interrupt handling for the AXP192 part
# IMPORTANT DISCLAIMERS AND FAQ
For a listing of currently missing features and a FAQ click [here](docs/faq.md).
Copyright 2019 Geeksville Industries, LLC. GPL V3 Licensed.

178
TODO.md
View File

@@ -1,178 +0,0 @@
# High priority
Items to complete before the first alpha release.
* have state machine properly enter deep sleep based on loss of mesh and phone comms
* default to enter deep sleep if no LORA received for two hours (indicates user has probably left the meshS)
* if the phone doesn't read fromradio mailbox within X seconds, assume the phone is gone and we can stop queing location msgs
for it (because it will redownload the nodedb when it comes back)
* 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.
F95::canSleep say no if we are busy receiving a message
* retest BLE software update for both board types
* send note about Adafruit Clue
* send note to the guy who designed the cases
* remeasure wake time power draws now that we run CPU down at 80MHz
# Medium priority
Items to complete before the first beta release.
* turn light sleep on aggressively (while lora is on but BLE off)
* sync wake windows to gps time
* research and implement better mesh algorithm
* the BLE stack is leaking about 200 bytes each time we go to light sleep
* use gps sleep mode instead of killing its power (to allow fast position when we wake)
* leave lora receiver always on
* 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
* Use the Periodic class for both position and user periodic broadcasts
* make debug info screen show real data (including battery level & charging)
* don't forward redundant pings or ping responses to the phone, it just wastes phone battery
* don't treat north as up, instead adjust shown bearings for our guess at the users heading (i.e. subtract one from the other)
* answer to pings (because some other user is looking at our nodeinfo) with our latest location
* show radio and gps signal strength as an image
* only BLE advertise for a short time after the screen is on and button pressed - to save power and prevent people for sniffing for our BT app.
* use https://platformio.org/lib/show/1260/OneButton if necessary
* make an about to sleep screen
* don't send location packets if we haven't moved
* scrub default radio config settings for bandwidth/range/speed
* add basic crypto - http://rweather.github.io/arduinolibs/crypto.html with speck https://www.airspayce.com/mikem/arduino/RadioHead/rf95_encrypted_client_8pde-example.html
* override peekAtMessage so we can see any messages that pass through our node (even if not broadcast)? would that be useful?
* sendToMesh can currently block for a long time, instead have it just queue a packet for a radio freertos thread
* How do avalanche beacons work? Could this do that as well? possibly by using beacon mode feature of the RF95?
* use std::map<NodeInfo*, std::string> in node db
* make a HAM build: yep - that's a great idea. I'll add it to the TODO. should be pretty painless - just a new frequency list, a bool to say 'never do encryption' and use hte callsign as that node's unique id. -from Girts
* add frequency hopping
* publish update articles on the web
# Pre-beta priority
During the beta timeframe the following improvements 'would be nice' (and yeah - I guess some of these items count as features, but it is a hobby project ;-) )
* Figure out why the RF95 ISR is never seeing RH_RF95_VALID_HEADER, so it is not protecting our rx packets from getting stomped on by sends
* use BLEDevice::setPower to lower our BLE transmit power - extra range doesn't help us, it costs amps and it increases snoopability
* make an install script to let novices install software on their boards
* fix the frequency error reading in the RF95 RX code (can't do floating point math in an ISR ;-)
* See CustomRF95::send and fix the problem of dropping partially received packets if we want to start sending
* swap out speck for hw-accelerated full AES https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/esp32/hwcrypto/aes.h
* use variable length arduino Strings in protobufs (instead of current fixed buffers)
* don't even power on bluetooth until we have some data to send to the android phone. Most of the time we should be sleeping in a lowpower "listening for lora" only mode. Once we have some packets for the phone, then power on bluetooth
until the phone pulls those packets. Ever so often power on bluetooth just so we can see if the phone wants to send some packets. Possibly might need ULP processor to help with this wake process.
* do hibernation mode to get power draw down to 2.5uA https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/
* make sure main cpu is not woken for packets with bad crc or not addressed to this node - do that in the radio hw
* enable fast init inside the gps chip
* triple check fcc compliance
* pick channel center frequency based on name? "dolphin" would hash to 900Mhz, "cat" to 905MHz etc? Or is that too opaque?
* scan to find channels with low background noise?
* share channel settings over Signal (or qr code) by embedding an an URL which is handled by the MeshUtil app.
# Low priority
Items after the first final candidate release.
* Use CAD mode of the RF95 to automatically find low noise channels
* read the PMU battery fault indicators and blink/led/warn user on screen
* make a no bluetooth configured yet screen - include this screen in the loop if the user hasn't yet paired
* the AXP debug output says it is trying to charge at 700mA, but the max I've seen is 180mA, so AXP registers probably need to be set to tell them the circuit can only provide 300mAish max. So that the low charge rate kicks in faster and we don't wear out batteries.
* increase the max charging rate a bit for 18650s, currently it limits to 180mA (at 4V). Work backwards from the 500mA USB limit (at 5V) and let the AXP charge at that rate.
* if radio params change fundamentally, discard the nodedb
* discard very old nodedb records (> 1wk)
* using the genpartitions based table doesn't work on TTGO so for now I stay with my old memory map
* We let anyone BLE scan for us (FIXME, perhaps only allow that until we are paired with a phone and configured)
* use two different buildenv flags for ttgo vs lora32. https://docs.platformio.org/en/latest/ide/vscode.html#key-bindings
* sim gps data for testing nodes that don't have hardware
* have android provide position data for nodes that don't have gps
* do debug serial logging to android over bluetooth
* break out my bluetooth OTA software as a seperate library so others can use it
* Heltec LoRa32 has 8MB flash, use a bigger partition table if needed - TTGO is 4MB but has PSRAM
* add a watchdog timer
* fix GPS.zeroOffset calculation it is wrong
* handle millis() rollover in GPS.getTime - otherwise we will break after 50 days
* reneable the bluetooth battery level service on the T-BEAM, because we can read battery level there
* report esp32 device code bugs back to the mothership via android
# Done
* change the partition table to take advantage of the 4MB flash on the wroom: http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
* wrap in nice MeshRadio class
* add mesh send & rx
* make message send from android go to service, then to mesh radio
* make message receive from radio go through to android
* test loopback tx/rx path code without using radio
* notify phone when rx packets arrive, currently the phone polls at startup only
* figure out if we can use PA_BOOST - yes, it seems to be on both boards
* implement new ble characteristics
* have MeshService keep a node DB by sniffing user messages
* have a state machine return the correct FromRadio packet to the phone, it isn't always going to be a MeshPacket. Do a notify on fromnum to force the radio to read our state machine generated packets
* send my_node_num when phone sends WantsNodes
* have meshservice periodically send location data on mesh (if device has a GPS)
* implement getCurrentTime() - set based off gps but then updated locally
* make default owner record have valid usernames
* message loop between node 0x28 and 0x7c
* check in my radiolib fixes
* figure out what is busted with rx
* send our owner info at boot, reply if we see anyone send theirs
* add manager layers
* confirm second device receives that gps message and updates device db
* send correct hw vendor in the bluetooth info - needed so the android app can update different radio models
* correctly map nodeids to nodenums, currently we just do a proof of concept by always doing a broadcast
* add interrupt detach/sleep mode config to lora radio so we can enable deepsleep without panicing
* make jtag work on second board
* implement regen owner and radio prefs
* use a better font
* make nice screens (boot, about to sleep, debug info (gps signal, #people), latest text, person info - one frame per person on network)
* turn framerate from ui->state.frameState to 1 fps (or less) unless in transition
* switch to my gui layout manager
* make basic gui. different screens: debug, one page for each user in the user db, last received text message
* make button press cycle between screens
* save our node db on entry to sleep
* fix the logo
* sent/received packets (especially if a node was just reset) have variant of zero sometimes - I think there is a bug (race-condtion?) in the radio send/rx path.
* DONE dynamic nodenum assignment tasks
* make jtag debugger id stable: https://askubuntu.com/questions/49910/how-to-distinguish-between-identical-usb-to-serial-adapters
* reported altitude is crap
* good tips on which bands might be more free https://github.com/TheThingsNetwork/ttn/issues/119
* finish power measurements (GPS on during sleep vs LCD on during sleep vs LORA on during sleep) and est battery life
* make screen sleep behavior work
* make screen advance only when a new node update arrives, a new text arrives or the user presses a button, turn off screen after a while
* after reboot, channel number is getting reset to zero! fix!
* send user and location events much less often
* send location (or if not available user) when the user wakes the device from display sleep (both for testing and to improve user experience)
* make real implementation of getNumOnlineNodes
* very occasionally send our position and user packet based on the schedule in the radio info (if for nothing else so that other nodes update last_seen)
* show real text info on the text screen
* apply radio settings from android land
* cope with nodes that have 0xff or 0x00 as the last byte of their mac
* allow setting full radio params from android
* add receive timestamps to messages, inserted by esp32 when message is received but then shown on the phone
* update build to generate both board types
* have node info screen show real info (including distance and heading)
* blink the power led less often
* have radiohead ISR send messages to RX queue directly, to allow that thread to block until we have something to send
* move lora rx/tx to own thread and block on IO
* keep our pseudo time moving forward even if we enter deep sleep (use esp32 rtc)
* for non GPS equipped devices, set time from phone
* GUI on oled hangs for a few seconds occasionally, but comes back
* update local GPS position (but do not broadcast) at whatever rate the GPS is giving it
* don't send our times to other nodes
* don't trust times from other nodes
* draw compass rose based off local walking track
* add requestResponse optional bool - use for location broadcasts when sending tests
* post sample video to signal forum
* support non US frequencies
* send pr https://github.com/ThingPulse/esp8266-oled-ssd1306 to tell them about this project
* document rules for sleep wrt lora/bluetooth/screen/gps. also: if I have text messages (only) for the phone, then give a few seconds in the hopes BLE can get it across before we have to go back to sleep.
* wake from light sleep as needed for our next scheduled periodic task (needed for gps position broadcasts etc)
* turn bluetooth off based on our sleep policy
* blink LED while in LS sleep mode
* scrolling between screens based on press is busted
* Use Neo-M8M API to put it in sleep mode (on hold until my new boards arrive)
* update the prebuilt bins for different regulatory regions
* don't enter NB state if we've recently talked to the phone (to prevent breaking syncing or bluetooth sw update)
* have sw update prevent BLE sleep
* manually delete characteristics/descs

View File

@@ -2,32 +2,65 @@
set -e
VERSION=0.0.4
source bin/version.sh
COUNTRIES="US EU CN JP"
COUNTRIES="US EU433 EU865 CN JP"
#COUNTRIES=US
#COUNTRIES=CN
SRCMAP=.pio/build/esp32/output.map
SRCBIN=.pio/build/esp32/firmware.bin
BOARDS="ttgo-lora32-v2 ttgo-lora32-v1 tbeam heltec"
#BOARDS=tbeam
OUTDIR=release/latest
# We keep all old builds (and their map files in the archive dir)
ARCHIVEDIR=release/archive
rm -f $OUTDIR/firmware*
mkdir -p $OUTDIR/bins $OUTDIR/elfs
rm -f $OUTDIR/bins/*
# build the named environment and copy the bins to the release directory
function do_build {
ENV_NAME=$1
echo "Building for $ENV_NAME with $PLATFORMIO_BUILD_FLAGS"
SRCBIN=.pio/build/$ENV_NAME/firmware.bin
SRCELF=.pio/build/$ENV_NAME/firmware.elf
rm -f $SRCBIN
# The shell vars the build tool expects to find
export HW_VERSION="1.0-$COUNTRY"
export APP_VERSION=$VERSION
export COUNTRY
pio run --jobs 4 --environment $ENV_NAME # -v
cp $SRCBIN $OUTDIR/bins/firmware-$ENV_NAME-$COUNTRY-$VERSION.bin
cp $SRCELF $OUTDIR/elfs/firmware-$ENV_NAME-$COUNTRY-$VERSION.elf
}
for COUNTRY in $COUNTRIES; do
COMMONOPTS="-DAPP_VERSION=$VERSION -DHW_VERSION_$COUNTRY -Wall -Wextra -Wno-missing-field-initializers -Isrc -Os -Wl,-Map,.pio/build/esp32/output.map -DAXP_DEBUG_PORT=Serial"
export PLATFORMIO_BUILD_FLAGS="-DT_BEAM_V10 $COMMONOPTS"
echo "Building with $PLATFORMIO_BUILD_FLAGS"
rm -f $SRCBIN $SRCMAP
pio run # -v
cp $SRCBIN release/firmware-TBEAM-$COUNTRY-$VERSION.bin
cp $SRCMAP release/firmware-TBEAM-$COUNTRY-$VERSION.map
export PLATFORMIO_BUILD_FLAGS="-DHELTEC_LORA32 $COMMONOPTS"
rm -f $SRCBIN $SRCMAP
pio run # -v
cp $SRCBIN release/firmware-HELTEC-$COUNTRY-$VERSION.bin
cp $SRCMAP release/firmware-HELTEC-$COUNTRY-$VERSION.map
for BOARD in $BOARDS; do
do_build $BOARD
done
done
zip release/firmware-$VERSION.zip release/firmware-*-$VERSION.bin
# keep the bins in archive also
cp $OUTDIR/bins/firmware* $OUTDIR/elfs/firmware* $ARCHIVEDIR
cat >$OUTDIR/curfirmwareversion.xml <<XML
<?xml version="1.0" encoding="utf-8"?>
<!-- This file is kept in source control because it reflects the last stable
release. It is used by the android app for forcing software updates. Do not edit.
Generated by bin/buildall.sh -->
<resources>
<string name="cur_firmware_version">$VERSION</string>
</resources>
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
echo BUILT ALL

11
bin/device-install.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
set -e
FILENAME=$1
echo "Trying to flash $FILENAME, but first erasing and writing system information"
esptool.py --baud 921600 erase_flash
esptool.py --baud 921600 write_flash 0x1000 system-info.bin
esptool.py --baud 921600 write_flash 0x10000 $FILENAME

329
bin/exception_decoder.py Executable file
View File

@@ -0,0 +1,329 @@
#!/usr/bin/env python3
"""ESP Exception Decoder
github: https://github.com/janLo/EspArduinoExceptionDecoder
license: GPL v3
author: Jan Losinski
Meshtastic notes:
* original version is at: https://github.com/janLo/EspArduinoExceptionDecoder
* version that's checked into meshtastic repo is based on: https://github.com/me21/EspArduinoExceptionDecoder
which adds in ESP32 Backtrace decoding.
* this also updates the defaults to use ESP32, instead of ESP8266 and defaults to the built firmware.bin
To use, copy the "Backtrace: 0x...." line to a file, e.g., backtrace.txt, then run:
$ bin/exception_decoder.py backtrace.txt
"""
import argparse
import re
import subprocess
from collections import namedtuple
import sys
import os
EXCEPTIONS = [
"Illegal instruction",
"SYSCALL instruction",
"InstructionFetchError: Processor internal physical address or data error during instruction fetch",
"LoadStoreError: Processor internal physical address or data error during load or store",
"Level1Interrupt: Level-1 interrupt as indicated by set level-1 bits in the INTERRUPT register",
"Alloca: MOVSP instruction, if caller's registers are not in the register file",
"IntegerDivideByZero: QUOS, QUOU, REMS, or REMU divisor operand is zero",
"reserved",
"Privileged: Attempt to execute a privileged operation when CRING ? 0",
"LoadStoreAlignmentCause: Load or store to an unaligned address",
"reserved",
"reserved",
"InstrPIFDataError: PIF data error during instruction fetch",
"LoadStorePIFDataError: Synchronous PIF data error during LoadStore access",
"InstrPIFAddrError: PIF address error during instruction fetch",
"LoadStorePIFAddrError: Synchronous PIF address error during LoadStore access",
"InstTLBMiss: Error during Instruction TLB refill",
"InstTLBMultiHit: Multiple instruction TLB entries matched",
"InstFetchPrivilege: An instruction fetch referenced a virtual address at a ring level less than CRING",
"reserved",
"InstFetchProhibited: An instruction fetch referenced a page mapped with an attribute that does not permit instruction fetch",
"reserved",
"reserved",
"reserved",
"LoadStoreTLBMiss: Error during TLB refill for a load or store",
"LoadStoreTLBMultiHit: Multiple TLB entries matched for a load or store",
"LoadStorePrivilege: A load or store referenced a virtual address at a ring level less than CRING",
"reserved",
"LoadProhibited: A load referenced a page mapped with an attribute that does not permit loads",
"StoreProhibited: A store referenced a page mapped with an attribute that does not permit stores"
]
PLATFORMS = {
"ESP8266": "lx106",
"ESP32": "esp32"
}
BACKTRACE_REGEX = re.compile(r"(?:\s+(0x40[0-2](?:\d|[a-f]|[A-F]){5}):0x(?:\d|[a-f]|[A-F]){8})\b")
EXCEPTION_REGEX = re.compile("^Exception \\((?P<exc>[0-9]*)\\):$")
COUNTER_REGEX = re.compile('^epc1=(?P<epc1>0x[0-9a-f]+) epc2=(?P<epc2>0x[0-9a-f]+) epc3=(?P<epc3>0x[0-9a-f]+) '
'excvaddr=(?P<excvaddr>0x[0-9a-f]+) depc=(?P<depc>0x[0-9a-f]+)$')
CTX_REGEX = re.compile("^ctx: (?P<ctx>.+)$")
POINTER_REGEX = re.compile('^sp: (?P<sp>[0-9a-f]+) end: (?P<end>[0-9a-f]+) offset: (?P<offset>[0-9a-f]+)$')
STACK_BEGIN = '>>>stack>>>'
STACK_END = '<<<stack<<<'
STACK_REGEX = re.compile(
'^(?P<off>[0-9a-f]+):\W+(?P<c1>[0-9a-f]+) (?P<c2>[0-9a-f]+) (?P<c3>[0-9a-f]+) (?P<c4>[0-9a-f]+)(\W.*)?$')
StackLine = namedtuple("StackLine", ["offset", "content"])
class ExceptionDataParser(object):
def __init__(self):
self.exception = None
self.epc1 = None
self.epc2 = None
self.epc3 = None
self.excvaddr = None
self.depc = None
self.ctx = None
self.sp = None
self.end = None
self.offset = None
self.stack = []
def _parse_backtrace(self, line):
if line.startswith('Backtrace:'):
self.stack = [StackLine(offset=0, content=(addr,)) for addr in BACKTRACE_REGEX.findall(line)]
return None
return self._parse_backtrace
def _parse_exception(self, line):
match = EXCEPTION_REGEX.match(line)
if match is not None:
self.exception = int(match.group('exc'))
return self._parse_counters
return self._parse_exception
def _parse_counters(self, line):
match = COUNTER_REGEX.match(line)
if match is not None:
self.epc1 = match.group("epc1")
self.epc2 = match.group("epc2")
self.epc3 = match.group("epc3")
self.excvaddr = match.group("excvaddr")
self.depc = match.group("depc")
return self._parse_ctx
return self._parse_counters
def _parse_ctx(self, line):
match = CTX_REGEX.match(line)
if match is not None:
self.ctx = match.group("ctx")
return self._parse_pointers
return self._parse_ctx
def _parse_pointers(self, line):
match = POINTER_REGEX.match(line)
if match is not None:
self.sp = match.group("sp")
self.end = match.group("end")
self.offset = match.group("offset")
return self._parse_stack_begin
return self._parse_pointers
def _parse_stack_begin(self, line):
if line == STACK_BEGIN:
return self._parse_stack_line
return self._parse_stack_begin
def _parse_stack_line(self, line):
if line != STACK_END:
match = STACK_REGEX.match(line)
if match is not None:
self.stack.append(StackLine(offset=match.group("off"),
content=(match.group("c1"), match.group("c2"), match.group("c3"),
match.group("c4"))))
return self._parse_stack_line
return None
def parse_file(self, file, platform, stack_only=False):
if platform == 'ESP32':
func = self._parse_backtrace
else:
func = self._parse_exception
if stack_only:
func = self._parse_stack_begin
for line in file:
func = func(line.strip())
if func is None:
break
if func is not None:
print("ERROR: Parser not complete!")
sys.exit(1)
class AddressResolver(object):
def __init__(self, tool_path, elf_path):
self._tool = tool_path
self._elf = elf_path
self._address_map = {}
def _lookup(self, addresses):
cmd = [self._tool, "-aipfC", "-e", self._elf] + [addr for addr in addresses if addr is not None]
if sys.version_info[0] < 3:
output = subprocess.check_output(cmd)
else:
output = subprocess.check_output(cmd, encoding="utf-8")
line_regex = re.compile("^(?P<addr>[0-9a-fx]+): (?P<result>.+)$")
last = None
for line in output.splitlines():
line = line.strip()
match = line_regex.match(line)
if match is None:
if last is not None and line.startswith('(inlined by)'):
line = line [12:].strip()
self._address_map[last] += ("\n \-> inlined by: " + line)
continue
if match.group("result") == '?? ??:0':
continue
self._address_map[match.group("addr")] = match.group("result")
last = match.group("addr")
def fill(self, parser):
addresses = [parser.epc1, parser.epc2, parser.epc3, parser.excvaddr, parser.sp, parser.end, parser.offset]
for line in parser.stack:
addresses.extend(line.content)
self._lookup(addresses)
def _sanitize_addr(self, addr):
if addr.startswith("0x"):
addr = addr[2:]
fill = "0" * (8 - len(addr))
return "0x" + fill + addr
def resolve_addr(self, addr):
out = self._sanitize_addr(addr)
if out in self._address_map:
out += ": " + self._address_map[out]
return out
def resolve_stack_addr(self, addr, full=True):
addr = self._sanitize_addr(addr)
if addr in self._address_map:
return addr + ": " + self._address_map[addr]
if full:
return "[DATA (0x" + addr + ")]"
return None
def print_addr(name, value, resolver):
print("{}:{} {}".format(name, " " * (8 - len(name)), resolver.resolve_addr(value)))
def print_stack_full(lines, resolver):
print("stack:")
for line in lines:
print(line.offset + ":")
for content in line.content:
print(" " + resolver.resolve_stack_addr(content))
def print_stack(lines, resolver):
print("stack:")
for line in lines:
for content in line.content:
out = resolver.resolve_stack_addr(content, full=False)
if out is None:
continue
print(out)
def print_result(parser, resolver, platform, full=True, stack_only=False):
if platform == 'ESP8266' and not stack_only:
print('Exception: {} ({})'.format(parser.exception, EXCEPTIONS[parser.exception]))
print("")
print_addr("epc1", parser.epc1, resolver)
print_addr("epc2", parser.epc2, resolver)
print_addr("epc3", parser.epc3, resolver)
print_addr("excvaddr", parser.excvaddr, resolver)
print_addr("depc", parser.depc, resolver)
print("")
print("ctx: " + parser.ctx)
print("")
print_addr("sp", parser.sp, resolver)
print_addr("end", parser.end, resolver)
print_addr("offset", parser.offset, resolver)
print("")
if full:
print_stack_full(parser.stack, resolver)
else:
print_stack(parser.stack, resolver)
def parse_args():
parser = argparse.ArgumentParser(description="decode ESP Stacktraces.")
parser.add_argument("-p", "--platform", help="The platform to decode from", choices=PLATFORMS.keys(),
default="ESP32")
parser.add_argument("-t", "--tool", help="Path to the xtensa toolchain",
default="~/.platformio/packages/toolchain-xtensa32/")
parser.add_argument("-e", "--elf", help="path to elf file",
default=".pio/build/esp32/firmware.elf")
parser.add_argument("-f", "--full", help="Print full stack dump", action="store_true")
parser.add_argument("-s", "--stack_only", help="Decode only a stractrace", action="store_true")
parser.add_argument("file", help="The file to read the exception data from ('-' for STDIN)", default="-")
return parser.parse_args()
if __name__ == "__main__":
args = parse_args()
if args.file == "-":
file = sys.stdin
else:
if not os.path.exists(args.file):
print("ERROR: file " + args.file + " not found")
sys.exit(1)
file = open(args.file, "r")
addr2line = os.path.join(os.path.abspath(os.path.expanduser(args.tool)),
"bin/xtensa-" + PLATFORMS[args.platform] + "-elf-addr2line")
if os.name == 'nt':
addr2line += '.exe'
if not os.path.exists(addr2line):
print("ERROR: addr2line not found (" + addr2line + ")")
elf_file = os.path.abspath(os.path.expanduser(args.elf))
if not os.path.exists(elf_file):
print("ERROR: elf file not found (" + elf_file + ")")
parser = ExceptionDataParser()
resolver = AddressResolver(addr2line, elf_file)
parser.parse_file(file, args.platform, args.stack_only)
resolver.fill(parser)
print_result(parser, resolver, args.platform, args.full, args.stack_only)

6
bin/program-release-heltec.sh Executable file
View File

@@ -0,0 +1,6 @@
set -e
source bin/version.sh
esptool.py --baud 921600 write_flash 0x10000 release/latest/bins/firmware-heltec-US-$VERSION.bin

6
bin/program-release-tbeam.sh Executable file
View File

@@ -0,0 +1,6 @@
set -e
source bin/version.sh
esptool.py --baud 921600 write_flash 0x10000 release/latest/bins/firmware-tbeam-US-$VERSION.bin

1
bin/read-system-info.sh Executable file
View File

@@ -0,0 +1 @@
esptool.py --baud 921600 read_flash 0x1000 0xf000 system-info.img

View File

@@ -1 +1,6 @@
/home/kevinh/packages/nanopb-0.4.1-linux-x86/generator-bin/protoc --nanopb_out=-v:src -I=../MeshUtil/app/src/main/proto mesh.proto
#!/bin/bash
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

1
bin/run-1-monitor.sh Executable file
View File

@@ -0,0 +1 @@
pio run --upload-port /dev/ttyUSB1 -t upload -t monitor

1
bin/start-terminal1.sh Executable file
View File

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

3
bin/version.sh Normal file
View File

@@ -0,0 +1,3 @@
export VERSION=0.4.3

View File

@@ -0,0 +1,9 @@
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.geeksville.mesh",
"sha256_cert_fingerprints":
["D0:05:E7:8B:D2:1B:FA:94:56:1D:6B:90:EB:53:07:1A:74:4F:D9:C2:6F:13:87:6A:D9:17:4F:C2:59:48:02:9D", "42:17:52:DC:57:40:38:B5:6B:86:61:1C:2F:47:DB:2B:0F:A2:EA:59:E1:18:9C:AA:90:8D:37:D6:CD:40:0E:BB", "A9:3B:45:65:68:C1:75:DB:08:00:A0:9F:06:77:7F:89:2D:81:24:32:AD:B8:A3:DF:73:BC:3E:7F:06:C8:0C:6D"]
}
}]

View File

@@ -1,78 +1,75 @@
# What is Meshtastic?
Meshtastic is a project that lets you use
inexpensive ($30 ish) GPS radios as an extensible, super long battery life mesh GPS communicator. These radios are great for hiking, skiing, paragliding -
essentially any hobby where you don't have reliable internet access. Each member of your private mesh can always see the location and distance of all other
members and any text messages sent to your group chat.
inexpensive (\$30 ish) GPS radios as an extensible, 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.
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.
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.
Note: Questions after reading this? See our new [forum](https://meshtastic.discourse.group/).
### Uses
* Outdoor sports where cellular coverage is limited. (Hiking, Skiing, Boating, Paragliding, Gliders etc..)
* 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 ;-)
- Outdoor sports where cellular coverage is limited. (Hiking, Skiing, Boating, Paragliding, Gliders etc..)
- 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 ;-)
[![Youtube video demo](desk-video-screenshot.png)](https://www.youtube.com/watch?v=WlNbMbVZlHI "Meshtastic early demo")
### Features
Not all of these features are fully implemented yet - see below. But they should be in by the time we decide to call this project beta (three months?)
* 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
* 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.
* Very easy sharing of private secured channels. Just share a special link or QR code with other users and they can join your encrypted mesh
Not all of these features are fully implemented yet - see **important** disclaimers below. But they should be in by the time we decide to call this project beta (three months?)
This project is currently in early alpha - if you have questions please join our chat [![Join the chat at https://gitter.im/Meshtastic/community](https://badges.gitter.im/Meshtastic/community.svg)](https://gitter.im/Meshtastic/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge).
- 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
- 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.
- 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 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](https://gitter.im/Meshtastic/community).
This project is currently in early alpha - if you have questions please [join our discussion forum](https://meshtastic.discourse.group/).
# Update 1
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/).
* 02/20/2020 - Our first alpha release of the radio software is ready for early users. If you'd like to try it, we'd love your feedback. Click [here](https://github.com/geeksville/Meshtastic-esp32/blob/master/README.md) for instructions.
# 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.
- 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.
- 02/20/2020 - Our first alpha release (0.0.3) of the radio software is ready brave early people.
## Meshtastic Android app
Soon our (optional) companion Android application will be released here:
Once out of alpha the companion Android application will be released 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)
If you would like to join our super bleeding-edge alpha test group for this app, we'd love to have you. Three steps:
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:
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.
3. If you encounter any problems or have questions, post in our gitter chat and we'll help.
3. If you encounter any problems or have questions, post in our [forum](https://meshtastic.discourse.group/) and we'll help.
If you'd like to help with development, the source code is [on github](https://github.com/geeksville/Meshtastic-Android).
If you'd like to help with development, the source code is [on github](https://github.com/meshtastic/Meshtastic-Android).
## Supported hardware
We currently support two brands of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4000119152086.html) and the [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/). Most users should buy the T-Beam and a 18650 battery (total cost less than $35). Make
sure to buy the frequency range which is legal for your country. For the USA, you should buy the 915MHz version. Getting a version that include a screen
is optional, but highly recommended.
Instructions for installing prebuilt firmware can be found [here](https://github.com/geeksville/Meshtastic-esp32/blob/master/README.md).
We currently support two brands of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4000119152086.html) and the [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/). Most people should buy the T-Beam and a 18650 battery (total cost less than \$35). Make
sure to buy the frequency range which is legal for your country. For the USA, you should buy the 915MHz version. Getting a version that include a screen is optional, but highly recommended.
For a nice TTGO 3D printable case see this [design](https://www.thingiverse.com/thing:3773717) by [bsiege](https://www.thingiverse.com/bsiege).
For a nice Heltec 3D printable case see this [design](https://www.thingiverse.com/thing:3125854) by [ornotermes](https://www.thingiverse.com/ornotermes).
Instructions for installing prebuilt firmware can be found [here](https://github.com/meshtastic/Meshtastic-esp32/blob/master/README.md).
# Disclaimers
For a nice printable cases:
This project is still pretty young but moving at a pretty good pace. Not all features are fully implemented in the current alpha builds.
Most of these problems should be solved by the beta release:
1. TTGO T-Beam V0 see this [design](https://www.thingiverse.com/thing:3773717) by [bsiege](https://www.thingiverse.com/bsiege).
2. TTGO T_Beam V1 see this [design](https://www.thingiverse.com/thing:3830711) by [rwanrooy](https://www.thingiverse.com/rwanrooy) or this [remix](https://www.thingiverse.com/thing:3949330) by [8ung](https://www.thingiverse.com/8ung)
3. Heltec Lora32 see this [design](https://www.thingiverse.com/thing:3125854) by [ornotermes](https://www.thingiverse.com/ornotermes).
* 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
* A number of (straightforward) software work items have to be completed before battery life matches our measurements, currently battery life is about two days. Join us on chat if you want the spreadsheet of power measurements/calculations.
* The current Android GUI is pretty ugly still
* The Android API needs to be documented better
* The Bluetooth API needs to be documented better
* The mesh protocol is turned off for now, currently we only send packets one hop distant
* No one has written an iOS app yet ;-)
# IMPORTANT DISCLAIMERS AND FAQ
For more details see the [device software TODO](https://github.com/geeksville/Meshtastic-esp32/blob/master/TODO.md) or the [Android app TODO](https://github.com/geeksville/Meshtastic-Android/blob/master/TODO.md).
For a listing of currently missing features and a FAQ click [here](faq.md).

View File

@@ -3,3 +3,6 @@ theme: jekyll-theme-cayman
title: Meshtastic
description: An opensource hiking, pilot, skiing, Signal-App-extending GPS mesh communicator
google_analytics: G-DRZ5H5EXHV
include: [".well-known"]

34
docs/faq.md Normal file
View File

@@ -0,0 +1,34 @@
# Disclaimers
This project is still pretty young but moving at a pretty good pace. Not all features are fully implemented in the current alpha builds.
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
- 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) ;-)
For more details see the [device software TODO](https://github.com/meshtastic/Meshtastic-esp32/blob/master/docs/software/TODO.md) or the [Android app TODO](https://github.com/meshtastic/Meshtastic-Android/blob/master/TODO.md).
# FAQ
If you have a question missing from this faq, please [ask in our discussion forum](https://meshtastic.discourse.group/). And if you are feeling extra generous send in a pull-request for this faq.md with whatever we answered ;-).
## Q: Which of the various supported radios should I buy?
Basically you just need the radio + (optional but recommended) battery. The TBEAM is usually better because it has gps and huge battery socket. The Heltec is basically the same hardware but without the GPS (the phone provides position data to the radio in that case, so the behavior is similar - but it does burn some battery in the phone). Also the battery for the Heltec can be smaller.
In addition to Aliexpress, (banggood.com) usually has stock and faster shipping, or Amazon. If buying a TBEAM, make sure to buy a version that includes the OLED screen - this project doesn't absolutely require the screen, but we use it if is installed.
@claesg has added links to various 3D printable cases, you can see them at (www.meshtastic.org).
## Q: Do you have plans to commercialize this project
Nope. though if some other person/group wanted to use this software and a more customized device we think that would be awesome (as long as they obey the GPL license).
## Q: Does this project use patented algorithms?
(Kindly borrowed from the geeks at [ffmpeg](http://ffmpeg.org/legal.html))
We do not know, we are not lawyers so we are not qualified to answer this. Also we have never read patents to implement any part of this, so even if we were qualified we could not answer it as we do not know what is patented. Furthermore the sheer number of software patents makes it impossible to read them all so no one (lawyer or not) could answer such a question with a definite no. We are merely geeks experimenting on a fun and free project.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -5,7 +5,7 @@ We don't collect any personal identifying information.
If you have opted-in to analytics (thank you - that helps us know what things we need to improve), we'll receive anonymized information about user behavior. i.e. which screens you used in the app etc... We never
capture usernames, the contents of your texts or your location.
This is an open-source project run by hobbyists and we try to be completely transparent. If you have questions on this policy, please file [a github issue](https://github.com/geeksville/meshtastic-esp32/issues) and we'll reply/clarify/correct.
This is an open-source project run by hobbyists and we try to be completely transparent. If you have questions on this policy, please file [a github issue](https://github.com/meshtastic/meshtastic-esp32/issues) and we'll reply/clarify/correct.
Keep being awesome!

View File

@@ -10,6 +10,13 @@ See [this site](https://www.rfwireless-world.com/Tutorials/LoRa-channels-list.ht
The maximum power allowed is +14dBM.
### 433 MHz
There are eight channels defined with a 0.2 MHz gap between them.
Channel zero starts at 433.175 MHz
### 870 MHz
There are eight channels defined with a 0.3 MHz gap between them.
Channel zero starts at 865.20 MHz

207
docs/software/TODO.md Normal file
View File

@@ -0,0 +1,207 @@
# High priority
Items to complete soon (next couple of alpha releases).
- lower wait_bluetooth_secs to 30 seconds once we have the GPS power on (but GPS in sleep mode) across light sleep. For the time
being I have it set at 2 minutes to ensure enough time for a GPS lock from scratch.
- 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.
- possibly switch to https://github.com/SlashDevin/NeoGPS for gps comms
- good source of battery/signal/gps icons https://materialdesignicons.com/
- research and implement better mesh algorithm - investigate changing routing to https://github.com/sudomesh/LoRaLayer2 ?
- check fcc rules on duty cycle. we might not need to freq hop. https://www.sunfiretesting.com/LoRa-FCC-Certification-Guide/
- use fuse bits to store the board type and region. So one load can be used on all boards
- the BLE stack is leaking about 200 bytes each time we go to light sleep
- 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.
- publish update articles on the web
# Pre-beta priority
During the beta timeframe the following improvements 'would be nice' (and yeah - I guess some of these items count as features, but it is a hobby project ;-) )
- If the phone doesn't read fromradio mailbox within X seconds, assume the phone is gone and we can stop queing location msgs
for it (because it will redownload the nodedb when it comes back)
- Figure out why the RF95 ISR is never seeing RH_RF95_VALID_HEADER, so it is not protecting our rx packets from getting stomped on by sends
- fix the frequency error reading in the RF95 RX code (can't do floating point math in an ISR ;-)
- See CustomRF95::send and fix the problem of dropping partially received packets if we want to start sending
- make sure main cpu is not woken for packets with bad crc or not addressed to this node - do that in the radio hw
- triple check fcc compliance
- pick channel center frequency based on channel name? "dolphin" would hash to 900Mhz, "cat" to 905MHz etc? allows us to hide the concept of channel # from hte user.
- scan to find channels with low background noise? (Use CAD mode of the RF95 to automatically find low noise channels)
- make a no bluetooth configured yet screen - include this screen in the loop if the user hasn't yet paired
- if radio params change fundamentally, discard the nodedb
- reneable the bluetooth battery level service on the T-BEAM, because we can read battery level there
# Spinoff project ideas
- an open source version of https://www.burnair.ch/skynet/
- a paragliding app like http://airwhere.co.uk/
- a version with a solar cell for power, just mounted high to permanently provide routing for nodes in a valley. Someone just pointed me at disaster.radio
- How do avalanche beacons work? Could this do that as well? possibly by using beacon mode feature of the RF95?
- provide generalized (but slow) internet message forwarding servie if one of our nodes has internet connectivity
# Low priority
Items after the first final candidate release.
- use variable length arduino Strings in protobufs (instead of current fixed buffers)
- use BLEDevice::setPower to lower our BLE transmit power - extra range doesn't help us, it costs amps and it increases snoopability
- make an install script to let novices install software on their boards
- use std::map<NodeInfo\*, std::string> in node db
- make a HAM build: yep - that's a great idea. I'll add it to the TODO. should be pretty painless - just a new frequency list, a bool to say 'never do encryption' and use hte callsign as that node's unique id. -from Girts
- don't forward redundant pings or ping responses to the phone, it just wastes phone battery
- use https://platformio.org/lib/show/1260/OneButton if necessary
- don't send location packets if we haven't moved
- scrub default radio config settings for bandwidth/range/speed
- answer to pings (because some other user is looking at our nodeinfo) with our latest location (not a stale location)
- show radio and gps signal strength as an image
- only BLE advertise for a short time after the screen is on and button pressed - to save power and prevent people for sniffing for our BT app.
- make mesh aware network timing state machine (sync wake windows to gps time)
- split out the software update utility so other projects can use it. Have the appload specify the URL for downloads.
- read the PMU battery fault indicators and blink/led/warn user on screen
- the AXP debug output says it is trying to charge at 700mA, but the max I've seen is 180mA, so AXP registers probably need to be set to tell them the circuit can only provide 300mAish max. So that the low charge rate kicks in faster and we don't wear out batteries.
- increase the max charging rate a bit for 18650s, currently it limits to 180mA (at 4V). Work backwards from the 500mA USB limit (at 5V) and let the AXP charge at that rate.
- discard very old nodedb records (> 1wk)
- using the genpartitions based table doesn't work on TTGO so for now I stay with my old memory map
- We let anyone BLE scan for us (FIXME, perhaps only allow that until we are paired with a phone and configured)
- use two different buildenv flags for ttgo vs lora32. https://docs.platformio.org/en/latest/ide/vscode.html#key-bindings
- sim gps data for testing nodes that don't have hardware
- do debug serial logging to android over bluetooth
- break out my bluetooth OTA software as a seperate library so others can use it
- Heltec LoRa32 has 8MB flash, use a bigger partition table if needed - TTGO is 4MB but has PSRAM
- add a watchdog timer
- handle millis() rollover in GPS.getTime - otherwise we will break after 50 days
- report esp32 device code bugs back to the mothership via android
# Done
- change the partition table to take advantage of the 4MB flash on the wroom: http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
- wrap in nice MeshRadio class
- add mesh send & rx
- make message send from android go to service, then to mesh radio
- make message receive from radio go through to android
- test loopback tx/rx path code without using radio
- notify phone when rx packets arrive, currently the phone polls at startup only
- figure out if we can use PA_BOOST - yes, it seems to be on both boards
- implement new ble characteristics
- have MeshService keep a node DB by sniffing user messages
- have a state machine return the correct FromRadio packet to the phone, it isn't always going to be a MeshPacket. Do a notify on fromnum to force the radio to read our state machine generated packets
- send my_node_num when phone sends WantsNodes
- have meshservice periodically send location data on mesh (if device has a GPS)
- implement getCurrentTime() - set based off gps but then updated locally
- make default owner record have valid usernames
- message loop between node 0x28 and 0x7c
- check in my radiolib fixes
- figure out what is busted with rx
- send our owner info at boot, reply if we see anyone send theirs
- add manager layers
- confirm second device receives that gps message and updates device db
- send correct hw vendor in the bluetooth info - needed so the android app can update different radio models
- correctly map nodeids to nodenums, currently we just do a proof of concept by always doing a broadcast
- add interrupt detach/sleep mode config to lora radio so we can enable deepsleep without panicing
- make jtag work on second board
- implement regen owner and radio prefs
- use a better font
- make nice screens (boot, about to sleep, debug info (gps signal, #people), latest text, person info - one frame per person on network)
- turn framerate from ui->state.frameState to 1 fps (or less) unless in transition
- switch to my gui layout manager
- make basic gui. different screens: debug, one page for each user in the user db, last received text message
- make button press cycle between screens
- save our node db on entry to sleep
- fix the logo
- sent/received packets (especially if a node was just reset) have variant of zero sometimes - I think there is a bug (race-condtion?) in the radio send/rx path.
- DONE dynamic nodenum assignment tasks
- make jtag debugger id stable: https://askubuntu.com/questions/49910/how-to-distinguish-between-identical-usb-to-serial-adapters
- reported altitude is crap
- good tips on which bands might be more free https://github.com/TheThingsNetwork/ttn/issues/119
- finish power measurements (GPS on during sleep vs LCD on during sleep vs LORA on during sleep) and est battery life
- make screen sleep behavior work
- make screen advance only when a new node update arrives, a new text arrives or the user presses a button, turn off screen after a while
- after reboot, channel number is getting reset to zero! fix!
- send user and location events much less often
- send location (or if not available user) when the user wakes the device from display sleep (both for testing and to improve user experience)
- make real implementation of getNumOnlineNodes
- very occasionally send our position and user packet based on the schedule in the radio info (if for nothing else so that other nodes update last_seen)
- show real text info on the text screen
- apply radio settings from android land
- cope with nodes that have 0xff or 0x00 as the last byte of their mac
- allow setting full radio params from android
- add receive timestamps to messages, inserted by esp32 when message is received but then shown on the phone
- update build to generate both board types
- have node info screen show real info (including distance and heading)
- blink the power led less often
- have radiohead ISR send messages to RX queue directly, to allow that thread to block until we have something to send
- move lora rx/tx to own thread and block on IO
- keep our pseudo time moving forward even if we enter deep sleep (use esp32 rtc)
- for non GPS equipped devices, set time from phone
- GUI on oled hangs for a few seconds occasionally, but comes back
- update local GPS position (but do not broadcast) at whatever rate the GPS is giving it
- don't send our times to other nodes
- don't trust times from other nodes
- draw compass rose based off local walking track
- add requestResponse optional bool - use for location broadcasts when sending tests
- post sample video to signal forum
- support non US frequencies
- send pr https://github.com/ThingPulse/esp8266-oled-ssd1306 to tell them about this project
- document rules for sleep wrt lora/bluetooth/screen/gps. also: if I have text messages (only) for the phone, then give a few seconds in the hopes BLE can get it across before we have to go back to sleep.
- wake from light sleep as needed for our next scheduled periodic task (needed for gps position broadcasts etc)
- turn bluetooth off based on our sleep policy
- blink LED while in LS sleep mode
- scrolling between screens based on press is busted
- Use Neo-M8M API to put it in sleep mode (on hold until my new boards arrive)
- update the prebuilt bins for different regulatory regions
- don't enter NB state if we've recently talked to the phone (to prevent breaking syncing or bluetooth sw update)
- have sw update prevent BLE sleep
- manually delete characteristics/descs
- leave lora receiver always on
- protobufs are sometimes corrupted after sleep!
- stay awake while charging
- check gps battery voltage
- if a position report includes ground truth time and we don't have time yet, set our clock from that. It is better than nothing.
- retest BLE software update for both board types
- report on wikifactory
- send note to the guy who designed the cases
- turn light sleep on aggressively (while lora is on but BLE off)
- Use the Periodic class for both position and user periodic broadcasts
- don't treat north as up, instead adjust shown bearings for our guess at the users heading (i.e. subtract one from the other)
- sendToMesh can currently block for a long time, instead have it just queue a packet for a radio freertos thread
- don't even power on bluetooth until we have some data to send to the android phone. Most of the time we should be sleeping in a lowpower "listening for lora" only mode. Once we have some packets for the phone, then power on bluetooth
until the phone pulls those packets. Ever so often power on bluetooth just so we can see if the phone wants to send some packets. Possibly might need ULP processor to help with this wake process.
- do hibernation mode to get power draw down to 2.5uA https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/
- fix GPS.zeroOffset calculation it is wrong
- (needs testing) fixed the following during a plane flight:
Have state machine properly enter deep sleep based on loss of mesh and phone comms.
Default to enter deep sleep if no LORA received for two hours (indicates user has probably left the mesh).
- (fixed I think) text messages are not showing on local screen if screen was on
- add links to todos
- link to the kanban page
- add a getting started page
- finish mesh alg reeval
- ublox gps parsing seems a little buggy (we shouldn't be sending out read solution commands, the device is already broadcasting them)
- turn on gps https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/blob/master/examples/Example18_PowerSaveMode/Example18_PowerSaveMode.ino
- switch gps to 38400 baud https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/blob/master/examples/Example11_ResetModule/Example2_FactoryDefaultsviaSerial/Example2_FactoryDefaultsviaSerial.ino
- Use Neo-M8M API to put it in sleep mode
- use gps sleep mode instead of killing its power (to allow fast position when we wake)
- enable fast lock and low power inside the gps chip
- Make a FAQ
- add a SF12 transmit option for _super_ long range

View File

@@ -0,0 +1,110 @@
# 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.
## 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.
## 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.
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"
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
Each characteristic is listed as follows:
UUID
Properties
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
mailbox.
f75c76d2-129e-4dad-a1dd-7866124401e7
write
toradio - write ToRadio protobufs to this characteristic to send them (up to MAXPACKET len)
ed9da18c-a800-4f66-a670-aa7547e34453
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.
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)
## Other bluetooth services
This document focuses on the core mesh service, but it is worth noting that the following other Bluetooth services are also
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).
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 | |
### DeviceInformationService
Implements the standard BLE contract for this service (has software version, hardware model, serial number, etc...)
### 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)

View File

@@ -0,0 +1,21 @@
# Build instructions
This project uses the simple PlatformIO build system. You can use the IDE, but for brevity
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.
## Decoding stack traces
If you get a crash, you can decode the addresses from the `Backtrace:` line:
1. Save the `Backtrace: 0x....` line to a file, e.g., `backtrace.txt`.
2. Run `bin/exception_decoder.py backtrace.txt` (this uses symbols from the
last `firmware.elf`, so you must be running the same binary that's still in
your `.pio/build` directory).

View File

@@ -0,0 +1,27 @@
(Here's some quick tips on installing the device code from OS-X, thanks to @android606)
First time using LoRa for anything, just checking it out.
I bought a T-Beam on eBay, followed the instructions to install the firmware here:
[https://github.com/meshtastic/Meshtastic-esp32](https://github.com/meshtastic/Meshtastic-esp32)
I'm using a Mac for this, so that might account for differences in the steps to get it working. I just swapped out my SSD last month, I'm using a pretty fresh install of OS X 10.15.3/Catalina.
I got it working fairly smoothly, but there were two hang-ups I thought I'd mention:
1. I am about 0% familiar with Python, so there were some issues getting esptool.py working. Basically, this OS X comes with Python 2.7 and no pip. Pip installed okay, so I used it to install esptool. Esptool appeared to install correctly, but I couldn't get it to work to save my life. Simply typing "esptool.py" doesn't work, and I just don't know enough python to figure out why. For some reason, it installs but isn't in the \$PATH anywhere, and I don't know where it went. Python 2.7 kept giving me warning messages about being old and unsupported, so I figured that might be a hint that I should upgrade.
I ended up doing this:
- brew install pyenv (to install pyenv)
- pyenv install 3.7.7 (to install and select python 3.7.7)
- pyenv global 3.7.7 (to select the new version of python)
- brew install pip (to install pip3)
- pip3 install --upgrade esptool (note I specifically had to use "pip3", not "pip")
...then I was able to execute esptool.py
2. esptool.py didn't work though, because the virtual com port wasn't showing up as a device. I had to install a driver from Silicon Labs, which I got here:
[driver for the CP210X USB to UART bridge from Silicon Labs](https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers)
After I installed that, esptool.py was completely happy and the firmware loaded right up.

95
docs/software/mesh-alg.md Normal file
View File

@@ -0,0 +1,95 @@
# 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/
TODO:
- DONE reread the radiohead mesh implementation - hop to hop acknoledgement 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
- 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
interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf
It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728
https://en.wikipedia.org/wiki/Dynamic_Source_Routing
broadcast solution:
Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent)
Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set
compare to db sync algorithm?
what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies.
If Y were to die, at least the neighbor nodes of Y would have their last known position of Y.
## approach 1
- send all broadcasts with a TTL
- periodically(?) do a survey to find the max TTL that is needed to fully cover the current network.
- to do a study first send a broadcast (maybe our current initial user announcement?) with TTL set to one (so therefore no one will rebroadcast our request)
- survey replies are sent unicast back to us (and intervening nodes will need to keep the route table that they have built up based on past packets)
- count the number of replies to this TTL 1 attempt. That is the number of nodes we can reach without any rebroadcasts
- repeat the study with a TTL of 2 and then 3. stop once the # of replies stops going up.
- it is important for any node to do listen before talk to prevent stomping on other rebroadcasters...
- For these little networks I bet a max TTL would never be higher than 3?
## approach 2
- send a TTL1 broadcast, the replies let us build a list of the nodes (stored as a bitvector?) that we can see (and their rssis)
- we then broadcast out that bitvector (also TTL1) asking "can any of ya'll (even indirectly) see anyone else?"
- if a node can see someone I missed (and they are the best person to see that node), they reply (unidirectionally) with the missing nodes and their rssis (other nodes might sniff (and update their db) based on this reply but they don't have to)
- given that the max number of nodes in this mesh will be like 20 (for normal cases), I bet globally updating this db of "nodenums and who has the best rssi for packets from that node" would be useful
- once the global DB is shared, when a node wants to broadcast, it just sends out its broadcast . the first level receivers then make a decision "am I the best to rebroadcast to someone who likely missed this packet?" if so, rebroadcast
## approach 3
- when a node X wants to know other nodes positions, it broadcasts its position with want_replies=true. Then each of the nodes that received that request broadcast their replies (possibly by using special timeslots?)
- all nodes constantly update their local db based on replies they witnessed.
- after 10s (or whatever) if node Y notices that it didn't hear a reply from node Z (that Y has heard from recently ) to that initial request, that means Z never heard the request from X. Node Y will reply to X on Z's behalf.
- could this work for more than one hop? Is more than one hop needed? Could it work for sending messages (i.e. for a msg sent to Z with want-reply set).
## approach 4
look into the literature for this idea specifically.
- don't view it as a mesh protocol as much as a "distributed db unification problem". When nodes talk to nearby nodes they work together
to update their nodedbs. Each nodedb would have a last change date and any new changes that only one node has would get passed to the
other node. This would nicely allow distant nodes to propogate their position to all other nodes (eventually).
- handle group messages the same way, there would be a table of messages and time of creation.
- when a node has a new position or message to send out, it does a broadcast. All the adjacent nodes update their db instantly (this handles 90% of messages I'll bet).
- Occasionally a node might broadcast saying "anyone have anything newer than time X?" If someone does, they send the diffs since that date.
- essentially everything in this variant becomes broadcasts of "request db updates for >time X - for _all_ or for a particular nodenum" and nodes sending (either due to request or because they changed state) "here's a set of db updates". Every node is constantly trying to
build the most recent version of reality, and if some nodes are too far, then nodes closer in will eventually forward their changes to the distributed db.
- construct non ambigious rules for who broadcasts to request db updates. ideally the algorithm should nicely realize node X can see most other nodes, so they should just listen to all those nodes and minimize the # of broadcasts. the distributed picture of nodes rssi could be useful here?
- possibly view the BLE protocol to the radio the same way - just a process of reconverging the node/msgdb database.
# Old notes
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
It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728
https://en.wikipedia.org/wiki/Dynamic_Source_Routing
broadcast solution:
Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent)
Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set
compare to db sync algorithm?
what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies.
If Y were to die, at least the neighbor nodes of Y would have their last known position of Y.

View File

@@ -1,12 +1,16 @@
This is a mini design doc for various core behaviors...
# Power Management State Machine
# Rules for sleep
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).
## 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
* 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)
@@ -53,8 +57,9 @@ off during light sleep, but there is a TODO item to fix this.
* 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 into LS: If either phone_sds_timeout_secs (default 1 hr) or mesh_sds_timeout_secs (default 1 hr) are exceeded we will lower into SDS mode for sds_secs (default 1 hr) (or a button press).
* Any time we enter LS mode: We stay in that mode for ls_secs (default 1 hr) (or until an interrupt, button press)
* 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...
@@ -78,49 +83,3 @@ General ideas to hit the power draws our spreadsheet predicts. Do the easy ones
* 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.
# Mesh broadcast algoritm
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 inefficent.
## approach 1
* send all broadcasts with a TTL
* periodically(?) do a survey to find the max TTL that is needed to fully cover the current network.
* to do a study first send a broadcast (maybe our current initial user announcement?) with TTL set to one (so therefore no one will rebroadcast our request)
* survey replies are sent unicast back to us (and intervening nodes will need to keep the route table that they have built up based on past packets)
* count the number of replies to this TTL 1 attempt. That is the number of nodes we can reach without any rebroadcasts
* repeat the study with a TTL of 2 and then 3. stop once the # of replies stops going up.
* it is important for any node to do listen before talk to prevent stomping on other rebroadcasters...
* For these little networks I bet a max TTL would never be higher than 3?
## approach 2
* send a TTL1 broadcast, the replies let us build a list of the nodes (stored as a bitvector?) that we can see (and their rssis)
* we then broadcast out that bitvector (also TTL1) asking "can any of ya'll (even indirectly) see anyone else?"
* if a node can see someone I missed (and they are the best person to see that node), they reply (unidirectionally) with the missing nodes and their rssis (other nodes might sniff (and update their db) based on this reply but they don't have to)
* given that the max number of nodes in this mesh will be like 20 (for normal cases), I bet globally updating this db of "nodenums and who has the best rssi for packets from that node" would be useful
* once the global DB is shared, when a node wants to broadcast, it just sends out its broadcast . the first level receivers then make a decision "am I the best to rebroadcast to someone who likely missed this packet?" if so, rebroadcast
## approach 3
* when a node X wants to know other nodes positions, it broadcasts its position with want_replies=true. Then each of the nodes that received that request broadcast their replies (possibly by using special timeslots?)
* all nodes constantly update their local db based on replies they witnessed.
* after 10s (or whatever) if node Y notices that it didn't hear a reply from node Z (that Y has heard from recently ) to that initial request, that means Z never heard the request from X. Node Y will reply to X on Z's behalf.
* could this work for more than one hop? Is more than one hop needed? Could it work for sending messages (i.e. for a msg sent to Z with want-reply set).
## approach 4
look into the literature for this idea specifically.
* don't view it as a mesh protocol as much as a "distributed db unification problem". When nodes talk to nearby nodes they work together
to update their nodedbs. Each nodedb would have a last change date and any new changes that only one node has would get passed to the
other node. This would nicely allow distant nodes to propogate their position to all other nodes (eventually).
* handle group messages the same way, there would be a table of messages and time of creation.
* when a node has a new position or message to send out, it does a broadcast. All the adjacent nodes update their db instantly (this handles 90% of messages I'll bet).
* Occasionally a node might broadcast saying "anyone have anything newer than time X?" If someone does, they send the diffs since that date.
* essentially everything in this variant becomes broadcasts of "request db updates for >time X - for _all_ or for a particular nodenum" and nodes sending (either due to request or because they changed state) "here's a set of db updates". Every node is constantly trying to
build the most recent version of reality, and if some nodes are too far, then nodes closer in will eventually forward their changes to the distributed db.
* construct non ambigious rules for who broadcasts to request db updates. ideally the algorithm should nicely realize node X can see most other nodes, so they should just listen to all those nodes and minimize the # of broadcasts. the distributed picture of nodes rssi could be useful here?
* possibly view the BLE protocol to the radio the same way - just a process of reconverging the node/msgdb database.

View File

@@ -0,0 +1,9 @@
This is a mini design doc for developing the meshtastic software.
* [Build instructions](build-instructions.md)
* [TODO](TODO.md) - read this if you are looking for things to do (or curious about currently missing features)
* Our [project board](https://github.com/orgs/meshtastic/projects/1) - shows what things we are currently working on and remaining work items for the current release.
* [Power Management](power.md)
* [Mesh algorithm](mesh-alg.md)
* [Bluetooth API](bluetooth-api.md) and porting guide for new clients (iOS, python, etc...)
* TODO: how to port the device code to a new device.

BIN
images/system-info.bin Normal file

Binary file not shown.

View File

@@ -2,13 +2,11 @@
"name": "BluetoothOTA",
"keywords": "esp32, bluetooth",
"description": "A BTLE based software OTA update tool",
"repository":
{
"repository": {
"type": "git",
"url": "https://github.com/geeksville/fixme.git"
},
"authors":
[
"authors": [
{
"name": "Kevin Hester",
"email": "kevinh@geeksville.com",
@@ -20,7 +18,15 @@
"frameworks": "arduino",
"platforms": "*",
"dependencies": [
{ "name": "Update" },
{ "name": "ESP32 BLE Arduino" }
{
"name": "Update"
},
{
"name": "ESP32 BLE Arduino"
},
{
"name": "arduino-fsm",
"version": "https://github.com/meshtastic/arduino-fsm.git"
}
]
}

View File

@@ -31,6 +31,13 @@ public:
if (!canBegin)
// Indicate failure by forcing the size to 0
c->setValue(0UL);
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();
}
}
};
@@ -101,6 +108,8 @@ public:
}
};
void bluetoothRebootCheck()
{
if (rebootAtMsec && millis() > rebootAtMsec)
@@ -108,20 +117,13 @@ void bluetoothRebootCheck()
}
/*
SoftwareUpdateService UUID cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30
See bluetooth-api.md
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
*/
BLEService *createUpdateService(BLEServer *server)
BLEService *createUpdateService(BLEServer *server, std::string hwVendor, std::string swVersion, std::string hwVersion)
{
// Create the BLE Service
BLEService *service = server->createService("cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30");
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);
@@ -133,10 +135,23 @@ BLEService *createUpdateService(BLEServer *server)
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);
swC->setValue(swVersion);
service->addCharacteristic(addBLECharacteristic(swC));
BLECharacteristic *mfC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_MANU_NAME), BLECharacteristic::PROPERTY_READ);
mfC->setValue(hwVendor);
service->addCharacteristic(addBLECharacteristic(mfC));
BLECharacteristic *hwvC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ);
hwvC->setValue(hwVersion);
service->addCharacteristic(addBLECharacteristic(hwvC));
return service;
}
void destroyUpdateService() {
void destroyUpdateService()
{
assert(resultC);
resultC = NULL;

View File

@@ -2,7 +2,7 @@
#include <Arduino.h>
BLEService *createUpdateService(BLEServer* server);
BLEService *createUpdateService(BLEServer* server, std::string hwVendor, std::string swVersion, std::string hwVersion);
void destroyUpdateService();
void bluetoothRebootCheck();

View File

@@ -5,7 +5,6 @@
#include <Arduino.h>
#include <Update.h>
#include "configuration.h"
#include "screen.h"
SimpleAllocator btPool;
@@ -173,7 +172,7 @@ uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue)
class MySecurity : public BLESecurityCallbacks
{
protected:
bool onConfirmPIN(uint32_t pin)
{
Serial.printf("onConfirmPIN %u\n", pin);
@@ -189,7 +188,7 @@ class MySecurity : public BLESecurityCallbacks
void onPassKeyNotify(uint32_t pass_key)
{
Serial.printf("onPassKeyNotify %u\n", pass_key);
screen_start_bluetooth(pass_key);
startCb(pass_key);
}
bool onSecurityRequest()
@@ -211,9 +210,13 @@ class MySecurity : public BLESecurityCallbacks
Serial.printf("onAuthenticationComplete -> fail %d\n", cmpl.fail_reason);
}
// Remove our custom screen
screen_set_frames();
// Remove our custom PIN request screen.
stopCb();
}
public:
StartBluetoothPinScreenCallback startCb;
StopBluetoothPinScreenCallback stopCb;
};
BLEServer *pServer;
@@ -255,7 +258,10 @@ void deinitBLE()
btPool.reset();
}
BLEServer *initBLE(std::string deviceName, std::string hwVendor, std::string swVersion, std::string hwVersion)
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);
@@ -264,6 +270,8 @@ BLEServer *initBLE(std::string deviceName, std::string hwVendor, std::string swV
* 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
@@ -276,7 +284,7 @@ BLEServer *initBLE(std::string deviceName, std::string hwVendor, std::string swV
// 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); // We need to advertise this so our android ble scan operation can see it
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());

View File

@@ -1,5 +1,7 @@
#pragma once
#include <functional>
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
@@ -17,8 +19,14 @@ void dumpCharacteristic(BLECharacteristic *c);
/** converting endianness pull out a 32 bit value */
uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue);
// TODO(girts): create a class for the bluetooth utils helpers?
using StartBluetoothPinScreenCallback = std::function<void(uint32_t pass_key)>;
using StopBluetoothPinScreenCallback = std::function<void(void)>;
void loopBLE();
BLEServer *initBLE(std::string devName, std::string hwVendor, std::string swVersion, std::string hwVersion = "");
BLEServer *initBLE(
StartBluetoothPinScreenCallback startBtPinScreen, StopBluetoothPinScreenCallback stopBtPinScreen,
std::string devName, std::string hwVendor, std::string swVersion, std::string hwVersion = "");
void deinitBLE();
/// Add a characteristic that we will delete when we restart

View File

@@ -1 +0,0 @@
../MeshUtil/app/src/main/proto/mesh.options

View File

@@ -1 +0,0 @@
../MeshUtil/app/src/main/proto/mesh.proto

View File

@@ -9,11 +9,20 @@
; https://docs.platformio.org/page/projectconf.html
[platformio]
default_envs = tbeam
[env:esp32]
[common]
; common is not currently used
; REQUIRED environment variables - if not set the specified default will be sued
; The following environment variables must be set in the shell if you'd like to override them.
; They are used in this ini file as systenv.VARNAME, so in your shell do export "VARNAME=fish"
; HW_VERSION (default US)
; APP_VERSION (default emptystring)
; HW_VERSION (default emptystring)
[env]
platform = espressif32
board = ttgo-t-beam
; board = heltec_wifi_lora_32_V2
framework = arduino
; customize the partition table
@@ -21,24 +30,27 @@ framework = arduino
board_build.partitions = partition-table.csv
; note: we add src to our include search path so that lmic_project_config can override
; FIXME fix dependencies on arduino-fsm
build_flags = -Wall -Wextra -Wno-missing-field-initializers -I.pio/libdeps/esp32/arduino-fsm -Isrc -Os -Wl,-Map,.pio/build/esp32/output.map -DAXP_DEBUG_PORT=Serial
; 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
-DAXP_DEBUG_PORT=Serial
-DHW_VERSION_${sysenv.COUNTRY}
-DAPP_VERSION=${sysenv.APP_VERSION}
-DHW_VERSION=${sysenv.HW_VERSION}
; not needed included in ttgo-t-beam board file
; also to use PSRAM https://docs.platformio.org/en/latest/platforms/espressif32.html#external-ram-psram
; -DBOARD_HAS_PSRAM
; -mfix-esp32-psram-cache-issue
; -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
upload_speed = 921600
upload_port = /dev/ttyUSB0
; leave this commented out to avoid breaking Windows
;upload_port = /dev/ttyUSB0
;monitor_port = /dev/ttyUSB0
; the default is esptool
; upload_protocol = esp-prog
monitor_port = /dev/ttyUSB0
monitor_speed = 115200
# debug_tool = esp-prog
@@ -53,22 +65,73 @@ debug_tool = jlink
;debug_init_cmds =
; monitor adapter_khz 10000
debug_init_break = tbreak setup
lib_deps =
https://github.com/geeksville/RadioHead.git
TinyGPSPlus
https://github.com/geeksville/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
AXP202X_Library
https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
SPI
OneButton
CRC32 ; explicitly needed because dependency is missing in the ble ota update lib
; 1260 ; OneButton - not used yet
1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib
Wire ; explicitly needed here because the AXP202 library forgets to add it
https://github.com/geeksville/arduino-fsm.git
https://github.com/meshtastic/arduino-fsm.git
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git
;[env:tbeam]
; Common settings for ESP targes, mixin with extends = esp32_base
[esp32_base]
src_filter =
${env.src_filter} -<bare/>
upload_speed = 921600
debug_init_break = tbreak setup
build_flags =
${env.build_flags} -Wall -Wextra
; The 1.0 release of the TBEAM board
[env:tbeam]
extends = esp32_base
board = ttgo-t-beam
lib_deps =
${env.lib_deps}
AXP202X_Library
build_flags =
${esp32_base.build_flags} -D TBEAM_V10
; The original TBEAM board without the AXP power chip and a few other changes
; Note: I've heard reports this didn't work. Disabled until someone with a 0.7 can test and debug.
;[env:tbeam0.7]
;extends = esp32_base
;board = ttgo-t-beam
;build_flags =
; ${esp32_base.build_flags} -D TBEAM_V07
;[env:heltec]
[env:heltec]
;build_type = debug ; to make it possible to step through our jtag debugger
;board = heltec_wifi_lora_32_V2
extends = esp32_base
board = heltec_wifi_lora_32_V2
[env:ttgo-lora32-v1]
extends = esp32_base
board = ttgo-lora32-v1
build_flags =
${esp32_base.build_flags} -D TTGO_LORA_V1
; note: the platformio definition for lora32-v2 seems stale, it is missing a pins_arduino.h file, therefore I don't think it works
[env:ttgo-lora32-v2]
extends = esp32_base
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]
platform = nordicnrf52
board = nrf52840_dk_adafruit ; nicer than nrf52840_dk - more full gpio mappings
framework = arduino
debug_tool = jlink
build_flags =
${env.build_flags} -D BARE_BOARD -Wno-unused-variable -Isrc/bare
src_filter =
${env.src_filter} -<esp32/>
lib_ignore =
BluetoothOTA
lib_deps =
${env.lib_deps}
monitor_port = /dev/ttyACM1

1
proto Submodule

Submodule proto added at e06645d8db

1
release/.gitignore vendored
View File

@@ -1,3 +1,4 @@
*.elf
*.bin
*.map
*.zip

1
release/latest/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
curfirmwareversion.xml

View File

@@ -1,30 +1,86 @@
#include "GPS.h"
#include "time.h"
#include <sys/time.h>
#include "configuration.h"
#include "time.h"
#include <assert.h>
#include <sys/time.h>
// stuff that really should be in in the instance instead...
#ifdef GPS_RX_PIN
HardwareSerial _serial_gps(GPS_SERIAL_NUM);
uint32_t timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time
uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock
#else
// Assume NRF52
// Uart _serial_gps(GPS_SERIAL_NUM);
#endif
RTC_DATA_ATTR bool timeSetFromGPS; // We only reset our time once per _boot_ after that point just run from the internal clock (even across sleeps)
bool timeSetFromGPS; // We try to set our time from GPS each time we wake from sleep
GPS gps;
bool hasValidLocation; // default to false, until we complete our first read
bool wantNewLocation = true;
GPS::GPS() : PeriodicTask()
{
}
// 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();
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
}
@@ -32,8 +88,7 @@ void GPS::readFromRTC()
{
struct timeval tv; /* btw settimeofday() is helpfull here too*/
if (!gettimeofday(&tv, NULL))
{
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);
@@ -45,11 +100,14 @@ void GPS::readFromRTC()
/// 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)
{
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();
}
}
@@ -75,70 +133,85 @@ uint32_t GPS::getValidTime()
/// 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 !wantNewLocation;
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()
{
// discard all rx serial bytes so we don't try to parse them when we come back
while (_serial_gps.available())
{
_serial_gps.read();
}
// make the parser bail on whatever it was parsing
encode('\n');
if (isConnected)
ublox.powerOff();
}
void GPS::doTask()
{
#ifdef GPS_RX_PIN
// Consume all characters that have arrived
uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix
while (_serial_gps.available())
{
encode(_serial_gps.read());
// DEBUG_MSG("Got GPS response\n");
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);
}
if (!timeSetFromGPS && time.isValid() && date.isValid())
{
// 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;
DEBUG_MSG("Got time from GPS\n");
/* 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).
*/
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 = time.second();
t.tm_min = time.minute();
t.tm_hour = time.hour();
t.tm_mday = date.day();
t.tm_mon = date.month() - 1;
t.tm_year = date.year() - 1900;
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);
perhapsSetRTC(&tv);
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
if (location.isValid() && location.isUpdated())
{ // we only notify if position has changed
// DEBUG_MSG("new gps pos\n");
hasValidLocation = true;
wantNewLocation = false;
notifyObservers();
}
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 100ms until we have something over the serial
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 100);
// 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()
@@ -147,11 +220,3 @@ void GPS::startLock()
wantNewLocation = true;
setPeriod(1);
}
String GPS::getTimeStr()
{
static char t[12]; // used to sprintf for Serial output
snprintf(t, sizeof(t), "%02d:%02d:%02d", time.hour(), time.minute(), time.second());
return t;
}

View File

@@ -1,8 +1,8 @@
#pragma once
#include <TinyGPS++.h>
#include "PeriodicTask.h"
#include "Observer.h"
#include "PeriodicTask.h"
#include "SparkFun_Ublox_Arduino_Library.h"
#include "sys/time.h"
/**
@@ -10,9 +10,15 @@
*
* When new data is available it will notify observers.
*/
class GPS : public PeriodicTask, public Observable, public TinyGPSPlus
class GPS : public PeriodicTask, public Observable<void *>
{
public:
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
@@ -21,8 +27,6 @@ public:
/// Return time since 1970 in secs. If we don't have a GPS lock return zero
uint32_t getValidTime();
String getTimeStr();
void setup();
virtual void loop();
@@ -41,8 +45,13 @@ public:
/// Restart our lock attempt - try to get and broadcast a GPS reading ASAP
void startLock();
private:
/// 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,18 +1,14 @@
#include "error.h"
#include <SPI.h>
#include "RH_RF95.h"
#include <RHMesh.h>
#include <assert.h>
#include <pb_encode.h>
#include <pb_decode.h>
#include "MeshRadio.h"
#include "configuration.h"
#include "MeshService.h"
#include "NodeDB.h"
#define DEFAULT_CHANNEL_NUM 3 // we randomly pick one
/// 16 bytes of random PSK for our _public_ default channel that all devices power up on
static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf};
#include "configuration.h"
#include "sleep.h"
#include <pb_decode.h>
#include <pb_encode.h>
/**
* ## LoRaWAN for North America
@@ -21,112 +17,109 @@ 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.
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(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &_rxDest)
: rf95(_pool, _rxDest),
manager(rf95)
MeshRadio::MeshRadio() // , manager(radioIf)
{
myNodeInfo.num_channels = NUM_CHANNELS;
myNodeInfo.num_channels = NUM_CHANNELS;
//radioConfig.modem_config = RadioConfig_ModemConfig_Bw125Cr45Sf128; // medium range and fast
//channelSettings.modem_config = ChannelSettings_ModemConfig_Bw500Cr45Sf128; // short range and fast, but wide bandwidth so incompatible radios can talk together
channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range
channelSettings.tx_power = 23;
channelSettings.channel_num = DEFAULT_CHANNEL_NUM;
memcpy(&channelSettings.psk, &defaultpsk, sizeof(channelSettings.psk));
strcpy(channelSettings.name, "Default");
// Can't print strings this early - serial not setup yet
// DEBUG_MSG("Set meshradio defaults name=%s\n", channelSettings.name);
// 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;
if (!useHardware)
return true;
DEBUG_MSG("Starting meshradio init...\n");
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);
pinMode(RESET_GPIO, OUTPUT); // Deassert reset
digitalWrite(RESET_GPIO, HIGH);
// pulse reset
digitalWrite(RESET_GPIO, LOW);
delay(10);
digitalWrite(RESET_GPIO, HIGH);
delay(10);
// pulse reset
digitalWrite(RESET_GPIO, LOW);
delay(10);
digitalWrite(RESET_GPIO, HIGH);
delay(10);
#endif
manager.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor time.
radioIf.setThisAddress(
nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor time.
if (!manager.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;
}
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);
// not needed - defaults on
// rf95.setPayloadCRC(true);
reloadConfig();
reloadConfig();
return true;
return true;
}
void MeshRadio::reloadConfig()
/** hash a string into an integer
*
* djb2 by Dan Bernstein.
* http://www.cse.yorku.ca/~oz/hash.html
*/
unsigned long hash(char *str)
{
rf95.setModeIdle(); // Need to be idle before doing init
unsigned long hash = 5381;
int c;
// Set up default configuration
// No Sync Words in LORA mode.
rf95.setModemConfig((RH_RF95::ModemConfigChoice)channelSettings.modem_config); // Radio default
// setModemConfig(Bw125Cr48Sf4096); // slow and reliable?
// rf95.setPreambleLength(8); // Default is 8
while ((c = *str++) != 0)
hash = ((hash << 5) + hash) + (unsigned char)c; /* hash * 33 + c */
assert(channelSettings.channel_num < NUM_CHANNELS); // If the phone tries to tell us to use an illegal channel then panic
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
float center_freq = CH0 + CH_SPACING * channelSettings.channel_num;
if (!rf95.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.
rf95.setTxPower(channelSettings.tx_power, false);
DEBUG_MSG("Set radio: name=%s. config=%u, ch=%d, txpower=%d\n", channelSettings.name, channelSettings.modem_config, channelSettings.channel_num, channelSettings.tx_power);
// Done with init tell radio to start receiving
rf95.setModeRx();
return hash;
}
ErrorCode MeshRadio::send(MeshPacket *p)
int MeshRadio::reloadConfig(void *unused)
{
if (useHardware)
return rf95.send(p);
else
{
rf95.pool.release(p);
return ERRNO_OK;
}
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;
}
void MeshRadio::loop()
{
// Currently does nothing, since we do it all in ISRs now
}

View File

@@ -1,31 +1,36 @@
#pragma once
#include "CustomRF95.h"
#include <RHMesh.h>
#include "MemoryPool.h"
#include "mesh.pb.h"
#include "PointerQueue.h"
#include "MeshTypes.h"
#include "Observer.h"
#include "PointerQueue.h"
#include "configuration.h"
#include "mesh.pb.h"
// US channel settings
#define CH0_US 903.08f // MHz
#define CH_SPACING_US 2.16f // MHz
#define CH0_US 903.08f // MHz
#define CH_SPACING_US 2.16f // MHz
#define NUM_CHANNELS_US 13
// EU channel settings
#define CH0_EU 865.2f // MHz
#define CH_SPACING_EU 0.3f // MHz
#define NUM_CHANNELS_EU 10
// EU433 channel settings
#define CH0_EU433 433.175f // MHz
#define CH_SPACING_EU433 0.2f // MHz
#define NUM_CHANNELS_EU433 8
// EU865 channel settings
#define CH0_EU865 865.2f // MHz
#define CH_SPACING_EU865 0.3f // MHz
#define NUM_CHANNELS_EU865 10
// CN channel settings
#define CH0_CN 470.0f // MHz
#define CH_SPACING_CN 2.0f // MHz FIXME, this is just a guess for 470-510
#define CH0_CN 470.0f // MHz
#define CH_SPACING_CN 2.0f // MHz FIXME, this is just a guess for 470-510
#define NUM_CHANNELS_CN 20
// CN channel settings
#define CH0_JP 920.0f // MHz
#define CH_SPACING_JP 0.5f // MHz FIXME, this is just a guess for 920-925
// JP channel settings
#define CH0_JP 920.0f // MHz
#define CH_SPACING_JP 0.5f // MHz FIXME, this is just a guess for 920-925
#define NUM_CHANNELS_JP 10
// FIXME add defs for other regions and use them here
@@ -33,10 +38,14 @@
#define CH0 CH0_US
#define CH_SPACING CH_SPACING_US
#define NUM_CHANNELS NUM_CHANNELS_US
#elif defined(HW_VERSION_EU)
#define CH0 CH0_EU
#define CH_SPACING CH_SPACING_EU
#define NUM_CHANNELS NUM_CHANNELS_EU
#elif defined(HW_VERSION_EU433)
#define CH0 CH0_EU433
#define CH_SPACING CH_SPACING_EU433
#define NUM_CHANNELS NUM_CHANNELS_EU433
#elif defined(HW_VERSION_EU865)
#define CH0 CH0_EU865
#define CH_SPACING CH_SPACING_EU865
#define NUM_CHANNELS NUM_CHANNELS_EU865
#elif defined(HW_VERSION_CN)
#define CH0 CH0_CN
#define CH_SPACING CH_SPACING_CN
@@ -46,48 +55,53 @@
#define CH_SPACING CH_SPACING_JP
#define NUM_CHANNELS NUM_CHANNELS_JP
#else
#error "HW_VERSION not set"
// HW version not set - assume US
#define CH0 CH0_US
#define CH_SPACING CH_SPACING_US
#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:
CustomRF95 rf95; // the raw radio interface - for now I'm leaving public - because this class is shrinking to be almost nothing
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(MemoryPool<MeshPacket> &pool, PointerQueue<MeshPacket> &rxDest);
MeshRadio();
bool init();
/// 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 send(MeshPacket *p);
private:
CallbackObserver<MeshRadio, void *> configChangedObserver =
CallbackObserver<MeshRadio, void *>(this, &MeshRadio::reloadConfig);
/// Do loop callback operations (we currently FIXME poll the receive mailbox here)
/// for received packets it will call the rx handler
void loop();
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
void reloadConfig();
int reloadConfig(void *unused = NULL);
private:
/// Return 0 if sleep is okay
int preflightSleepCb(void *unused = NULL) { return radioIf.canSleep() ? 0 : 1; }
// RHDatagram manager;
// RHReliableDatagram manager; // don't use mesh yet
RHMesh manager;
// MeshRXHandler rxHandler;
/// low level send, might block for mutiple seconds
ErrorCode sendTo(NodeNum dest, const uint8_t *buf, size_t len);
/// enqueue a received packet in rxDest
void handleReceive(MeshPacket *p);
int notifyDeepSleepDb(void *unused = NULL)
{
radioIf.sleep();
return 0;
}
};

View File

@@ -1,54 +1,64 @@
#include <Arduino.h>
#include <assert.h>
#include <string>
#include "mesh-pb-constants.h"
#include "MeshService.h"
#include "MeshBluetoothService.h"
#include "NodeDB.h"
#include "GPS.h"
#include "screen.h"
//#include "MeshBluetoothService.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "Periodic.h"
#include "PowerFSM.h"
#include "main.h"
#include "mesh-pb-constants.h"
/*
receivedPacketQueue - this is a queue of messages we've received from the mesh, which we are keeping to deliver to the phone.
It is implemented with a FreeRTos queue (wrapped with a little RTQueue class) of pointers to MeshPacket protobufs (which were alloced with new).
After a packet ptr is removed from the queue and processed it should be deleted. (eventually we should move sent packets into a 'sentToPhone' queue
of packets we can delete just as soon as we are sure the phone has acked those packets - when the phone writes to FromNum)
It is implemented with a FreeRTos queue (wrapped with a little RTQueue class) of pointers to MeshPacket protobufs (which were
alloced with new). After a packet ptr is removed from the queue and processed it should be deleted. (eventually we should move
sent packets into a 'sentToPhone' queue of packets we can delete just as soon as we are sure the phone has acked those packets -
when the phone writes to FromNum)
mesh - an instance of Mesh class. Which manages the interface to the mesh radio library, reception of packets from other nodes, arbitrating to select
a node number and keeping the current nodedb.
mesh - an instance of Mesh class. Which manages the interface to the mesh radio library, reception of packets from other nodes,
arbitrating to select a node number and keeping the current nodedb.
*/
/* Broadcast when a newly powered mesh node wants to find a node num it can use
The algoritm is as follows:
* when a node starts up, it broadcasts their user and the normal flow is for all other nodes to reply with their User as well (so the new node can build its node db)
* If a node ever receives a User (not just the first broadcast) message where the sender node number equals our node number, that indicates a collision has occurred and the following steps should happen:
* when a node starts up, it broadcasts their user and the normal flow is for all other nodes to reply with their User as well (so
the new node can build its node db)
* If a node ever receives a User (not just the first broadcast) message where the sender node number equals our node number, that
indicates a collision has occurred and the following steps should happen:
If the receiving node (that was already in the mesh)'s macaddr is LOWER than the new User who just tried to sign in: it gets to keep its nodenum. We send a broadcast message
of OUR User (we use a broadcast so that the other node can receive our message, considering we have the same id - it also serves to let observers correct their nodedb) - this case is rare so it should be okay.
If the receiving node (that was already in the mesh)'s macaddr is LOWER than the new User who just tried to sign in: it gets to
keep its nodenum. We send a broadcast message of OUR User (we use a broadcast so that the other node can receive our message,
considering we have the same id - it also serves to let observers correct their nodedb) - this case is rare so it should be okay.
If any node receives a User where the macaddr is GTE than their local macaddr, they have been vetoed and should pick a new random nodenum (filtering against whatever it knows about the nodedb) and
rebroadcast their User.
If any node receives a User where the macaddr is GTE than their local macaddr, they have been vetoed and should pick a new random
nodenum (filtering against whatever it knows about the nodedb) and rebroadcast their User.
FIXME in the initial proof of concept we just skip the entire want/deny flow and just hand pick node numbers at first.
*/
MeshService service;
// I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX
#define MAX_PACKETS (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + MAX_TX_QUEUE + 2) // max number of packets which can be in flight (either queued from reception or queued for sending)
#include "Router.h"
#define MAX_RX_FROMRADIO 4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big
#define NUM_PACKET_ID 255 // 0 is consider invalid
MeshService::MeshService()
: packetPool(MAX_PACKETS),
toPhoneQueue(MAX_RX_TOPHONE),
fromRadioQueue(MAX_RX_FROMRADIO),
fromNum(0),
radio(packetPool, fromRadioQueue)
/// Generate a unique packet id
// FIXME, move this someplace better
PacketId generatePacketId()
{
static uint32_t i;
i++;
return (i % NUM_PACKET_ID) + 1; // return number between 1 and 255
}
MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE)
{
// assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro
}
@@ -57,21 +67,17 @@ void MeshService::init()
{
nodeDB.init();
if (!radio.init())
DEBUG_MSG("radio init failed\n");
gps.addObserver(this);
// No need to call this here, our periodic task will fire quite soon
// sendOwnerPeriod();
gpsObserver.observe(&gps);
packetReceivedObserver.observe(&router.notifyPacketReceived);
}
void MeshService::sendOurOwner(NodeNum dest)
void MeshService::sendOurOwner(NodeNum dest, bool wantReplies)
{
MeshPacket *p = allocForSending();
p->to = dest;
p->payload.which_variant = SubPacket_user_tag;
User &u = p->payload.variant.user;
p->payload.want_response = wantReplies;
p->payload.has_user = true;
User &u = p->payload.user;
u = owner;
DEBUG_MSG("sending owner %s/%s/%s\n", u.id, u.long_name, u.short_name);
@@ -79,101 +85,110 @@ void MeshService::sendOurOwner(NodeNum dest)
}
/// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all
MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp)
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.variant.user.macaddr, sizeof(owner.macaddr)) < 0;
bool weWin = memcmp(&owner.macaddr, &mp->payload.user.macaddr, sizeof(owner.macaddr)) < 0;
if (isCollision)
{
if (weWin)
{
if (isCollision) {
if (weWin) {
DEBUG_MSG("NOTE! Received a nodenum collision and we are vetoing\n");
packetPool.release(mp); // discard it
mp = NULL;
sendOurOwner(); // send our owner as a _broadcast_ because that other guy is mistakenly using our nodenum
}
else
{
} else {
// we lost, we need to try for a new nodenum!
DEBUG_MSG("NOTE! Received a nodenum collision we lost, so picking a new nodenum\n");
nodeDB.updateFrom(*mp); // update the DB early - before trying to repick (so we don't select the same node number again)
nodeDB.updateFrom(
*mp); // update the DB early - before trying to repick (so we don't select the same node number again)
nodeDB.pickNewNodeNum();
sendOurOwner(); // broadcast our new attempt at a node number
}
}
else if (wasBroadcast)
{
// If we haven't yet abandoned the packet and it was a broadcast, reply (just to them) with our User record so they can build their DB
} else if (wasBroadcast) {
// If we haven't yet abandoned the packet and it was a broadcast, reply (just to them) with our User record so they can
// build their DB
// Someone just sent us a User, reply with our Owner
DEBUG_MSG("Received broadcast Owner from 0x%x, replying with our owner\n", mp->from);
sendOurOwner(mp->from);
String lcd = String("Joined: ") + mp->payload.variant.user.long_name + "\n";
screen_print(lcd.c_str());
String lcd = String("Joined: ") + mp->payload.user.long_name + "\n";
screen.print(lcd.c_str());
}
return mp;
}
void MeshService::handleFromRadio(MeshPacket *mp)
void MeshService::handleIncomingPosition(const MeshPacket *mp)
{
mp->rx_time = gps.getValidTime(); // store the arrival timestamp for the phone
if (mp->has_payload && mp->payload.has_position) {
DEBUG_MSG("handled incoming position time=%u\n", mp->payload.position.time);
if (mp->has_payload && mp->payload.which_variant == SubPacket_user_tag)
{
if (mp->payload.position.time) {
struct timeval tv;
uint32_t secs = mp->payload.position.time;
tv.tv_sec = secs;
tv.tv_usec = 0;
gps.perhapsSetRTC(&tv);
}
} else {
DEBUG_MSG("Ignoring incoming packet - not a position\n");
}
}
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)
handleIncomingPosition(mp);
else {
DEBUG_MSG("Ignoring incoming time, because we have a GPS\n");
}
if (mp->has_payload && mp->payload.has_user) {
mp = handleFromRadioUser(mp);
}
// If we veto a received User packet, we don't put it into the DB or forward it to the phone (to prevent confusing it)
if (mp)
{
if (mp) {
DEBUG_MSG("Forwarding to phone, from=0x%x, rx_time=%u\n", mp->from, mp->rx_time);
nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
fromNum++;
if (toPhoneQueue.numFree() == 0)
{
if (toPhoneQueue.numFree() == 0) {
DEBUG_MSG("NOTE: tophone queue is full, discarding oldest\n");
MeshPacket *d = toPhoneQueue.dequeuePtr(0);
if (d)
releaseToPool(d);
}
assert(toPhoneQueue.enqueue(mp, 0) == pdTRUE); // FIXME, instead of failing for full queue, delete the oldest mssages
if(mp->payload.want_response)
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)
sendNetworkPing(mp->from);
} else {
DEBUG_MSG("Not delivering vetoed User message\n");
}
else
DEBUG_MSG("Dropping vetoed User message\n");
}
void MeshService::handleFromRadio()
{
MeshPacket *mp;
uint32_t oldFromNum = fromNum;
while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL)
{
handleFromRadio(mp);
}
if (oldFromNum != fromNum) // We don't want to generate extra notifies for multiple new packets
bluetoothNotifyFromNum(fromNum);
return 0;
}
uint32_t sendOwnerCb()
{
service.sendOurOwner();
service.sendOurOwner();
return radioConfig.preferences.send_owner_interval * radioConfig.preferences.position_broadcast_secs * 1000;
return radioConfig.preferences.send_owner_interval * radioConfig.preferences.position_broadcast_secs * 1000;
}
Periodic sendOwnerPeriod(sendOwnerCb);
@@ -181,9 +196,10 @@ Periodic sendOwnerPeriod(sendOwnerCb);
/// Do idle processing (mostly processing messages which have been queued from the radio)
void MeshService::loop()
{
radio.loop(); // FIXME, possibly move radio interaction to own thread
handleFromRadio();
if (oldFromNum != fromNum) { // We don't want to generate extra notifies for multiple new packets
fromNumChanged.notifyObservers(fromNum);
oldFromNum = fromNum;
}
// occasionally send our owner info
sendOwnerPeriod.loop();
@@ -193,7 +209,8 @@ void MeshService::loop()
void MeshService::reloadConfig()
{
// If we can successfully set this radio to these settings, save them to disk
radio.reloadConfig();
nodeDB.resetRadioConfig(); // Don't let the phone send us fatally bad settings
configChanged.notifyObservers(NULL);
nodeDB.saveToDisk();
}
@@ -202,36 +219,32 @@ void MeshService::handleToRadio(std::string s)
{
static ToRadio r; // this is a static scratch object, any data must be copied elsewhere before returning
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 (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
if (r.variant.packet.has_payload && r.variant.packet.payload.which_variant == SubPacket_position_tag && r.variant.packet.payload.variant.position.time)
{
struct timeval tv;
uint32_t secs = r.variant.packet.payload.variant.position.time;
MeshPacket &p = r.variant.packet;
handleIncomingPosition(&p); // If it is a position packet, perhaps set our clock
// FIXME, this is a shit not right version of the standard def of unix time!!!
tv.tv_sec = secs;
tv.tv_usec = 0;
if (p.from == 0) // If the phone didn't set a sending node ID, use ours
p.from = nodeDB.getNodeNum();
gps.perhapsSetRTC(&tv);
}
if (p.id == 0)
p.id = generatePacketId(); // If the phone didn't supply one, then pick one
r.variant.packet.rx_time = gps.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
sendToMesh(packetPool.allocCopy(r.variant.packet));
sendToMesh(packetPool.allocCopy(p));
bool loopback = false; // if true send any packet the phone sends back itself (for testing)
if (loopback)
{
MeshPacket *mp = packetPool.allocCopy(r.variant.packet);
handleFromRadio(mp);
bluetoothNotifyFromNum(fromNum); // tell the phone a new packet arrived
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;
}
@@ -239,6 +252,8 @@ void MeshService::handleToRadio(std::string s)
DEBUG_MSG("Error: unexpected ToRadio variant\n");
break;
}
} else {
DEBUG_MSG("Error: ignoring malformed toradio\n");
}
}
@@ -246,18 +261,26 @@ void MeshService::sendToMesh(MeshPacket *p)
{
nodeDB.updateFrom(*p); // update our local DB for this packet (because phone might have sent position packets etc...)
// 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)
if (p->has_payload && p->payload.which_variant == SubPacket_position_tag)
p->payload.variant.position.time = 0;
// 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;
} else
DEBUG_MSG("Providing time to mesh %u\n", p->payload.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
{
else {
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
if (radio.send(p) != ERRNO_OK)
DEBUG_MSG("Dropped packet because send queue was full!\n");
if (router.send(p) != ERRNO_OK) {
DEBUG_MSG("No radio was able to send packet, discarding...\n");
releaseToPool(p);
}
}
}
@@ -268,23 +291,25 @@ MeshPacket *MeshService::allocForSending()
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;
}
void MeshService::sendNetworkPing(NodeNum dest)
void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
{
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
assert(node);
DEBUG_MSG("Sending network ping to 0x%x, with position=%d, wantReplies=%d\n", dest, node->has_position, wantReplies);
if (node->has_position)
sendOurPosition(dest);
sendOurPosition(dest, wantReplies);
else
sendOurOwner(dest);
sendOurOwner(dest, wantReplies);
}
void MeshService::sendOurPosition(NodeNum dest)
void MeshService::sendOurPosition(NodeNum dest, bool wantReplies)
{
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
assert(node);
@@ -293,46 +318,45 @@ void MeshService::sendOurPosition(NodeNum dest)
// Update our local node info with our position (even if we don't decide to update anyone else)
MeshPacket *p = allocForSending();
p->to = dest;
p->payload.which_variant = SubPacket_position_tag;
p->payload.variant.position = node->position;
// FIXME - for now we are leaving this in the sent packets (for debugging)
//p->payload.variant.position.time = 0; // No need to send time, other node won't trust it anyways
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.
sendToMesh(p);
}
void MeshService::onGPSChanged()
int MeshService::onGPSChanged(void *unused)
{
DEBUG_MSG("got gps notify\n");
// Update our local node info with our position (even if we don't decide to update anyone else)
MeshPacket *p = allocForSending();
p->payload.which_variant = SubPacket_position_tag;
Position &pos = p->payload.variant.position;
if (gps.altitude.isValid())
pos.altitude = gps.altitude.meters();
pos.latitude = gps.location.lat();
pos.longitude = gps.location.lng();
pos.time = gps.getValidTime();
p->payload.has_position = true;
Position &pos = p->payload.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();
}
// We limit our GPS broadcasts to a max rate
static uint32_t lastGpsSend;
uint32_t now = millis();
if (lastGpsSend == 0 || now - lastGpsSend > radioConfig.preferences.position_broadcast_secs * 1000)
{
if (lastGpsSend == 0 || now - lastGpsSend > radioConfig.preferences.position_broadcast_secs * 1000) {
lastGpsSend = now;
DEBUG_MSG("Sending position to mesh\n");
sendToMesh(p);
}
else
{
} else {
// We don't need to send this packet to anyone else, but it still serves as a nice uniform way to update our local state
nodeDB.updateFrom(*p);
releaseToPool(p);
}
}
void MeshService::onNotify(Observable *o)
{
DEBUG_MSG("got gps notify\n");
onGPSChanged();
return 0;
}

View File

@@ -2,20 +2,24 @@
#include <Arduino.h>
#include <assert.h>
#include <string>
#include "mesh.pb.h"
#include "MeshRadio.h"
#include "PointerQueue.h"
#include "MemoryPool.h"
#include "MeshRadio.h"
#include "MeshTypes.h"
#include "Observer.h"
#include "PointerQueue.h"
#include "mesh.pb.h"
/**
* Top level app for this service. keeps the mesh, the radio config and the queue of received packets.
*
*/
class MeshService: private Observer
class MeshService
{
MemoryPool<MeshPacket> packetPool;
CallbackObserver<MeshService, void *> gpsObserver = CallbackObserver<MeshService, void *>(this, &MeshService::onGPSChanged);
CallbackObserver<MeshService, const MeshPacket *> packetReceivedObserver =
CallbackObserver<MeshService, const MeshPacket *>(this, &MeshService::handleFromRadio);
/// received packets waiting for the phone to process them
/// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure
@@ -23,16 +27,18 @@ class MeshService: private Observer
/// FIXME - save this to flash on deep sleep
PointerQueue<MeshPacket> toPhoneQueue;
/// Packets which have just arrived from the radio, ready to be processed by this service and possibly
/// forwarded to the phone.
PointerQueue<MeshPacket> fromRadioQueue;
/// The current nonce for the newest packet which has been queued for the phone
uint32_t fromNum;
uint32_t fromNum = 0;
public:
/// Updated in loop() to detect when fromNum changes
uint32_t oldFromNum = 0;
MeshRadio radio;
public:
/// Called when some new packets have arrived from one of the radios
Observable<uint32_t> fromNumChanged;
/// Called when radio config has changed (radios should observe this and set their hardware as required)
Observable<void *> configChanged;
MeshService();
@@ -60,33 +66,35 @@ public:
/// 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 = NODENUM_BROADCAST);
/// 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);
/// Send our owner info to a particular node
void sendOurOwner(NodeNum dest = NODENUM_BROADCAST);
private:
/// Broadcasts our last known position
void sendOurPosition(NodeNum dest = NODENUM_BROADCAST);
void sendOurOwner(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
/// Send a packet into the mesh - note p must have been allocated from packetPool. We will return it to that pool after sending.
/// This is the ONLY function you should use for sending messages into the mesh, because it also updates the nodedb cache
private:
/// Broadcasts our last known position
void sendOurPosition(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
/// Send a packet into the mesh - note p must have been allocated from packetPool. We will return it to that pool after
/// sending. This is the ONLY function you should use for sending messages into the mesh, because it also updates the nodedb
/// cache
void sendToMesh(MeshPacket *p);
/// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh
void onGPSChanged();
/// returns 0 to allow futher processing
int onGPSChanged(void *arg);
virtual void onNotify(Observable *o);
/// handle all the packets that just arrived from the mesh radio
void handleFromRadio();
/// Handle a packet that just arrived from the radio
void handleFromRadio(MeshPacket *p);
/// Handle a packet that just arrived from the radio. This method does _not_ free the provided packet. If it needs
/// to keep the packet around it makes a copy
int handleFromRadio(const MeshPacket *p);
/// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all
MeshPacket *handleFromRadioUser(MeshPacket *mp);
const MeshPacket *handleFromRadioUser(const MeshPacket *mp);
/// look at inbound packets and if they contain a position with time, possibly set our clock
void handleIncomingPosition(const MeshPacket *mp);
};
extern MeshService service;

View File

@@ -2,12 +2,18 @@
// 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;

View File

@@ -5,13 +5,14 @@
#include "FS.h"
#include "SPIFFS.h"
#include <pb_encode.h>
#include <pb_decode.h>
#include "configuration.h"
#include "mesh-pb-constants.h"
#include "NodeDB.h"
#include "GPS.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include "configuration.h"
#include "error.h"
#include "mesh-pb-constants.h"
#include <pb_decode.h>
#include <pb_encode.h>
NodeDB nodeDB;
@@ -21,7 +22,20 @@ MyNodeInfo &myNodeInfo = devicestate.my_node;
RadioConfig &radioConfig = devicestate.radio;
ChannelSettings &channelSettings = radioConfig.channel_settings;
/*
DeviceState versions used to be defined in the .proto file but really only this function cares. So changed to a
#define here.
*/
#define DEVICESTATE_CUR_VER 7
#define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER
#ifndef NO_ESP32
#define FS SPIFFS
#endif
// FIXME - move this somewhere else
extern void getMacAddr(uint8_t *dmac);
/**
*
@@ -33,42 +47,71 @@ User &owner = devicestate.owner;
static uint8_t ourMacAddr[6];
NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_count)
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
static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59,
0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf};
if (radioConfig.preferences.sds_secs == 0) {
DEBUG_MSG("RadioConfig reset!\n");
radioConfig.preferences.send_owner_interval = 4; // per sw-design.md
radioConfig.preferences.position_broadcast_secs = 15 * 60;
radioConfig.preferences.wait_bluetooth_secs = 120;
radioConfig.preferences.screen_on_secs = 5 * 60;
radioConfig.preferences.mesh_sds_timeout_secs = 2 * 60 * 60;
radioConfig.preferences.phone_sds_timeout_sec = 2 * 60 * 60;
radioConfig.preferences.sds_secs = 365 * 24 * 60 * 60; // one year
radioConfig.preferences.ls_secs = 60 * 60;
radioConfig.preferences.phone_timeout_secs = 15 * 60;
radioConfig.has_channel_settings = true;
radioConfig.has_preferences = true;
// radioConfig.modem_config = RadioConfig_ModemConfig_Bw125Cr45Sf128; // medium range and fast
// channelSettings.modem_config = ChannelSettings_ModemConfig_Bw500Cr45Sf128; // short range and fast, but wide bandwidth
// so incompatible radios can talk together
channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range
channelSettings.tx_power = 23;
memcpy(&channelSettings.psk, &defaultpsk, sizeof(channelSettings.psk));
strcpy(channelSettings.name, "Default");
}
// temp hack for quicker testing
/*
radioConfig.preferences.screen_on_secs = 30;
radioConfig.preferences.wait_bluetooth_secs = 30;
radioConfig.preferences.position_broadcast_secs = 15;
*/
}
void NodeDB::init()
{
// init our devicestate with valid flags so protobuf writing/reading will work
devicestate.has_my_node = true;
devicestate.has_radio = true;
devicestate.has_owner = true;
devicestate.has_radio = true;
devicestate.has_radio = false;
devicestate.radio.has_channel_settings = true;
devicestate.radio.has_preferences = true;
devicestate.node_db_count = 0;
devicestate.receive_queue_count = 0;
radioConfig.preferences.send_owner_interval = 4; // per sw-design.md
radioConfig.preferences.position_broadcast_secs = 15 * 60;
radioConfig.preferences.wait_bluetooth_secs = 120;
radioConfig.preferences.screen_on_secs = 30;
radioConfig.preferences.mesh_sds_timeout_secs = 60 * 60;
radioConfig.preferences.phone_sds_timeout_sec = 60 * 60;
radioConfig.preferences.sds_secs = 60 * 60;
radioConfig.preferences.ls_secs = 60 * 60;
radioConfig.preferences.phone_timeout_secs = 15 * 60;
resetRadioConfig();
#ifdef GPS_RX_PIN
// some hardware defaults to have a built in GPS
myNodeInfo.has_gps = true;
#endif
// default to no GPS, until one has been found by probing
myNodeInfo.has_gps = false;
strncpy(myNodeInfo.region, xstr(HW_VERSION), sizeof(myNodeInfo.region));
strncpy(myNodeInfo.firmware_version, xstr(APP_VERSION), sizeof(myNodeInfo.firmware_version));
strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model));
// Init our blank owner info to reasonable defaults
esp_efuse_mac_get_default(ourMacAddr);
sprintf(owner.id, "!%02x%02x%02x%02x%02x%02x", ourMacAddr[0],
ourMacAddr[1], ourMacAddr[2], ourMacAddr[3], ourMacAddr[4], ourMacAddr[5]);
getMacAddr(ourMacAddr);
sprintf(owner.id, "!%02x%02x%02x%02x%02x%02x", ourMacAddr[0], ourMacAddr[1], ourMacAddr[2], ourMacAddr[3], ourMacAddr[4],
ourMacAddr[5]);
memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr));
// make each node start with ad different random seed (but okay that the sequence is the same each boot)
@@ -85,14 +128,9 @@ void NodeDB::init()
info->user = owner;
info->has_user = true;
if (!FS.begin(true)) // FIXME - do this in main?
{
DEBUG_MSG("ERROR SPIFFS Mount Failed\n");
// FIXME - report failure to phone
}
// saveToDisk();
loadFromDisk();
resetRadioConfig(); // If bogus settings got saved, then fix them
DEBUG_MSG("NODENUM=0x%x, dbsize=%d\n", myNodeInfo.my_node_num, *numNodes);
}
@@ -111,8 +149,7 @@ void NodeDB::pickNewNodeNum()
r = NUM_RESERVED; // don't pick a reserved node number
NodeInfo *found;
while ((found = getNode(r)) && memcmp(found->user.macaddr, owner.macaddr, sizeof(owner.macaddr)))
{
while ((found = getNode(r)) && memcmp(found->user.macaddr, owner.macaddr, sizeof(owner.macaddr))) {
NodeNum n = random(NUM_RESERVED, NODENUM_BROADCAST); // try a new random choice
DEBUG_MSG("NOTE! Our desired nodenum 0x%x is in use, so trying for 0x%x\n", r, n);
r = n;
@@ -126,56 +163,59 @@ const char *preftmp = "/db.proto.tmp";
void NodeDB::loadFromDisk()
{
#ifdef FS
static DeviceState scratch;
File f = FS.open(preffile);
if (f)
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);
if (f) {
DEBUG_MSG("Loading saved preferences\n");
pb_istream_t stream = {&readcb, &f, DeviceState_size};
//DEBUG_MSG("Preload channel name=%s\n", channelSettings.name);
// DEBUG_MSG("Preload channel name=%s\n", channelSettings.name);
memset(&scratch, 0, sizeof(scratch));
if (!pb_decode(&stream, DeviceState_fields, &scratch))
{
if (!pb_decode(&stream, DeviceState_fields, &scratch)) {
DEBUG_MSG("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream));
// FIXME - report failure to phone
}
else
{
if (scratch.version < DeviceState_Version_Minimum)
} else {
if (scratch.version < DEVICESTATE_MIN_VER)
DEBUG_MSG("Warn: devicestate is old, discarding\n");
else
{
else {
DEBUG_MSG("Loaded saved preferences version %d\n", scratch.version);
devicestate = scratch;
}
//DEBUG_MSG("Postload channel name=%s\n", channelSettings.name);
// DEBUG_MSG("Postload channel name=%s\n", channelSettings.name);
}
f.close();
}
else
{
} else {
DEBUG_MSG("No saved preferences found\n");
}
#else
DEBUG_MSG("ERROR: Filesystem not implemented\n");
#endif
}
void NodeDB::saveToDisk()
{
#ifdef FS
File f = FS.open(preftmp, "w");
if (f)
{
if (f) {
DEBUG_MSG("Writing preferences\n");
pb_ostream_t stream = {&writecb, &f, SIZE_MAX, 0};
//DEBUG_MSG("Presave channel name=%s\n", channelSettings.name);
// DEBUG_MSG("Presave channel name=%s\n", channelSettings.name);
devicestate.version = DeviceState_Version_Current;
if (!pb_encode(&stream, DeviceState_fields, &devicestate))
{
devicestate.version = DEVICESTATE_CUR_VER;
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
}
@@ -187,11 +227,12 @@ void NodeDB::saveToDisk()
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
{
} else {
DEBUG_MSG("ERROR: can't write prefs\n"); // FIXME report to app
}
#else
DEBUG_MSG("ERROR filesystem not implemented\n");
#endif
}
const NodeInfo *NodeDB::readNextInfo()
@@ -233,10 +274,9 @@ 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)
{
if (mp.has_payload) {
const SubPacket &p = mp.payload;
DEBUG_MSG("Update DB node 0x%x for variant %d, rx_time=%u\n", mp.from, p.which_variant, mp.rx_time);
DEBUG_MSG("Update DB node 0x%x, rx_time=%u\n", mp.from, mp.rx_time);
int oldNumNodes = *numNodes;
NodeInfo *info = getOrCreateNode(mp.from);
@@ -244,55 +284,45 @@ void NodeDB::updateFrom(const MeshPacket &mp)
if (oldNumNodes != *numNodes)
updateGUI = true; // we just created a nodeinfo
if (mp.rx_time)
{ // if the packet has a valid timestamp use it to update our last_seen
if (mp.rx_time) { // if the packet has a valid timestamp use it to update our last_seen
info->has_position = true; // at least the time is valid
info->position.time = mp.rx_time;
}
switch (p.which_variant)
{
case SubPacket_position_tag:
{
if (p.has_position) {
// we carefully preserve the old time, because we always trust our local timestamps more
uint32_t oldtime = info->position.time;
info->position = p.variant.position;
info->position = p.position;
info->position.time = oldtime;
info->has_position = true;
updateGUIforNode = info;
break;
}
case SubPacket_data_tag:
{
if (p.has_data) {
// Keep a copy of the most recent text message.
if (p.variant.data.typ == Data_Type_CLEAR_TEXT)
{
DEBUG_MSG("Received text msg from=0%0x, msg=%.*s\n", mp.from, p.variant.data.payload.size, p.variant.data.payload.bytes);
if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum())
{
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);
if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum()) {
// We only store/display messages destined for us.
devicestate.rx_text_message = mp;
devicestate.has_rx_text_message = true;
updateTextMessage = true;
powerFSM.trigger(EVENT_NODEDB_UPDATED);
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG);
}
}
break;
}
case SubPacket_user_tag:
{
if (p.has_user) {
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.variant.user, sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay
bool changed = memcmp(&info->user, &p.user,
sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay
info->user = p.variant.user;
info->user = p.user;
DEBUG_MSG("updating changed=%d user %s/%s/%s\n", changed, info->user.id, info->user.long_name, info->user.short_name);
info->has_user = true;
if (changed)
{
if (changed) {
updateGUIforNode = info;
powerFSM.trigger(EVENT_NODEDB_UPDATED);
@@ -300,11 +330,6 @@ void NodeDB::updateFrom(const MeshPacket &mp)
// We just changed something important about the user, store our DB
// saveToDisk();
}
break;
}
default:
break; // Ignore other packet types
}
}
}
@@ -325,8 +350,7 @@ NodeInfo *NodeDB::getOrCreateNode(NodeNum n)
{
NodeInfo *info = getNode(n);
if (!info)
{
if (!info) {
// add the node
assert(*numNodes < MAX_NUM_NODES);
info = &nodes[(*numNodes)++];
@@ -338,3 +362,12 @@ NodeInfo *NodeDB::getOrCreateNode(NodeNum n)
return info;
}
/// Record an error that should be reported via analytics
void recordCriticalError(CriticalErrorCode code, uint32_t address)
{
DEBUG_MSG("NOTE! Recording critical error %d, address=%x\n", code, address);
myNodeInfo.error_code = code;
myNodeInfo.error_address = address;
myNodeInfo.error_count++;
}

View File

@@ -3,8 +3,8 @@
#include <Arduino.h>
#include <assert.h>
#include "mesh-pb-constants.h"
#include "MeshTypes.h"
#include "mesh-pb-constants.h"
extern DeviceState devicestate;
extern MyNodeInfo &myNodeInfo;
@@ -28,10 +28,10 @@ class NodeDB
int readPointer = 0;
public:
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
public:
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
NodeInfo *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
bool updateTextMessage = false; // if true, the GUI should show a new text message
bool updateTextMessage = false; // if true, the GUI should show a new text message
/// don't do mesh based algoritm for node id assignment (initially)
/// instead just store in flash - possibly even in the initial alpha release do this hack
@@ -43,6 +43,9 @@ public:
/// write to flash
void saveToDisk();
// Reinit radio config if needed, because sometimes a buggy android app might send us bogus settings
void resetRadioConfig();
/// given a subpacket sniffed from the network, update our DB state
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
void updateFrom(const MeshPacket &p);
@@ -56,10 +59,11 @@ public:
// bool handleWantNodeNum(NodeNum n);
/* void handleDenyNodeNum(NodeNum FIXME read mesh proto docs, perhaps picking a random node num is not a great idea
and instead we should use a special 'im unconfigured node number' and include our desired node number in the wantnum message. the
unconfigured node num would only be used while initially joining the mesh so low odds of conflicting (especially if we randomly select
from a small number of nodenums which can be used temporarily for this operation). figure out what the lower level
mesh sw does if it does conflict? would it be better for people who are replying with denynode num to just broadcast their denial?)
and instead we should use a special 'im unconfigured node number' and include our desired node number in the wantnum message.
the unconfigured node num would only be used while initially joining the mesh so low odds of conflicting (especially if we
randomly select from a small number of nodenums which can be used temporarily for this operation). figure out what the lower
level mesh sw does if it does conflict? would it be better for people who are replying with denynode num to just broadcast
their denial?)
*/
/// Called from bluetooth when the user wants to start reading the node DB from scratch.
@@ -74,13 +78,16 @@ public:
/// Find a node in our DB, return null for missing
NodeInfo *getNode(NodeNum n);
NodeInfo *getNodeByIndex(size_t x) { assert(x < *numNodes); return &nodes[x]; }
NodeInfo *getNodeByIndex(size_t x)
{
assert(x < *numNodes);
return &nodes[x];
}
/// Return the number of nodes we've heard from recently (within the last 2 hrs?)
size_t getNumOnlineNodes();
private:
private:
/// Find a node in our DB, create an empty NodeInfo if missing
NodeInfo *getOrCreateNode(NodeNum n);
@@ -89,4 +96,3 @@ private:
};
extern NodeDB nodeDB;

View File

@@ -1,13 +1,2 @@
#include "Observer.h"
Observer::~Observer()
{
if (observed)
observed->removeObserver(this);
observed = NULL;
}
void Observer::observe(Observable *o)
{
o->addObserver(this);
}

View File

@@ -4,46 +4,96 @@
#include <list>
class Observable;
template <class T> class Observable;
class Observer
/**
* An observer which can be mixed in as a baseclass. Implement onNotify as a method in your class.
*/
template <class T> class Observer
{
Observable *observed;
Observable<T> *observed = NULL;
public:
Observer() : observed(NULL) {}
public:
virtual ~Observer();
void observe(Observable *o);
void observe(Observable<T> *o);
private:
friend class Observable;
private:
friend class Observable<T>;
virtual void onNotify(Observable *o) = 0;
protected:
/**
* returns 0 if other observers should continue to be called
* returns !0 if the observe calls should be aborted and this result code returned for notifyObservers
**/
virtual int onNotify(T arg) = 0;
};
class Observable
/**
* An observer that calls an arbitrary method
*/
template <class Callback, class T> class CallbackObserver : public Observer<T>
{
std::list<Observer *> observers;
typedef int (Callback::*ObserverCallback)(T arg);
public:
void notifyObservers()
{
for (std::list<Observer *>::const_iterator iterator = observers.begin(); iterator != observers.end(); ++iterator)
{
(*iterator)->onNotify(this);
}
}
Callback *objPtr;
ObserverCallback method;
void addObserver(Observer *o)
{
observers.push_back(o);
}
public:
CallbackObserver(Callback *_objPtr, ObserverCallback _method) : objPtr(_objPtr), method(_method) {}
void removeObserver(Observer *o)
{
observers.remove(o);
}
protected:
virtual int onNotify(T arg) { return (objPtr->*method)(arg); }
};
/**
* An observable class that will notify observers anytime notifyObservers is called. Argument type T can be any type, but for
* performance reasons a pointer or word sized object is recommended.
*/
template <class T> class Observable
{
std::list<Observer<T> *> observers;
public:
/**
* Tell all observers about a change, observers can process arg as they wish
*
* returns !0 if an observer chose to abort processing by returning this code
*/
int notifyObservers(T arg)
{
for (typename std::list<Observer<T> *>::const_iterator iterator = observers.begin(); iterator != observers.end();
++iterator) {
int result = (*iterator)->onNotify(arg);
if (result != 0)
return result;
}
return 0;
}
private:
friend class Observer<T>;
// Not called directly, instead call observer.observe
void addObserver(Observer<T> *o) { observers.push_back(o); }
void removeObserver(Observer<T> *o) { observers.remove(o); }
};
template <class T> Observer<T>::~Observer()
{
if (observed)
observed->removeObserver(this);
observed = NULL;
}
template <class T> void Observer<T>::observe(Observable<T> *o)
{
// We can only watch one thing at a time
assert(!observed);
observed = o;
o->addObserver(this);
}

View File

@@ -1,7 +1,7 @@
#pragma once
#include <Arduino.h>
#include "PeriodicTask.h"
#include <Arduino.h>
/**
* Periodically invoke a callback.
@@ -10,13 +10,12 @@
*/
class Periodic : public PeriodicTask
{
uint32_t (*callback)();
uint32_t (*callback)();
public:
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
Periodic(uint32_t (*_callback)()) : callback(_callback) {}
public:
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
Periodic(uint32_t (*_callback)()) : callback(_callback) {}
protected:
void doTask();
protected:
void doTask();
};

View File

@@ -1,19 +1,21 @@
#include "PeriodicTask.h"
#include "Periodic.h"
PeriodicTask::PeriodicTask(uint32_t initialPeriod) : period(initialPeriod)
{
}
PeriodicTask::PeriodicTask(uint32_t initialPeriod) : period(initialPeriod) {}
/// call this from loop
void PeriodicTask::loop()
{
uint32_t now = millis();
if (period && (now - lastMsec) >= period)
{
meshtastic::LockGuard lg(&lock);
uint32_t now = millis();
if (!period || (now - lastMsec) < period) {
return;
}
lastMsec = now;
doTask();
}
// Release the lock in case the task wants to change the period.
doTask();
}
void Periodic::doTask()

View File

@@ -1,7 +1,8 @@
#pragma once
#include <Arduino.h>
#include "configuration.h"
#include <cstdint>
#include "lock.h"
/**
* A base class for tasks that want their doTask() method invoked periodically
@@ -15,9 +16,10 @@ class PeriodicTask
uint32_t lastMsec = 0;
uint32_t period = 1; // call soon after creation
public:
uint32_t periodMsec;
// Protects the above variables.
meshtastic::Lock lock;
public:
virtual ~PeriodicTask() {}
PeriodicTask(uint32_t initialPeriod = 1);
@@ -26,8 +28,12 @@ public:
virtual void loop();
/// Set a new period in msecs (can be called from doTask or elsewhere and the scheduler will cope)
void setPeriod(uint32_t p) { period = p; }
void setPeriod(uint32_t p)
{
meshtastic::LockGuard lg(&lock);
period = p;
}
protected:
protected:
virtual void doTask() = 0;
};

View File

@@ -1,11 +1,13 @@
#include "sleep.h"
#include "PowerFSM.h"
#include "GPS.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "configuration.h"
#include "main.h"
#include "screen.h"
#include "PowerFSM.h"
#include "GPS.h"
#include "sleep.h"
#include "target_specific.h"
static void sdsEnter()
{
@@ -22,22 +24,26 @@ static void sdsEnter()
doDeepSleep(radioConfig.preferences.sds_secs * 1000LL);
}
#include "error.h"
static void lsEnter()
{
while (!service.radio.rf95.canSleep())
delay(10); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives)
DEBUG_MSG("lsEnter begin, ls_secs=%u\n", radioConfig.preferences.ls_secs);
screen.setOn(false);
gps.prepareSleep(); // abandon in-process parsing
setGPSPower(false); // kill GPS power
DEBUG_MSG("lsEnter end\n");
}
static void lsIdle()
{
DEBUG_MSG("lsIdle begin ls_secs=%u\n", radioConfig.preferences.ls_secs);
#ifndef NO_ESP32
uint32_t secsSlept = 0;
esp_sleep_source_t wakeCause = ESP_SLEEP_WAKEUP_UNDEFINED;
bool reached_ls_secs = false;
while (secsSlept < radioConfig.preferences.ls_secs)
{
while (!reached_ls_secs) {
// Briefly come out of sleep long enough to blink the led once every few seconds
uint32_t sleepTime = 5;
@@ -52,21 +58,41 @@ static void lsIdle()
break;
secsSlept += sleepTime;
reached_ls_secs = secsSlept >= radioConfig.preferences.ls_secs;
}
setLed(false);
// Regardless of why we woke (for now) just transition to NB (and that state will handle stuff like IRQs etc)
powerFSM.trigger(EVENT_WAKE_TIMER);
if (reached_ls_secs) {
// stay in LS mode but let loop check whatever it wants
DEBUG_MSG("reached ls_secs, servicing loop()\n");
} else {
DEBUG_MSG("wakeCause %d\n", wakeCause);
#ifdef BUTTON_PIN
bool pressed = !digitalRead(BUTTON_PIN);
#else
bool pressed = false;
#endif
if (pressed) // If we woke because of press, instead generate a PRESS event.
{
powerFSM.trigger(EVENT_PRESS);
} else {
// Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc)
powerFSM.trigger(EVENT_WAKE_TIMER);
}
}
#endif
}
static void lsExit()
{
setGPSPower(true); // restore GPS power
// setGPSPower(true); // restore GPS power
gps.startLock();
}
static void nbEnter()
{
screen.setOn(false);
setBluetoothEnable(false);
// FIXME - check if we already have packets for phone and immediately trigger EVENT_PACKETS_FOR_PHONE
@@ -74,6 +100,7 @@ static void nbEnter()
static void darkEnter()
{
setBluetoothEnable(true);
screen.setOn(false);
}
@@ -81,47 +108,53 @@ static void onEnter()
{
screen.setOn(true);
setBluetoothEnable(true);
static uint32_t lastPingMs;
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);
lastPingMs = now;
}
}
static void onExit()
{
screen.setOn(false);
}
static void wakeForPing()
{
}
static void wakeForPing() {}
static void screenPress()
{
screen.onPress();
}
static void bootEnter() {}
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 stateON(onEnter, NULL, onExit, "ON");
Fsm powerFSM(&stateDARK);
State stateBOOT(bootEnter, NULL, NULL, "BOOT");
State stateON(onEnter, NULL, NULL, "ON");
Fsm powerFSM(&stateBOOT);
void PowerFSM_setup()
{
powerFSM.add_transition(&stateDARK, &stateON, EVENT_BOOT, NULL, "Boot");
powerFSM.add_timed_transition(&stateBOOT, &stateON, 3 * 1000, NULL, "boot timeout");
powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WAKE_TIMER, wakeForPing, "Wake timer");
// Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB and then it handles things
// powerFSM.add_transition(&stateLS, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet");
// Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB and then it
// handles things powerFSM.add_transition(&stateLS, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet");
powerFSM.add_transition(&stateNB, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet, resetting win wake");
// Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB and then it handles things
// powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL, "Press");
// Handle press events
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
powerFSM.add_transition(&stateNB, &stateON, EVENT_PRESS, NULL, "Bluetooth pairing");
powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, NULL, "Bluetooth pairing");
powerFSM.add_transition(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
powerFSM.add_transition(&stateON, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
@@ -130,6 +163,7 @@ void PowerFSM_setup()
powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
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(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone");
@@ -141,7 +175,14 @@ void PowerFSM_setup()
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");
powerFSM.add_timed_transition(&stateDARK, &stateLS, radioConfig.preferences.wait_bluetooth_secs * 1000, NULL,
"Bluetooth timeout");
powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.mesh_sds_timeout_secs * 1000, NULL,
"mesh timeout");
// removing for now, because some users don't even have phones
// powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.phone_sds_timeout_sec * 1000, NULL, "phone
// timeout");
powerFSM.run_machine(); // run one interation of the state machine, so we run our on enter tasks for the initial DARK state
}

View File

@@ -1,6 +1,6 @@
#pragma once
#include "Fsm.h"
#include <Fsm.h>
// See sw-design.md for documentation
@@ -9,9 +9,9 @@
#define EVENT_RECEIVED_PACKET 3
#define EVENT_PACKET_FOR_PHONE 4
#define EVENT_RECEIVED_TEXT_MSG 5
#define EVENT_BOOT 6
// #define EVENT_BOOT 6 // now done with a timed transition
#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_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
extern Fsm powerFSM;

View File

@@ -1,58 +0,0 @@
#pragma once
#include <Arduino.h>
#include <assert.h>
/**
* A wrapper for freertos queues. Note: each element object must be quite small, so T should be only
* pointer types or ints
*/
template <class T>
class TypedQueue
{
QueueHandle_t h;
public:
TypedQueue(int maxElements)
{
h = xQueueCreate(maxElements, sizeof(T));
assert(h);
}
~TypedQueue()
{
vQueueDelete(h);
}
int numFree()
{
return uxQueueSpacesAvailable(h);
}
bool isEmpty()
{
return uxQueueMessagesWaiting(h) == 0;
}
// pdTRUE for success else failure
BaseType_t enqueue(T x, TickType_t maxWait = portMAX_DELAY)
{
return xQueueSendToBack(h, &x, maxWait);
}
BaseType_t enqueueFromISR(T x, BaseType_t *higherPriWoken)
{
return xQueueSendToBackFromISR(h, &x, higherPriWoken);
}
// pdTRUE for success else failure
BaseType_t dequeue(T *p, TickType_t maxWait = portMAX_DELAY)
{
return xQueueReceive(h, p, maxWait);
}
BaseType_t dequeueFromISR(T *p, BaseType_t *higherPriWoken)
{
return xQueueReceiveFromISR(h, p, higherPriWoken);
}
};

3
src/bare/FS.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
// FIXME - make a FS abstraction for NRF52

3
src/bare/SPIFFS.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
// FIXME - make a FS abstraction for NRF52

6
src/bare/main-bare.cpp Normal file
View File

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

View File

@@ -29,44 +29,37 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// Version
// -----------------------------------------------------------------------------
#define APP_NAME "Meshtastic"
// If app version is not specified we assume we are not being invoked by the build script
#ifndef APP_VERSION
#define APP_VERSION "0.0.0" // this def normally comes from build-all.sh
#error APP_VERSION, HW_VERSION, and HW_VERSION_countryname must be set by the build environment
//#define APP_VERSION 0.0.0 // this def normally comes from build-all.sh
//#define HW_VERSION 1.0 - US // normally comes from build-all.sh and contains the region code
#endif
// -----------------------------------------------------------------------------
// Configuration
// -----------------------------------------------------------------------------
// Select which board is being used. If the outside build environment has sent a choice, just use that
#if !defined(T_BEAM_V10) && !defined(HELTEC_LORA32)
//#define T_BEAM_V10 // AKA Rev1 (second board released)
#define HELTEC_LORA32
#define HW_VERSION_US // We encode the hardware freq range in the hw version string, so sw update can eventually install the correct build
#endif
// If we are using the JTAG port for debugging, some pins must be left free for that (and things like GPS have to be disabled)
// we don't support jtag on the ttgo - access to gpio 12 is a PITA
#ifdef HELTEC_LORA32
#ifdef ARDUINO_HELTEC_WIFI_LORA_32_V2
//#define USE_JTAG
#endif
#define DEBUG_PORT Serial // Serial debug port
#define SERIAL_BAUD 115200 // Serial debug baud rate
#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
#define REQUIRE_RADIO true // If true, we will fail to start if the radio is not found
#define xstr(s) str(s)
#define str(s) #s
// -----------------------------------------------------------------------------
// DEBUG
// -----------------------------------------------------------------------------
#ifdef DEBUG_PORT
#define DEBUG_MSG(...) DEBUG_PORT.printf( __VA_ARGS__ )
#define DEBUG_MSG(...) DEBUG_PORT.printf(__VA_ARGS__)
#else
#define DEBUG_MSG(...)
#endif
@@ -77,79 +70,165 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#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_SERIAL_NUM 1
#define GPS_BAUDRATE 9600
#define GPS_SERIAL_NUM 1
#define GPS_BAUDRATE 9600
#if defined(T_BEAM_V10)
#define GPS_RX_PIN 34
#define GPS_RX_PIN 34
#ifdef USE_JTAG
#define GPS_TX_PIN -1
#define GPS_TX_PIN -1
#else
#define GPS_TX_PIN 12
#endif
#define GPS_TX_PIN 12
#endif
// -----------------------------------------------------------------------------
// LoRa SPI
// -----------------------------------------------------------------------------
#define SCK_GPIO 5
#define MISO_GPIO 19
#define MOSI_GPIO 27
#define NSS_GPIO 18
#define SCK_GPIO 5
#define MISO_GPIO 19
#define MOSI_GPIO 27
#define NSS_GPIO 18
#if defined(T_BEAM_V10)
#define HW_VENDOR "TTGO"
#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"
#define BICOLOR_DISPLAY // we have yellow at the top 16 lines
// #define BUTTON_NEED_PULLUP // if set we need to turn on the internal CPU pullup during sleep
#define I2C_SDA 21
#define I2C_SCL 22
#define I2C_SDA 21
#define I2C_SCL 22
#define BUTTON_PIN 38
#define BUTTON_PIN 38
#ifndef USE_JTAG
#define RESET_GPIO 14
#define RESET_GPIO 14
#endif
#define DIO0_GPIO 26
#define DIO1_GPIO 33 // Note: not really used on this board
#define DIO2_GPIO 32 // Note: not really used on this board
#define RF95_IRQ_GPIO 26
#define DIO1_GPIO 33 // Note: not really used on this board
#define DIO2_GPIO 32 // Note: not really used on this board
// Leave undefined to disable our PMU IRQ handler
#define PMU_IRQ 35
#define PMU_IRQ 35
#elif defined(HELTEC_LORA32)
#define HW_VENDOR "Heltec"
#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"
#ifndef USE_JTAG // gpio15 is TDO for JTAG, so no I2C on this board while doing jtag
#define I2C_SDA 4
#define I2C_SCL 15
#endif
// #define BUTTON_NEED_PULLUP // if set we need to turn on the internal CPU pullup during sleep
#define RESET_OLED 16
#define I2C_SDA 21
#define I2C_SCL 22
#define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost
#define LED_PIN 25
#define BUTTON_PIN 0
#define BUTTON_PIN 39
#ifndef USE_JTAG
#define RESET_GPIO 14
#define RESET_GPIO 23
#endif
#define DIO0_GPIO 26
#define DIO1_GPIO 35
#define DIO2_GPIO 34
#define RF95_IRQ_GPIO 26
#define DIO1_GPIO 33 // Note: not really used on this board
#define DIO2_GPIO 32 // Note: not really used on this board
// This board has different GPS pins than all other boards
#undef GPS_RX_PIN
#undef GPS_TX_PIN
#define GPS_RX_PIN 12
#define GPS_TX_PIN 15
#elif defined(ARDUINO_HELTEC_WIFI_LORA_32_V2)
// This string must exactly match the case used in release file names or the android updater won't work
#define HW_VENDOR "heltec"
#ifndef USE_JTAG // gpio15 is TDO for JTAG, so no I2C on this board while doing jtag
#define I2C_SDA 4 // I2C pins for this board
#define I2C_SCL 15
#endif
#define RESET_OLED 16 // If defined, this pin will be used to reset the display controller
#define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost
#define LED_PIN 25 // If defined we will blink this LED
#define BUTTON_PIN 0 // If defined, this will be used for user button presses
#ifndef USE_JTAG
#define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio
#endif
#define RF95_IRQ_GPIO 26
#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(TTGO_LORA_V1)
// This string must exactly match the case used in release file names or the android updater won't work
#define HW_VENDOR "ttgo-lora32-v1"
#define I2C_SDA 4 // I2C pins for this board
#define I2C_SCL 15
#define RESET_OLED 16 // If defined, this pin will be used to reset the display controller
// #define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost
#define LED_PIN 2 // If defined we will blink this LED
#define BUTTON_PIN 0 // If defined, this will be used for user button presses
#define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio
#define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio
#define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
#define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
#elif defined(TTGO_LORA_V2)
// This string must exactly match the case used in release file names or the android updater won't work
#define HW_VENDOR "ttgo-lora32-v2"
#define I2C_SDA 21 // I2C pins for this board
#define I2C_SCL 22
#define RESET_OLED 16 // If defined, this pin will be used to reset the display controller
#define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost
#define LED_PIN 25 // If defined we will blink this LED
#define BUTTON_PIN \
0 // If defined, this will be used for user button presses, if your board doesn't have a physical switch, you can wire one
// between this pin and ground
#define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio
#define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio
#define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
#define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
#elif defined(BARE_BOARD)
// 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
// This board uses 0 to be mean LED on
#undef LED_INVERTED
#define LED_INVERTED 1
#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
#define GPS_POWER_CTRL_CH 3
#define LORA_POWER_CTRL_CH 2

20
src/debug.cpp Normal file
View File

@@ -0,0 +1,20 @@
#include "debug.h"
#include <cstdint>
#include "freertosinc.h"
#include "configuration.h"
namespace meshtastic
{
void printThreadInfo(const char *extra)
{
#ifndef NO_ESP32
uint32_t taskHandle = reinterpret_cast<uint32_t>(xTaskGetCurrentTaskHandle());
DEBUG_MSG("printThreadInfo(%s) task: %" PRIx32 " core id: %u min free stack: %u\n", extra, taskHandle, xPortGetCoreID(),
uxTaskGetStackHighWaterMark(nullptr));
#endif
}
} // namespace meshtastic

10
src/debug.h Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
namespace meshtastic
{
/// Dumps out which core we are running on, and min level of remaining stack
/// seen.
void printThreadInfo(const char *extra);
} // namespace meshtastic

9
src/error.h Normal file
View File

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

View File

@@ -1,19 +1,22 @@
#include "BluetoothUtil.h"
#include "MeshBluetoothService.h"
#include <esp_gatt_defs.h>
#include <BLE2902.h>
#include "BluetoothUtil.h"
#include <Arduino.h>
#include <BLE2902.h>
#include <assert.h>
#include <esp_gatt_defs.h>
#include "mesh.pb.h"
#include "MeshService.h"
#include "mesh-pb-constants.h"
#include "NodeDB.h"
#include "configuration.h"
#include "PowerFSM.h"
#include "CallbackCharacteristic.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include "configuration.h"
#include "mesh-pb-constants.h"
#include "mesh.pb.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
#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)];
class ProtobufCharacteristic : public CallbackCharacteristic
@@ -21,11 +24,9 @@ class ProtobufCharacteristic : public CallbackCharacteristic
const pb_msgdesc_t *fields;
void *my_struct;
public:
public:
ProtobufCharacteristic(const char *uuid, uint32_t btprops, const pb_msgdesc_t *_fields, void *_my_struct)
: CallbackCharacteristic(uuid, btprops),
fields(_fields),
my_struct(_my_struct)
: CallbackCharacteristic(uuid, btprops), fields(_fields), my_struct(_my_struct)
{
setCallbacks(this);
}
@@ -33,8 +34,8 @@ public:
void onRead(BLECharacteristic *c)
{
BLEKeepAliveCallbacks::onRead(c);
DEBUG_MSG("Got proto read\n");
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);
}
@@ -44,24 +45,25 @@ public:
writeToDest(c, my_struct);
}
protected:
protected:
/// like onWrite, but we provide an different destination to write to, for use by subclasses that
/// want to optionally ignore parts of writes.
/// returns true for success
bool writeToDest(BLECharacteristic *c, void *dest)
{
// dumpCharacteristic(pCharacteristic);
DEBUG_MSG("Got on proto write\n");
std::string src = c->getValue();
DEBUG_MSG("pbwrite to %s of %d bytes\n", c->getUUID().toString().c_str(), src.length());
return pb_decode_from_bytes((const uint8_t *)src.c_str(), src.length(), fields, dest);
}
};
class NodeInfoCharacteristic : public BLECharacteristic, public BLEKeepAliveCallbacks
{
public:
public:
NodeInfoCharacteristic()
: BLECharacteristic("d31e02e0-c8ab-4d3f-9cc9-0b8466bdabe8", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ)
: BLECharacteristic("d31e02e0-c8ab-4d3f-9cc9-0b8466bdabe8",
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ)
{
setCallbacks(this);
}
@@ -72,14 +74,12 @@ public:
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);
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
{
} else {
c->setValue(trBytes, 0); // Send an empty response
DEBUG_MSG("Done sending nodeinfos\n");
}
@@ -88,7 +88,7 @@ public:
void onWrite(BLECharacteristic *c)
{
BLEKeepAliveCallbacks::onWrite(c);
DEBUG_MSG("Got on nodeinfo write\n");
DEBUG_MSG("Reset nodeinfo read pointer\n");
nodeDB.resetReadPointer();
}
};
@@ -96,14 +96,23 @@ public:
// wrap our protobuf version with something that forces the service to reload the config
class RadioCharacteristic : public ProtobufCharacteristic
{
public:
public:
RadioCharacteristic()
: ProtobufCharacteristic("b56786c8-839a-44a1-b98e-a1724c4a0262", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ, RadioConfig_fields, &radioConfig)
: 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();
}
@@ -112,33 +121,31 @@ public:
// wrap our protobuf version with something that forces the service to reload the owner
class OwnerCharacteristic : public ProtobufCharacteristic
{
public:
public:
OwnerCharacteristic()
: ProtobufCharacteristic("6ff1d8b6-e2de-41e3-8c0b-8fa384f64eb6", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ, User_fields, &owner)
: 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
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))
{
if (writeToDest(c, &o)) {
int changed = 0;
if (*o.long_name)
{
if (*o.long_name) {
changed |= strcmp(owner.long_name, o.long_name);
strcpy(owner.long_name, o.long_name);
}
if (*o.short_name)
{
if (*o.short_name) {
changed |= strcmp(owner.short_name, o.short_name);
strcpy(owner.short_name, o.short_name);
}
if (*o.id)
{
if (*o.id) {
changed |= strcmp(owner.id, o.id);
strcpy(owner.id, o.id);
}
@@ -151,11 +158,8 @@ public:
class ToRadioCharacteristic : public CallbackCharacteristic
{
public:
ToRadioCharacteristic()
: CallbackCharacteristic("f75c76d2-129e-4dad-a1dd-7866124401e7", BLECharacteristic::PROPERTY_WRITE)
{
}
public:
ToRadioCharacteristic() : CallbackCharacteristic("f75c76d2-129e-4dad-a1dd-7866124401e7", BLECharacteristic::PROPERTY_WRITE) {}
void onWrite(BLECharacteristic *c)
{
@@ -168,9 +172,8 @@ public:
class FromRadioCharacteristic : public CallbackCharacteristic
{
public:
FromRadioCharacteristic()
: CallbackCharacteristic("8ba2bcc2-ee02-4a55-a531-c525c5e454d5", BLECharacteristic::PROPERTY_READ)
public:
FromRadioCharacteristic() : CallbackCharacteristic("8ba2bcc2-ee02-4a55-a531-c525c5e454d5", BLECharacteristic::PROPERTY_READ)
{
}
@@ -181,37 +184,35 @@ public:
// 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)
{
if (!mp) {
DEBUG_MSG("toPhone queue is empty\n");
c->setValue((uint8_t *)"", 0);
}
else
{
DEBUG_MSG("delivering toPhone packet to phone\n");
} else {
static FromRadio fRadio;
static FromRadio fradio;
// Encapsulate as a FromRadio packet
memset(&fRadio, 0, sizeof(fRadio));
fRadio.which_variant = FromRadio_packet_tag;
fRadio.variant.packet = *mp;
// Encapsulate as a ToRadio 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
size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), FromRadio_fields, &fradio);
c->setValue(trBytes, numbytes);
}
}
};
class FromNumCharacteristic : public CallbackCharacteristic
class FromNumCharacteristic : public CallbackCharacteristic, public Observer<uint32_t>
{
public:
public:
FromNumCharacteristic()
: CallbackCharacteristic("ed9da18c-a800-4f66-a670-aa7547e34453",
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY)
: CallbackCharacteristic("ed9da18c-a800-4f66-a670-aa7547e34453", BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_NOTIFY)
{
observe(&service.fromNumChanged);
}
void onRead(BLECharacteristic *c)
@@ -219,85 +220,48 @@ public:
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;
}
};
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;
/**
* Tell any bluetooth clients that the number of rx packets has changed
*/
void bluetoothNotifyFromNum(uint32_t newValue)
{
if (meshFromNumCharacteristic)
{
// if bt not running ignore
meshFromNumCharacteristic->setValue(newValue);
meshFromNumCharacteristic->notify();
}
}
BLEService *meshService;
/*
MeshBluetoothService UUID 6ba1b218-15a8-461f-9fa8-5dcae273eafd
FIXME - notify vs indication for fromradio output. Using notify for now, not sure if that is best
FIXME - in the esp32 mesh managment code, occasionally mirror the current net db to flash, so that if we reboot we still have a good guess of users who are out there.
FIXME - make sure this protocol is guaranteed robust and won't drop packets
"According to the BLE specification the notification length can be max ATT_MTU - 3. The 3 bytes subtracted is the 3-byte header(OP-code (operation, 1 byte) and the attribute handle (2 bytes)).
In BLE 4.1 the ATT_MTU is 23 bytes (20 bytes for payload), but in BLE 4.2 the ATT_MTU can be negotiated up to 247 bytes."
MAXPACKET is 256? look into what the lora lib uses. FIXME
Characteristics:
UUID
properties
description
8ba2bcc2-ee02-4a55-a531-c525c5e454d5
read
fromradio - contains a newly received 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
mailbox.
f75c76d2-129e-4dad-a1dd-7866124401e7
write
toradio - write ToRadio protobufs to this charstic to send them (up to MAXPACKET len)
ed9da18c-a800-4f66-a670-aa7547e34453
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.
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.
meshMyNodeCharacteristic("ea9f3f82-8dc4-4733-9452-1f6da28892a2", BLECharacteristic::PROPERTY_READ)
mynode - read this to access a MyNodeInfo protobuf
meshNodeInfoCharacteristic("d31e02e0-c8ab-4d3f-9cc9-0b8466bdabe8", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ),
nodeinfo - read this to get a series of node infos (ending with a null empty record), write to this to restart the read statemachine that returns all the node infos
meshRadioCharacteristic("b56786c8-839a-44a1-b98e-a1724c4a0262", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ),
radio - read/write this to access a RadioConfig protobuf
meshOwnerCharacteristic("6ff1d8b6-e2de-41e3-8c0b-8fa384f64eb6", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ)
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)
See bluetooth-api.md for documentation.
*/
BLEService *createMeshBluetoothService(BLEServer *server)
{
// Create the BLE Service, we need more than the default of 15 handles
BLEService *service = server->createService(BLEUUID("6ba1b218-15a8-461f-9fa8-5dcae273eafd"), 25, 0);
BLEService *service = server->createService(BLEUUID("6ba1b218-15a8-461f-9fa8-5dcae273eafd"), 30, 0);
assert(!meshFromNumCharacteristic);
meshFromNumCharacteristic = new FromNumCharacteristic;
@@ -305,8 +269,7 @@ BLEService *createMeshBluetoothService(BLEServer *server)
addWithDesc(service, meshFromNumCharacteristic, "fromRadio");
addWithDesc(service, new ToRadioCharacteristic, "toRadio");
addWithDesc(service, new FromRadioCharacteristic, "fromNum");
addWithDesc(service, new ProtobufCharacteristic("ea9f3f82-8dc4-4733-9452-1f6da28892a2", BLECharacteristic::PROPERTY_READ, MyNodeInfo_fields, &myNodeInfo), "myNode");
addWithDesc(service, new MyNodeInfoCharacteristic, "myNode");
addWithDesc(service, new RadioCharacteristic, "radio");
addWithDesc(service, new OwnerCharacteristic, "owner");
addWithDesc(service, new NodeInfoCharacteristic, "nodeinfo");
@@ -317,8 +280,7 @@ BLEService *createMeshBluetoothService(BLEServer *server)
// We only add to advertisting once, because the ESP32 arduino code is dumb and that object never dies
static bool firstTime = true;
if (firstTime)
{
if (firstTime) {
firstTime = false;
server->getAdvertising()->addServiceUUID(service->getUUID());
}
@@ -330,6 +292,12 @@ BLEService *createMeshBluetoothService(BLEServer *server)
return service;
}
void stopMeshBluetoothService()
{
assert(meshService);
meshService->stop();
}
void destroyMeshBluetoothService()
{
assert(meshService);

View File

@@ -1,13 +1,15 @@
#pragma once
#include <BLEService.h>
#include <BLEServer.h>
#include <Arduino.h>
#include <BLEServer.h>
#include <BLEService.h>
BLEService *createMeshBluetoothService(BLEServer* server);
BLEService *createMeshBluetoothService(BLEServer *server);
void destroyMeshBluetoothService();
/**
* Tell any bluetooth clients that the number of rx packets has changed
*/
void bluetoothNotifyFromNum(uint32_t newValue);
void stopMeshBluetoothService();

53
src/esp32/main-esp32.cpp Normal file
View File

@@ -0,0 +1,53 @@
#include "BluetoothUtil.h"
#include "MeshBluetoothService.h"
#include "PowerFSM.h"
#include "configuration.h"
#include "main.h"
#include "target_specific.h"
bool bluetoothOn;
// This routine is called multiple times, once each time we come back from sleep
void reinitBluetooth()
{
DEBUG_MSG("Starting bluetooth\n");
// FIXME - we are leaking like crazy
// AllocatorScope scope(btPool);
// Note: these callbacks might be coming in from a different thread.
BLEServer *serve = initBLE(
[](uint32_t pin) {
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
createMeshBluetoothService(serve);
// Start advertising - this must be done _after_ creating all services
serve->getAdvertising()->start();
}
// Enable/disable bluetooth.
void setBluetoothEnable(bool on)
{
if (on != bluetoothOn) {
DEBUG_MSG("Setting bluetooth enable=%d\n", on);
bluetoothOn = on;
if (on) {
Serial.printf("Pre BT: %u heap size\n", ESP.getFreeHeap());
// ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );
reinitBluetooth();
} else {
// We have to totally teardown our bluetooth objects to prevent leaks
stopMeshBluetoothService(); // Must do before shutting down bluetooth
deinitBLE();
destroyMeshBluetoothService(); // must do after deinit, because it frees our service
Serial.printf("Shutdown BT: %u heap size\n", ESP.getFreeHeap());
// ESP_ERROR_CHECK( heap_trace_stop() );
// heap_trace_dump();
}
}
}

View File

@@ -1,423 +1,423 @@
const uint8_t Custom_ArialMT_Plain_10[] PROGMEM = {
0x0A, // Width: 10
0x0A, // Height: 10
0x20, // First Char: 32
0xE0, // Numbers of Chars: 224
0x0A, // Width: 10
0x0A, // Height: 10
0x20, // First Char: 32
0xE0, // Numbers of Chars: 224
// Jump Table:
0xFF, 0xFF, 0x00, 0x03, // 32:65535
0x00, 0x00, 0x04, 0x03, // 33:0
0x00, 0x04, 0x05, 0x04, // 34:4
0x00, 0x09, 0x09, 0x06, // 35:9
0x00, 0x12, 0x0A, 0x06, // 36:18
0x00, 0x1C, 0x10, 0x09, // 37:28
0x00, 0x2C, 0x0E, 0x07, // 38:44
0x00, 0x3A, 0x01, 0x02, // 39:58
0x00, 0x3B, 0x06, 0x03, // 40:59
0x00, 0x41, 0x06, 0x03, // 41:65
0x00, 0x47, 0x05, 0x04, // 42:71
0x00, 0x4C, 0x09, 0x06, // 43:76
0x00, 0x55, 0x04, 0x03, // 44:85
0x00, 0x59, 0x03, 0x03, // 45:89
0x00, 0x5C, 0x04, 0x03, // 46:92
0x00, 0x60, 0x05, 0x03, // 47:96
0x00, 0x65, 0x0A, 0x06, // 48:101
0x00, 0x6F, 0x08, 0x06, // 49:111
0x00, 0x77, 0x0A, 0x06, // 50:119
0x00, 0x81, 0x0A, 0x06, // 51:129
0x00, 0x8B, 0x0B, 0x06, // 52:139
0x00, 0x96, 0x0A, 0x06, // 53:150
0x00, 0xA0, 0x0A, 0x06, // 54:160
0x00, 0xAA, 0x09, 0x06, // 55:170
0x00, 0xB3, 0x0A, 0x06, // 56:179
0x00, 0xBD, 0x0A, 0x06, // 57:189
0x00, 0xC7, 0x04, 0x03, // 58:199
0x00, 0xCB, 0x04, 0x03, // 59:203
0x00, 0xCF, 0x0A, 0x06, // 60:207
0x00, 0xD9, 0x09, 0x06, // 61:217
0x00, 0xE2, 0x09, 0x06, // 62:226
0x00, 0xEB, 0x0B, 0x06, // 63:235
0x00, 0xF6, 0x14, 0x0A, // 64:246
0x01, 0x0A, 0x0E, 0x07, // 65:266
0x01, 0x18, 0x0C, 0x07, // 66:280
0x01, 0x24, 0x0C, 0x07, // 67:292
0x01, 0x30, 0x0B, 0x07, // 68:304
0x01, 0x3B, 0x0C, 0x07, // 69:315
0x01, 0x47, 0x09, 0x06, // 70:327
0x01, 0x50, 0x0D, 0x08, // 71:336
0x01, 0x5D, 0x0C, 0x07, // 72:349
0x01, 0x69, 0x04, 0x03, // 73:361
0x01, 0x6D, 0x08, 0x05, // 74:365
0x01, 0x75, 0x0E, 0x07, // 75:373
0x01, 0x83, 0x0C, 0x06, // 76:387
0x01, 0x8F, 0x10, 0x08, // 77:399
0x01, 0x9F, 0x0C, 0x07, // 78:415
0x01, 0xAB, 0x0E, 0x08, // 79:427
0x01, 0xB9, 0x0B, 0x07, // 80:441
0x01, 0xC4, 0x0E, 0x08, // 81:452
0x01, 0xD2, 0x0C, 0x07, // 82:466
0x01, 0xDE, 0x0C, 0x07, // 83:478
0x01, 0xEA, 0x0B, 0x06, // 84:490
0x01, 0xF5, 0x0C, 0x07, // 85:501
0x02, 0x01, 0x0D, 0x07, // 86:513
0x02, 0x0E, 0x11, 0x09, // 87:526
0x02, 0x1F, 0x0E, 0x07, // 88:543
0x02, 0x2D, 0x0D, 0x07, // 89:557
0x02, 0x3A, 0x0C, 0x06, // 90:570
0x02, 0x46, 0x06, 0x03, // 91:582
0x02, 0x4C, 0x06, 0x03, // 92:588
0x02, 0x52, 0x04, 0x03, // 93:594
0x02, 0x56, 0x09, 0x05, // 94:598
0x02, 0x5F, 0x0C, 0x06, // 95:607
0x02, 0x6B, 0x03, 0x03, // 96:619
0x02, 0x6E, 0x0A, 0x06, // 97:622
0x02, 0x78, 0x0A, 0x06, // 98:632
0x02, 0x82, 0x0A, 0x05, // 99:642
0x02, 0x8C, 0x0A, 0x06, // 100:652
0x02, 0x96, 0x0A, 0x06, // 101:662
0x02, 0xA0, 0x05, 0x03, // 102:672
0x02, 0xA5, 0x0A, 0x06, // 103:677
0x02, 0xAF, 0x0A, 0x06, // 104:687
0x02, 0xB9, 0x04, 0x02, // 105:697
0x02, 0xBD, 0x04, 0x02, // 106:701
0x02, 0xC1, 0x08, 0x05, // 107:705
0x02, 0xC9, 0x04, 0x02, // 108:713
0x02, 0xCD, 0x10, 0x08, // 109:717
0x02, 0xDD, 0x0A, 0x06, // 110:733
0x02, 0xE7, 0x0A, 0x06, // 111:743
0x02, 0xF1, 0x0A, 0x06, // 112:753
0x02, 0xFB, 0x0A, 0x06, // 113:763
0x03, 0x05, 0x05, 0x03, // 114:773
0x03, 0x0A, 0x08, 0x05, // 115:778
0x03, 0x12, 0x06, 0x03, // 116:786
0x03, 0x18, 0x0A, 0x06, // 117:792
0x03, 0x22, 0x09, 0x05, // 118:802
0x03, 0x2B, 0x0E, 0x07, // 119:811
0x03, 0x39, 0x0A, 0x05, // 120:825
0x03, 0x43, 0x09, 0x05, // 121:835
0x03, 0x4C, 0x0A, 0x05, // 122:844
0x03, 0x56, 0x06, 0x03, // 123:854
0x03, 0x5C, 0x04, 0x03, // 124:860
0x03, 0x60, 0x05, 0x03, // 125:864
0x03, 0x65, 0x09, 0x06, // 126:869
0xFF, 0xFF, 0x00, 0x00, // 127:65535
0xFF, 0xFF, 0x00, 0x0A, // 128:65535
0xFF, 0xFF, 0x00, 0x0A, // 129:65535
0xFF, 0xFF, 0x00, 0x0A, // 130:65535
0xFF, 0xFF, 0x00, 0x0A, // 131:65535
0xFF, 0xFF, 0x00, 0x0A, // 132:65535
0xFF, 0xFF, 0x00, 0x0A, // 133:65535
0xFF, 0xFF, 0x00, 0x0A, // 134:65535
0xFF, 0xFF, 0x00, 0x0A, // 135:65535
0xFF, 0xFF, 0x00, 0x0A, // 136:65535
0xFF, 0xFF, 0x00, 0x0A, // 137:65535
0xFF, 0xFF, 0x00, 0x0A, // 138:65535
0xFF, 0xFF, 0x00, 0x0A, // 139:65535
0xFF, 0xFF, 0x00, 0x0A, // 140:65535
0xFF, 0xFF, 0x00, 0x0A, // 141:65535
0xFF, 0xFF, 0x00, 0x0A, // 142:65535
0xFF, 0xFF, 0x00, 0x0A, // 143:65535
0xFF, 0xFF, 0x00, 0x0A, // 144:65535
0xFF, 0xFF, 0x00, 0x0A, // 145:65535
0xFF, 0xFF, 0x00, 0x0A, // 146:65535
0xFF, 0xFF, 0x00, 0x0A, // 147:65535
0xFF, 0xFF, 0x00, 0x0A, // 148:65535
0xFF, 0xFF, 0x00, 0x0A, // 149:65535
0xFF, 0xFF, 0x00, 0x0A, // 150:65535
0xFF, 0xFF, 0x00, 0x0A, // 151:65535
0xFF, 0xFF, 0x00, 0x0A, // 152:65535
0xFF, 0xFF, 0x00, 0x0A, // 153:65535
0xFF, 0xFF, 0x00, 0x0A, // 154:65535
0xFF, 0xFF, 0x00, 0x0A, // 155:65535
0xFF, 0xFF, 0x00, 0x0A, // 156:65535
0xFF, 0xFF, 0x00, 0x0A, // 157:65535
0xFF, 0xFF, 0x00, 0x0A, // 158:65535
0xFF, 0xFF, 0x00, 0x0A, // 159:65535
0xFF, 0xFF, 0x00, 0x03, // 160:65535
0x03, 0x6E, 0x04, 0x03, // 161:878
0x03, 0x72, 0x0A, 0x06, // 162:882
0x03, 0x7C, 0x0C, 0x06, // 163:892
0x03, 0x88, 0x0A, 0x06, // 164:904
0x03, 0x92, 0x0A, 0x06, // 165:914
0x03, 0x9C, 0x04, 0x03, // 166:924
0x03, 0xA0, 0x0A, 0x06, // 167:928
0x03, 0xAA, 0x05, 0x03, // 168:938
0x03, 0xAF, 0x0D, 0x07, // 169:943
0x03, 0xBC, 0x07, 0x04, // 170:956
0x03, 0xC3, 0x0A, 0x06, // 171:963
0x03, 0xCD, 0x09, 0x06, // 172:973
0x03, 0xD6, 0x03, 0x03, // 173:982
0x03, 0xD9, 0x0D, 0x07, // 174:985
0x03, 0xE6, 0x0B, 0x06, // 175:998
0x03, 0xF1, 0x07, 0x04, // 176:1009
0x03, 0xF8, 0x0A, 0x05, // 177:1016
0x04, 0x02, 0x05, 0x03, // 178:1026
0x04, 0x07, 0x05, 0x03, // 179:1031
0x04, 0x0C, 0x05, 0x03, // 180:1036
0x04, 0x11, 0x0A, 0x06, // 181:1041
0x04, 0x1B, 0x09, 0x05, // 182:1051
0x04, 0x24, 0x03, 0x03, // 183:1060
0x04, 0x27, 0x06, 0x03, // 184:1063
0x04, 0x2D, 0x05, 0x03, // 185:1069
0x04, 0x32, 0x07, 0x04, // 186:1074
0x04, 0x39, 0x0A, 0x06, // 187:1081
0x04, 0x43, 0x10, 0x08, // 188:1091
0x04, 0x53, 0x10, 0x08, // 189:1107
0x04, 0x63, 0x10, 0x08, // 190:1123
0x04, 0x73, 0x0A, 0x06, // 191:1139
0x04, 0x7D, 0x0E, 0x07, // 192:1149
0x04, 0x8B, 0x0E, 0x07, // 193:1163
0x04, 0x99, 0x0E, 0x07, // 194:1177
0x04, 0xA7, 0x0E, 0x07, // 195:1191
0x04, 0xB5, 0x0E, 0x07, // 196:1205
0x04, 0xC3, 0x0E, 0x07, // 197:1219
0x04, 0xD1, 0x12, 0x0A, // 198:1233
0x04, 0xE3, 0x0C, 0x07, // 199:1251
0x04, 0xEF, 0x0C, 0x07, // 200:1263
0x04, 0xFB, 0x0C, 0x07, // 201:1275
0x05, 0x07, 0x0C, 0x07, // 202:1287
0x05, 0x13, 0x0C, 0x07, // 203:1299
0x05, 0x1F, 0x05, 0x03, // 204:1311
0x05, 0x24, 0x04, 0x03, // 205:1316
0x05, 0x28, 0x04, 0x03, // 206:1320
0x05, 0x2C, 0x05, 0x03, // 207:1324
0x05, 0x31, 0x0B, 0x07, // 208:1329
0x05, 0x3C, 0x0C, 0x07, // 209:1340
0x05, 0x48, 0x0E, 0x08, // 210:1352
0x05, 0x56, 0x0E, 0x08, // 211:1366
0x05, 0x64, 0x0E, 0x08, // 212:1380
0x05, 0x72, 0x0E, 0x08, // 213:1394
0x05, 0x80, 0x0E, 0x08, // 214:1408
0x05, 0x8E, 0x0A, 0x06, // 215:1422
0x05, 0x98, 0x0D, 0x08, // 216:1432
0x05, 0xA5, 0x0C, 0x07, // 217:1445
0x05, 0xB1, 0x0C, 0x07, // 218:1457
0x05, 0xBD, 0x0C, 0x07, // 219:1469
0x05, 0xC9, 0x0C, 0x07, // 220:1481
0x05, 0xD5, 0x0D, 0x07, // 221:1493
0x05, 0xE2, 0x0B, 0x07, // 222:1506
0x05, 0xED, 0x0C, 0x06, // 223:1517
0x05, 0xF9, 0x0A, 0x06, // 224:1529
0x06, 0x03, 0x0A, 0x06, // 225:1539
0x06, 0x0D, 0x0A, 0x06, // 226:1549
0x06, 0x17, 0x0A, 0x06, // 227:1559
0x06, 0x21, 0x0A, 0x06, // 228:1569
0x06, 0x2B, 0x0A, 0x06, // 229:1579
0x06, 0x35, 0x10, 0x09, // 230:1589
0x06, 0x45, 0x0A, 0x05, // 231:1605
0x06, 0x4F, 0x0A, 0x06, // 232:1615
0x06, 0x59, 0x0A, 0x06, // 233:1625
0x06, 0x63, 0x0A, 0x06, // 234:1635
0x06, 0x6D, 0x0A, 0x06, // 235:1645
0x06, 0x77, 0x05, 0x03, // 236:1655
0x06, 0x7C, 0x04, 0x03, // 237:1660
0x06, 0x80, 0x05, 0x03, // 238:1664
0x06, 0x85, 0x05, 0x03, // 239:1669
0x06, 0x8A, 0x0A, 0x06, // 240:1674
0x06, 0x94, 0x0A, 0x06, // 241:1684
0x06, 0x9E, 0x0A, 0x06, // 242:1694
0x06, 0xA8, 0x0A, 0x06, // 243:1704
0x06, 0xB2, 0x0A, 0x06, // 244:1714
0x06, 0xBC, 0x0A, 0x06, // 245:1724
0x06, 0xC6, 0x0A, 0x06, // 246:1734
0x06, 0xD0, 0x09, 0x05, // 247:1744
0x06, 0xD9, 0x0A, 0x06, // 248:1753
0x06, 0xE3, 0x0A, 0x06, // 249:1763
0x06, 0xED, 0x0A, 0x06, // 250:1773
0x06, 0xF7, 0x0A, 0x06, // 251:1783
0x07, 0x01, 0x0A, 0x06, // 252:1793
0x07, 0x0B, 0x09, 0x05, // 253:1803
0x07, 0x14, 0x0A, 0x06, // 254:1812
0x07, 0x1E, 0x09, 0x05, // 255:1822
// Jump Table:
0xFF, 0xFF, 0x00, 0x03, // 32:65535
0x00, 0x00, 0x04, 0x03, // 33:0
0x00, 0x04, 0x05, 0x04, // 34:4
0x00, 0x09, 0x09, 0x06, // 35:9
0x00, 0x12, 0x0A, 0x06, // 36:18
0x00, 0x1C, 0x10, 0x09, // 37:28
0x00, 0x2C, 0x0E, 0x07, // 38:44
0x00, 0x3A, 0x01, 0x02, // 39:58
0x00, 0x3B, 0x06, 0x03, // 40:59
0x00, 0x41, 0x06, 0x03, // 41:65
0x00, 0x47, 0x05, 0x04, // 42:71
0x00, 0x4C, 0x09, 0x06, // 43:76
0x00, 0x55, 0x04, 0x03, // 44:85
0x00, 0x59, 0x03, 0x03, // 45:89
0x00, 0x5C, 0x04, 0x03, // 46:92
0x00, 0x60, 0x05, 0x03, // 47:96
0x00, 0x65, 0x0A, 0x06, // 48:101
0x00, 0x6F, 0x08, 0x06, // 49:111
0x00, 0x77, 0x0A, 0x06, // 50:119
0x00, 0x81, 0x0A, 0x06, // 51:129
0x00, 0x8B, 0x0B, 0x06, // 52:139
0x00, 0x96, 0x0A, 0x06, // 53:150
0x00, 0xA0, 0x0A, 0x06, // 54:160
0x00, 0xAA, 0x09, 0x06, // 55:170
0x00, 0xB3, 0x0A, 0x06, // 56:179
0x00, 0xBD, 0x0A, 0x06, // 57:189
0x00, 0xC7, 0x04, 0x03, // 58:199
0x00, 0xCB, 0x04, 0x03, // 59:203
0x00, 0xCF, 0x0A, 0x06, // 60:207
0x00, 0xD9, 0x09, 0x06, // 61:217
0x00, 0xE2, 0x09, 0x06, // 62:226
0x00, 0xEB, 0x0B, 0x06, // 63:235
0x00, 0xF6, 0x14, 0x0A, // 64:246
0x01, 0x0A, 0x0E, 0x07, // 65:266
0x01, 0x18, 0x0C, 0x07, // 66:280
0x01, 0x24, 0x0C, 0x07, // 67:292
0x01, 0x30, 0x0B, 0x07, // 68:304
0x01, 0x3B, 0x0C, 0x07, // 69:315
0x01, 0x47, 0x09, 0x06, // 70:327
0x01, 0x50, 0x0D, 0x08, // 71:336
0x01, 0x5D, 0x0C, 0x07, // 72:349
0x01, 0x69, 0x04, 0x03, // 73:361
0x01, 0x6D, 0x08, 0x05, // 74:365
0x01, 0x75, 0x0E, 0x07, // 75:373
0x01, 0x83, 0x0C, 0x06, // 76:387
0x01, 0x8F, 0x10, 0x08, // 77:399
0x01, 0x9F, 0x0C, 0x07, // 78:415
0x01, 0xAB, 0x0E, 0x08, // 79:427
0x01, 0xB9, 0x0B, 0x07, // 80:441
0x01, 0xC4, 0x0E, 0x08, // 81:452
0x01, 0xD2, 0x0C, 0x07, // 82:466
0x01, 0xDE, 0x0C, 0x07, // 83:478
0x01, 0xEA, 0x0B, 0x06, // 84:490
0x01, 0xF5, 0x0C, 0x07, // 85:501
0x02, 0x01, 0x0D, 0x07, // 86:513
0x02, 0x0E, 0x11, 0x09, // 87:526
0x02, 0x1F, 0x0E, 0x07, // 88:543
0x02, 0x2D, 0x0D, 0x07, // 89:557
0x02, 0x3A, 0x0C, 0x06, // 90:570
0x02, 0x46, 0x06, 0x03, // 91:582
0x02, 0x4C, 0x06, 0x03, // 92:588
0x02, 0x52, 0x04, 0x03, // 93:594
0x02, 0x56, 0x09, 0x05, // 94:598
0x02, 0x5F, 0x0C, 0x06, // 95:607
0x02, 0x6B, 0x03, 0x03, // 96:619
0x02, 0x6E, 0x0A, 0x06, // 97:622
0x02, 0x78, 0x0A, 0x06, // 98:632
0x02, 0x82, 0x0A, 0x05, // 99:642
0x02, 0x8C, 0x0A, 0x06, // 100:652
0x02, 0x96, 0x0A, 0x06, // 101:662
0x02, 0xA0, 0x05, 0x03, // 102:672
0x02, 0xA5, 0x0A, 0x06, // 103:677
0x02, 0xAF, 0x0A, 0x06, // 104:687
0x02, 0xB9, 0x04, 0x02, // 105:697
0x02, 0xBD, 0x04, 0x02, // 106:701
0x02, 0xC1, 0x08, 0x05, // 107:705
0x02, 0xC9, 0x04, 0x02, // 108:713
0x02, 0xCD, 0x10, 0x08, // 109:717
0x02, 0xDD, 0x0A, 0x06, // 110:733
0x02, 0xE7, 0x0A, 0x06, // 111:743
0x02, 0xF1, 0x0A, 0x06, // 112:753
0x02, 0xFB, 0x0A, 0x06, // 113:763
0x03, 0x05, 0x05, 0x03, // 114:773
0x03, 0x0A, 0x08, 0x05, // 115:778
0x03, 0x12, 0x06, 0x03, // 116:786
0x03, 0x18, 0x0A, 0x06, // 117:792
0x03, 0x22, 0x09, 0x05, // 118:802
0x03, 0x2B, 0x0E, 0x07, // 119:811
0x03, 0x39, 0x0A, 0x05, // 120:825
0x03, 0x43, 0x09, 0x05, // 121:835
0x03, 0x4C, 0x0A, 0x05, // 122:844
0x03, 0x56, 0x06, 0x03, // 123:854
0x03, 0x5C, 0x04, 0x03, // 124:860
0x03, 0x60, 0x05, 0x03, // 125:864
0x03, 0x65, 0x09, 0x06, // 126:869
0xFF, 0xFF, 0x00, 0x00, // 127:65535
0xFF, 0xFF, 0x00, 0x0A, // 128:65535
0xFF, 0xFF, 0x00, 0x0A, // 129:65535
0xFF, 0xFF, 0x00, 0x0A, // 130:65535
0xFF, 0xFF, 0x00, 0x0A, // 131:65535
0xFF, 0xFF, 0x00, 0x0A, // 132:65535
0xFF, 0xFF, 0x00, 0x0A, // 133:65535
0xFF, 0xFF, 0x00, 0x0A, // 134:65535
0xFF, 0xFF, 0x00, 0x0A, // 135:65535
0xFF, 0xFF, 0x00, 0x0A, // 136:65535
0xFF, 0xFF, 0x00, 0x0A, // 137:65535
0xFF, 0xFF, 0x00, 0x0A, // 138:65535
0xFF, 0xFF, 0x00, 0x0A, // 139:65535
0xFF, 0xFF, 0x00, 0x0A, // 140:65535
0xFF, 0xFF, 0x00, 0x0A, // 141:65535
0xFF, 0xFF, 0x00, 0x0A, // 142:65535
0xFF, 0xFF, 0x00, 0x0A, // 143:65535
0xFF, 0xFF, 0x00, 0x0A, // 144:65535
0xFF, 0xFF, 0x00, 0x0A, // 145:65535
0xFF, 0xFF, 0x00, 0x0A, // 146:65535
0xFF, 0xFF, 0x00, 0x0A, // 147:65535
0xFF, 0xFF, 0x00, 0x0A, // 148:65535
0xFF, 0xFF, 0x00, 0x0A, // 149:65535
0xFF, 0xFF, 0x00, 0x0A, // 150:65535
0xFF, 0xFF, 0x00, 0x0A, // 151:65535
0xFF, 0xFF, 0x00, 0x0A, // 152:65535
0xFF, 0xFF, 0x00, 0x0A, // 153:65535
0xFF, 0xFF, 0x00, 0x0A, // 154:65535
0xFF, 0xFF, 0x00, 0x0A, // 155:65535
0xFF, 0xFF, 0x00, 0x0A, // 156:65535
0xFF, 0xFF, 0x00, 0x0A, // 157:65535
0xFF, 0xFF, 0x00, 0x0A, // 158:65535
0xFF, 0xFF, 0x00, 0x0A, // 159:65535
0xFF, 0xFF, 0x00, 0x03, // 160:65535
0x03, 0x6E, 0x04, 0x03, // 161:878
0x03, 0x72, 0x0A, 0x06, // 162:882
0x03, 0x7C, 0x0C, 0x06, // 163:892
0x03, 0x88, 0x0A, 0x06, // 164:904
0x03, 0x92, 0x0A, 0x06, // 165:914
0x03, 0x9C, 0x04, 0x03, // 166:924
0x03, 0xA0, 0x0A, 0x06, // 167:928
0x03, 0xAA, 0x05, 0x03, // 168:938
0x03, 0xAF, 0x0D, 0x07, // 169:943
0x03, 0xBC, 0x07, 0x04, // 170:956
0x03, 0xC3, 0x0A, 0x06, // 171:963
0x03, 0xCD, 0x09, 0x06, // 172:973
0x03, 0xD6, 0x03, 0x03, // 173:982
0x03, 0xD9, 0x0D, 0x07, // 174:985
0x03, 0xE6, 0x0B, 0x06, // 175:998
0x03, 0xF1, 0x07, 0x04, // 176:1009
0x03, 0xF8, 0x0A, 0x05, // 177:1016
0x04, 0x02, 0x05, 0x03, // 178:1026
0x04, 0x07, 0x05, 0x03, // 179:1031
0x04, 0x0C, 0x05, 0x03, // 180:1036
0x04, 0x11, 0x0A, 0x06, // 181:1041
0x04, 0x1B, 0x09, 0x05, // 182:1051
0x04, 0x24, 0x03, 0x03, // 183:1060
0x04, 0x27, 0x06, 0x03, // 184:1063
0x04, 0x2D, 0x05, 0x03, // 185:1069
0x04, 0x32, 0x07, 0x04, // 186:1074
0x04, 0x39, 0x0A, 0x06, // 187:1081
0x04, 0x43, 0x10, 0x08, // 188:1091
0x04, 0x53, 0x10, 0x08, // 189:1107
0x04, 0x63, 0x10, 0x08, // 190:1123
0x04, 0x73, 0x0A, 0x06, // 191:1139
0x04, 0x7D, 0x0E, 0x07, // 192:1149
0x04, 0x8B, 0x0E, 0x07, // 193:1163
0x04, 0x99, 0x0E, 0x07, // 194:1177
0x04, 0xA7, 0x0E, 0x07, // 195:1191
0x04, 0xB5, 0x0E, 0x07, // 196:1205
0x04, 0xC3, 0x0E, 0x07, // 197:1219
0x04, 0xD1, 0x12, 0x0A, // 198:1233
0x04, 0xE3, 0x0C, 0x07, // 199:1251
0x04, 0xEF, 0x0C, 0x07, // 200:1263
0x04, 0xFB, 0x0C, 0x07, // 201:1275
0x05, 0x07, 0x0C, 0x07, // 202:1287
0x05, 0x13, 0x0C, 0x07, // 203:1299
0x05, 0x1F, 0x05, 0x03, // 204:1311
0x05, 0x24, 0x04, 0x03, // 205:1316
0x05, 0x28, 0x04, 0x03, // 206:1320
0x05, 0x2C, 0x05, 0x03, // 207:1324
0x05, 0x31, 0x0B, 0x07, // 208:1329
0x05, 0x3C, 0x0C, 0x07, // 209:1340
0x05, 0x48, 0x0E, 0x08, // 210:1352
0x05, 0x56, 0x0E, 0x08, // 211:1366
0x05, 0x64, 0x0E, 0x08, // 212:1380
0x05, 0x72, 0x0E, 0x08, // 213:1394
0x05, 0x80, 0x0E, 0x08, // 214:1408
0x05, 0x8E, 0x0A, 0x06, // 215:1422
0x05, 0x98, 0x0D, 0x08, // 216:1432
0x05, 0xA5, 0x0C, 0x07, // 217:1445
0x05, 0xB1, 0x0C, 0x07, // 218:1457
0x05, 0xBD, 0x0C, 0x07, // 219:1469
0x05, 0xC9, 0x0C, 0x07, // 220:1481
0x05, 0xD5, 0x0D, 0x07, // 221:1493
0x05, 0xE2, 0x0B, 0x07, // 222:1506
0x05, 0xED, 0x0C, 0x06, // 223:1517
0x05, 0xF9, 0x0A, 0x06, // 224:1529
0x06, 0x03, 0x0A, 0x06, // 225:1539
0x06, 0x0D, 0x0A, 0x06, // 226:1549
0x06, 0x17, 0x0A, 0x06, // 227:1559
0x06, 0x21, 0x0A, 0x06, // 228:1569
0x06, 0x2B, 0x0A, 0x06, // 229:1579
0x06, 0x35, 0x10, 0x09, // 230:1589
0x06, 0x45, 0x0A, 0x05, // 231:1605
0x06, 0x4F, 0x0A, 0x06, // 232:1615
0x06, 0x59, 0x0A, 0x06, // 233:1625
0x06, 0x63, 0x0A, 0x06, // 234:1635
0x06, 0x6D, 0x0A, 0x06, // 235:1645
0x06, 0x77, 0x05, 0x03, // 236:1655
0x06, 0x7C, 0x04, 0x03, // 237:1660
0x06, 0x80, 0x05, 0x03, // 238:1664
0x06, 0x85, 0x05, 0x03, // 239:1669
0x06, 0x8A, 0x0A, 0x06, // 240:1674
0x06, 0x94, 0x0A, 0x06, // 241:1684
0x06, 0x9E, 0x0A, 0x06, // 242:1694
0x06, 0xA8, 0x0A, 0x06, // 243:1704
0x06, 0xB2, 0x0A, 0x06, // 244:1714
0x06, 0xBC, 0x0A, 0x06, // 245:1724
0x06, 0xC6, 0x0A, 0x06, // 246:1734
0x06, 0xD0, 0x09, 0x05, // 247:1744
0x06, 0xD9, 0x0A, 0x06, // 248:1753
0x06, 0xE3, 0x0A, 0x06, // 249:1763
0x06, 0xED, 0x0A, 0x06, // 250:1773
0x06, 0xF7, 0x0A, 0x06, // 251:1783
0x07, 0x01, 0x0A, 0x06, // 252:1793
0x07, 0x0B, 0x09, 0x05, // 253:1803
0x07, 0x14, 0x0A, 0x06, // 254:1812
0x07, 0x1E, 0x09, 0x05, // 255:1822
// Font Data:
0x00,0x00,0xF8,0x02, // 33
0x38,0x00,0x00,0x00,0x38, // 34
0xA0,0x03,0xE0,0x00,0xB8,0x03,0xE0,0x00,0xB8, // 35
0x30,0x01,0x28,0x02,0xF8,0x07,0x48,0x02,0x90,0x01, // 36
0x00,0x00,0x30,0x00,0x48,0x00,0x30,0x03,0xC0,0x00,0xB0,0x01,0x48,0x02,0x80,0x01, // 37
0x80,0x01,0x50,0x02,0x68,0x02,0xA8,0x02,0x18,0x01,0x80,0x03,0x80,0x02, // 38
0x38, // 39
0xE0,0x03,0x10,0x04,0x08,0x08, // 40
0x08,0x08,0x10,0x04,0xE0,0x03, // 41
0x28,0x00,0x18,0x00,0x28, // 42
0x40,0x00,0x40,0x00,0xF0,0x01,0x40,0x00,0x40, // 43
0x00,0x00,0x00,0x06, // 44
0x80,0x00,0x80, // 45
0x00,0x00,0x00,0x02, // 46
0x00,0x03,0xE0,0x00,0x18, // 47
0xF0,0x01,0x08,0x02,0x08,0x02,0x08,0x02,0xF0,0x01, // 48
0x00,0x00,0x20,0x00,0x10,0x00,0xF8,0x03, // 49
0x10,0x02,0x08,0x03,0x88,0x02,0x48,0x02,0x30,0x02, // 50
0x10,0x01,0x08,0x02,0x48,0x02,0x48,0x02,0xB0,0x01, // 51
0xC0,0x00,0xA0,0x00,0x90,0x00,0x88,0x00,0xF8,0x03,0x80, // 52
0x60,0x01,0x38,0x02,0x28,0x02,0x28,0x02,0xC8,0x01, // 53
0xF0,0x01,0x28,0x02,0x28,0x02,0x28,0x02,0xD0,0x01, // 54
0x08,0x00,0x08,0x03,0xC8,0x00,0x38,0x00,0x08, // 55
0xB0,0x01,0x48,0x02,0x48,0x02,0x48,0x02,0xB0,0x01, // 56
0x70,0x01,0x88,0x02,0x88,0x02,0x88,0x02,0xF0,0x01, // 57
0x00,0x00,0x20,0x02, // 58
0x00,0x00,0x20,0x06, // 59
0x00,0x00,0x40,0x00,0xA0,0x00,0xA0,0x00,0x10,0x01, // 60
0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0, // 61
0x00,0x00,0x10,0x01,0xA0,0x00,0xA0,0x00,0x40, // 62
0x10,0x00,0x08,0x00,0x08,0x00,0xC8,0x02,0x48,0x00,0x30, // 63
0x00,0x00,0xC0,0x03,0x30,0x04,0xD0,0x09,0x28,0x0A,0x28,0x0A,0xC8,0x0B,0x68,0x0A,0x10,0x05,0xE0,0x04, // 64
0x00,0x02,0xC0,0x01,0xB0,0x00,0x88,0x00,0xB0,0x00,0xC0,0x01,0x00,0x02, // 65
0x00,0x00,0xF8,0x03,0x48,0x02,0x48,0x02,0x48,0x02,0xF0,0x01, // 66
0x00,0x00,0xF0,0x01,0x08,0x02,0x08,0x02,0x08,0x02,0x10,0x01, // 67
0x00,0x00,0xF8,0x03,0x08,0x02,0x08,0x02,0x10,0x01,0xE0, // 68
0x00,0x00,0xF8,0x03,0x48,0x02,0x48,0x02,0x48,0x02,0x48,0x02, // 69
0x00,0x00,0xF8,0x03,0x48,0x00,0x48,0x00,0x08, // 70
0x00,0x00,0xE0,0x00,0x10,0x01,0x08,0x02,0x48,0x02,0x50,0x01,0xC0, // 71
0x00,0x00,0xF8,0x03,0x40,0x00,0x40,0x00,0x40,0x00,0xF8,0x03, // 72
0x00,0x00,0xF8,0x03, // 73
0x00,0x03,0x00,0x02,0x00,0x02,0xF8,0x01, // 74
0x00,0x00,0xF8,0x03,0x80,0x00,0x60,0x00,0x90,0x00,0x08,0x01,0x00,0x02, // 75
0x00,0x00,0xF8,0x03,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02, // 76
0x00,0x00,0xF8,0x03,0x30,0x00,0xC0,0x01,0x00,0x02,0xC0,0x01,0x30,0x00,0xF8,0x03, // 77
0x00,0x00,0xF8,0x03,0x30,0x00,0x40,0x00,0x80,0x01,0xF8,0x03, // 78
0x00,0x00,0xF0,0x01,0x08,0x02,0x08,0x02,0x08,0x02,0x08,0x02,0xF0,0x01, // 79
0x00,0x00,0xF8,0x03,0x48,0x00,0x48,0x00,0x48,0x00,0x30, // 80
0x00,0x00,0xF0,0x01,0x08,0x02,0x08,0x02,0x08,0x03,0x08,0x03,0xF0,0x02, // 81
0x00,0x00,0xF8,0x03,0x48,0x00,0x48,0x00,0xC8,0x00,0x30,0x03, // 82
0x00,0x00,0x30,0x01,0x48,0x02,0x48,0x02,0x48,0x02,0x90,0x01, // 83
0x00,0x00,0x08,0x00,0x08,0x00,0xF8,0x03,0x08,0x00,0x08, // 84
0x00,0x00,0xF8,0x01,0x00,0x02,0x00,0x02,0x00,0x02,0xF8,0x01, // 85
0x08,0x00,0x70,0x00,0x80,0x01,0x00,0x02,0x80,0x01,0x70,0x00,0x08, // 86
0x18,0x00,0xE0,0x01,0x00,0x02,0xF0,0x01,0x08,0x00,0xF0,0x01,0x00,0x02,0xE0,0x01,0x18, // 87
0x00,0x02,0x08,0x01,0x90,0x00,0x60,0x00,0x90,0x00,0x08,0x01,0x00,0x02, // 88
0x08,0x00,0x10,0x00,0x20,0x00,0xC0,0x03,0x20,0x00,0x10,0x00,0x08, // 89
0x08,0x03,0x88,0x02,0xC8,0x02,0x68,0x02,0x38,0x02,0x18,0x02, // 90
0x00,0x00,0xF8,0x0F,0x08,0x08, // 91
0x18,0x00,0xE0,0x00,0x00,0x03, // 92
0x08,0x08,0xF8,0x0F, // 93
0x40,0x00,0x30,0x00,0x08,0x00,0x30,0x00,0x40, // 94
0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x08, // 95
0x08,0x00,0x10, // 96
0x00,0x00,0x00,0x03,0xA0,0x02,0xA0,0x02,0xE0,0x03, // 97
0x00,0x00,0xF8,0x03,0x20,0x02,0x20,0x02,0xC0,0x01, // 98
0x00,0x00,0xC0,0x01,0x20,0x02,0x20,0x02,0x40,0x01, // 99
0x00,0x00,0xC0,0x01,0x20,0x02,0x20,0x02,0xF8,0x03, // 100
0x00,0x00,0xC0,0x01,0xA0,0x02,0xA0,0x02,0xC0,0x02, // 101
0x20,0x00,0xF0,0x03,0x28, // 102
0x00,0x00,0xC0,0x05,0x20,0x0A,0x20,0x0A,0xE0,0x07, // 103
0x00,0x00,0xF8,0x03,0x20,0x00,0x20,0x00,0xC0,0x03, // 104
0x00,0x00,0xE8,0x03, // 105
0x00,0x08,0xE8,0x07, // 106
0xF8,0x03,0x80,0x00,0xC0,0x01,0x20,0x02, // 107
0x00,0x00,0xF8,0x03, // 108
0x00,0x00,0xE0,0x03,0x20,0x00,0x20,0x00,0xE0,0x03,0x20,0x00,0x20,0x00,0xC0,0x03, // 109
0x00,0x00,0xE0,0x03,0x20,0x00,0x20,0x00,0xC0,0x03, // 110
0x00,0x00,0xC0,0x01,0x20,0x02,0x20,0x02,0xC0,0x01, // 111
0x00,0x00,0xE0,0x0F,0x20,0x02,0x20,0x02,0xC0,0x01, // 112
0x00,0x00,0xC0,0x01,0x20,0x02,0x20,0x02,0xE0,0x0F, // 113
0x00,0x00,0xE0,0x03,0x20, // 114
0x40,0x02,0xA0,0x02,0xA0,0x02,0x20,0x01, // 115
0x20,0x00,0xF8,0x03,0x20,0x02, // 116
0x00,0x00,0xE0,0x01,0x00,0x02,0x00,0x02,0xE0,0x03, // 117
0x20,0x00,0xC0,0x01,0x00,0x02,0xC0,0x01,0x20, // 118
0xE0,0x01,0x00,0x02,0xC0,0x01,0x20,0x00,0xC0,0x01,0x00,0x02,0xE0,0x01, // 119
0x20,0x02,0x40,0x01,0x80,0x00,0x40,0x01,0x20,0x02, // 120
0x20,0x00,0xC0,0x09,0x00,0x06,0xC0,0x01,0x20, // 121
0x20,0x02,0x20,0x03,0xA0,0x02,0x60,0x02,0x20,0x02, // 122
0x80,0x00,0x78,0x0F,0x08,0x08, // 123
0x00,0x00,0xF8,0x0F, // 124
0x08,0x08,0x78,0x0F,0x80, // 125
0xC0,0x00,0x40,0x00,0xC0,0x00,0x80,0x00,0xC0, // 126
0x00,0x00,0xA0,0x0F, // 161
0x00,0x00,0xC0,0x01,0xA0,0x0F,0x78,0x02,0x40,0x01, // 162
0x40,0x02,0x70,0x03,0xC8,0x02,0x48,0x02,0x08,0x02,0x10,0x02, // 163
0x00,0x00,0xE0,0x01,0x20,0x01,0x20,0x01,0xE0,0x01, // 164
0x48,0x01,0x70,0x01,0xC0,0x03,0x70,0x01,0x48,0x01, // 165
0x00,0x00,0x38,0x0F, // 166
0xD0,0x04,0x28,0x09,0x48,0x09,0x48,0x0A,0x90,0x05, // 167
0x08,0x00,0x00,0x00,0x08, // 168
0xE0,0x00,0x10,0x01,0x48,0x02,0xA8,0x02,0xA8,0x02,0x10,0x01,0xE0, // 169
0x68,0x00,0x68,0x00,0x68,0x00,0x78, // 170
0x00,0x00,0x80,0x01,0x40,0x02,0x80,0x01,0x40,0x02, // 171
0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0xE0, // 172
0x80,0x00,0x80, // 173
0xE0,0x00,0x10,0x01,0xE8,0x02,0x68,0x02,0xC8,0x02,0x10,0x01,0xE0, // 174
0x02,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02, // 175
0x00,0x00,0x38,0x00,0x28,0x00,0x38, // 176
0x40,0x02,0x40,0x02,0xF0,0x03,0x40,0x02,0x40,0x02, // 177
0x48,0x00,0x68,0x00,0x58, // 178
0x48,0x00,0x58,0x00,0x68, // 179
0x00,0x00,0x10,0x00,0x08, // 180
0x00,0x00,0xE0,0x0F,0x00,0x02,0x00,0x02,0xE0,0x03, // 181
0x70,0x00,0xF8,0x0F,0x08,0x00,0xF8,0x0F,0x08, // 182
0x00,0x00,0x40, // 183
0x00,0x00,0x00,0x14,0x00,0x18, // 184
0x00,0x00,0x10,0x00,0x78, // 185
0x30,0x00,0x48,0x00,0x48,0x00,0x30, // 186
0x00,0x00,0x40,0x02,0x80,0x01,0x40,0x02,0x80,0x01, // 187
0x00,0x00,0x10,0x02,0x78,0x01,0xC0,0x00,0x20,0x01,0x90,0x01,0xC8,0x03,0x00,0x01, // 188
0x00,0x00,0x10,0x02,0x78,0x01,0x80,0x00,0x60,0x00,0x50,0x02,0x48,0x03,0xC0,0x02, // 189
0x48,0x00,0x58,0x00,0x68,0x03,0x80,0x00,0x60,0x01,0x90,0x01,0xC8,0x03,0x00,0x01, // 190
0x00,0x00,0x00,0x06,0x00,0x09,0xA0,0x09,0x00,0x04, // 191
0x00,0x02,0xC0,0x01,0xB0,0x00,0x89,0x00,0xB2,0x00,0xC0,0x01,0x00,0x02, // 192
0x00,0x02,0xC0,0x01,0xB0,0x00,0x8A,0x00,0xB1,0x00,0xC0,0x01,0x00,0x02, // 193
0x00,0x02,0xC0,0x01,0xB2,0x00,0x89,0x00,0xB2,0x00,0xC0,0x01,0x00,0x02, // 194
0x00,0x02,0xC2,0x01,0xB1,0x00,0x8A,0x00,0xB1,0x00,0xC0,0x01,0x00,0x02, // 195
0x00,0x02,0xC0,0x01,0xB2,0x00,0x88,0x00,0xB2,0x00,0xC0,0x01,0x00,0x02, // 196
0x00,0x02,0xC0,0x01,0xBE,0x00,0x8A,0x00,0xBE,0x00,0xC0,0x01,0x00,0x02, // 197
0x00,0x03,0xC0,0x00,0xE0,0x00,0x98,0x00,0x88,0x00,0xF8,0x03,0x48,0x02,0x48,0x02,0x48,0x02, // 198
0x00,0x00,0xF0,0x01,0x08,0x02,0x08,0x16,0x08,0x1A,0x10,0x01, // 199
0x00,0x00,0xF8,0x03,0x49,0x02,0x4A,0x02,0x48,0x02,0x48,0x02, // 200
0x00,0x00,0xF8,0x03,0x48,0x02,0x4A,0x02,0x49,0x02,0x48,0x02, // 201
0x00,0x00,0xFA,0x03,0x49,0x02,0x4A,0x02,0x48,0x02,0x48,0x02, // 202
0x00,0x00,0xF8,0x03,0x4A,0x02,0x48,0x02,0x4A,0x02,0x48,0x02, // 203
0x00,0x00,0xF9,0x03,0x02, // 204
0x02,0x00,0xF9,0x03, // 205
0x01,0x00,0xFA,0x03, // 206
0x02,0x00,0xF8,0x03,0x02, // 207
0x40,0x00,0xF8,0x03,0x48,0x02,0x48,0x02,0x10,0x01,0xE0, // 208
0x00,0x00,0xFA,0x03,0x31,0x00,0x42,0x00,0x81,0x01,0xF8,0x03, // 209
0x00,0x00,0xF0,0x01,0x08,0x02,0x09,0x02,0x0A,0x02,0x08,0x02,0xF0,0x01, // 210
0x00,0x00,0xF0,0x01,0x08,0x02,0x0A,0x02,0x09,0x02,0x08,0x02,0xF0,0x01, // 211
0x00,0x00,0xF0,0x01,0x08,0x02,0x0A,0x02,0x09,0x02,0x0A,0x02,0xF0,0x01, // 212
0x00,0x00,0xF0,0x01,0x0A,0x02,0x09,0x02,0x0A,0x02,0x09,0x02,0xF0,0x01, // 213
0x00,0x00,0xF0,0x01,0x0A,0x02,0x08,0x02,0x0A,0x02,0x08,0x02,0xF0,0x01, // 214
0x10,0x01,0xA0,0x00,0xE0,0x00,0xA0,0x00,0x10,0x01, // 215
0x00,0x00,0xF0,0x02,0x08,0x03,0xC8,0x02,0x28,0x02,0x18,0x03,0xE8, // 216
0x00,0x00,0xF8,0x01,0x01,0x02,0x02,0x02,0x00,0x02,0xF8,0x01, // 217
0x00,0x00,0xF8,0x01,0x02,0x02,0x01,0x02,0x00,0x02,0xF8,0x01, // 218
0x00,0x00,0xF8,0x01,0x02,0x02,0x01,0x02,0x02,0x02,0xF8,0x01, // 219
0x00,0x00,0xF8,0x01,0x02,0x02,0x00,0x02,0x02,0x02,0xF8,0x01, // 220
0x08,0x00,0x10,0x00,0x20,0x00,0xC2,0x03,0x21,0x00,0x10,0x00,0x08, // 221
0x00,0x00,0xF8,0x03,0x10,0x01,0x10,0x01,0x10,0x01,0xE0, // 222
0x00,0x00,0xF0,0x03,0x08,0x01,0x48,0x02,0xB0,0x02,0x80,0x01, // 223
0x00,0x00,0x00,0x03,0xA4,0x02,0xA8,0x02,0xE0,0x03, // 224
0x00,0x00,0x00,0x03,0xA8,0x02,0xA4,0x02,0xE0,0x03, // 225
0x00,0x00,0x00,0x03,0xA8,0x02,0xA4,0x02,0xE8,0x03, // 226
0x00,0x00,0x08,0x03,0xA4,0x02,0xA8,0x02,0xE4,0x03, // 227
0x00,0x00,0x00,0x03,0xA8,0x02,0xA0,0x02,0xE8,0x03, // 228
0x00,0x00,0x00,0x03,0xAE,0x02,0xAA,0x02,0xEE,0x03, // 229
0x00,0x00,0x40,0x03,0xA0,0x02,0xA0,0x02,0xC0,0x01,0xA0,0x02,0xA0,0x02,0xC0,0x02, // 230
0x00,0x00,0xC0,0x01,0x20,0x16,0x20,0x1A,0x40,0x01, // 231
0x00,0x00,0xC0,0x01,0xA4,0x02,0xA8,0x02,0xC0,0x02, // 232
0x00,0x00,0xC0,0x01,0xA8,0x02,0xA4,0x02,0xC0,0x02, // 233
0x00,0x00,0xC0,0x01,0xA8,0x02,0xA4,0x02,0xC8,0x02, // 234
0x00,0x00,0xC0,0x01,0xA8,0x02,0xA0,0x02,0xC8,0x02, // 235
0x00,0x00,0xE4,0x03,0x08, // 236
0x08,0x00,0xE4,0x03, // 237
0x08,0x00,0xE4,0x03,0x08, // 238
0x08,0x00,0xE0,0x03,0x08, // 239
0x00,0x00,0xC0,0x01,0x28,0x02,0x38,0x02,0xE0,0x01, // 240
0x00,0x00,0xE8,0x03,0x24,0x00,0x28,0x00,0xC4,0x03, // 241
0x00,0x00,0xC0,0x01,0x24,0x02,0x28,0x02,0xC0,0x01, // 242
0x00,0x00,0xC0,0x01,0x28,0x02,0x24,0x02,0xC0,0x01, // 243
0x00,0x00,0xC0,0x01,0x28,0x02,0x24,0x02,0xC8,0x01, // 244
0x00,0x00,0xC8,0x01,0x24,0x02,0x28,0x02,0xC4,0x01, // 245
0x00,0x00,0xC0,0x01,0x28,0x02,0x20,0x02,0xC8,0x01, // 246
0x40,0x00,0x40,0x00,0x50,0x01,0x40,0x00,0x40, // 247
0x00,0x00,0xC0,0x02,0xA0,0x03,0x60,0x02,0xA0,0x01, // 248
0x00,0x00,0xE0,0x01,0x04,0x02,0x08,0x02,0xE0,0x03, // 249
0x00,0x00,0xE0,0x01,0x08,0x02,0x04,0x02,0xE0,0x03, // 250
0x00,0x00,0xE8,0x01,0x04,0x02,0x08,0x02,0xE0,0x03, // 251
0x00,0x00,0xE0,0x01,0x08,0x02,0x00,0x02,0xE8,0x03, // 252
0x20,0x00,0xC0,0x09,0x08,0x06,0xC4,0x01,0x20, // 253
0x00,0x00,0xF8,0x0F,0x20,0x02,0x20,0x02,0xC0,0x01, // 254
0x20,0x00,0xC8,0x09,0x00,0x06,0xC8,0x01,0x20 // 255
// Font Data:
0x00, 0x00, 0xF8, 0x02, // 33
0x38, 0x00, 0x00, 0x00, 0x38, // 34
0xA0, 0x03, 0xE0, 0x00, 0xB8, 0x03, 0xE0, 0x00, 0xB8, // 35
0x30, 0x01, 0x28, 0x02, 0xF8, 0x07, 0x48, 0x02, 0x90, 0x01, // 36
0x00, 0x00, 0x30, 0x00, 0x48, 0x00, 0x30, 0x03, 0xC0, 0x00, 0xB0, 0x01, 0x48, 0x02, 0x80, 0x01, // 37
0x80, 0x01, 0x50, 0x02, 0x68, 0x02, 0xA8, 0x02, 0x18, 0x01, 0x80, 0x03, 0x80, 0x02, // 38
0x38, // 39
0xE0, 0x03, 0x10, 0x04, 0x08, 0x08, // 40
0x08, 0x08, 0x10, 0x04, 0xE0, 0x03, // 41
0x28, 0x00, 0x18, 0x00, 0x28, // 42
0x40, 0x00, 0x40, 0x00, 0xF0, 0x01, 0x40, 0x00, 0x40, // 43
0x00, 0x00, 0x00, 0x06, // 44
0x80, 0x00, 0x80, // 45
0x00, 0x00, 0x00, 0x02, // 46
0x00, 0x03, 0xE0, 0x00, 0x18, // 47
0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 48
0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0xF8, 0x03, // 49
0x10, 0x02, 0x08, 0x03, 0x88, 0x02, 0x48, 0x02, 0x30, 0x02, // 50
0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 51
0xC0, 0x00, 0xA0, 0x00, 0x90, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x80, // 52
0x60, 0x01, 0x38, 0x02, 0x28, 0x02, 0x28, 0x02, 0xC8, 0x01, // 53
0xF0, 0x01, 0x28, 0x02, 0x28, 0x02, 0x28, 0x02, 0xD0, 0x01, // 54
0x08, 0x00, 0x08, 0x03, 0xC8, 0x00, 0x38, 0x00, 0x08, // 55
0xB0, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 56
0x70, 0x01, 0x88, 0x02, 0x88, 0x02, 0x88, 0x02, 0xF0, 0x01, // 57
0x00, 0x00, 0x20, 0x02, // 58
0x00, 0x00, 0x20, 0x06, // 59
0x00, 0x00, 0x40, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 60
0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, // 61
0x00, 0x00, 0x10, 0x01, 0xA0, 0x00, 0xA0, 0x00, 0x40, // 62
0x10, 0x00, 0x08, 0x00, 0x08, 0x00, 0xC8, 0x02, 0x48, 0x00, 0x30, // 63
0x00, 0x00, 0xC0, 0x03, 0x30, 0x04, 0xD0, 0x09, 0x28, 0x0A, 0x28, 0x0A, 0xC8, 0x0B, 0x68, 0x0A, 0x10, 0x05, 0xE0, 0x04, // 64
0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x88, 0x00, 0xB0, 0x00, 0xC0, 0x01, 0x00, 0x02, // 65
0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xF0, 0x01, // 66
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, // 67
0x00, 0x00, 0xF8, 0x03, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, 0xE0, // 68
0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 69
0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x08, // 70
0x00, 0x00, 0xE0, 0x00, 0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x50, 0x01, 0xC0, // 71
0x00, 0x00, 0xF8, 0x03, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xF8, 0x03, // 72
0x00, 0x00, 0xF8, 0x03, // 73
0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 74
0x00, 0x00, 0xF8, 0x03, 0x80, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 75
0x00, 0x00, 0xF8, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 76
0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x30, 0x00, 0xF8, 0x03, // 77
0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0x40, 0x00, 0x80, 0x01, 0xF8, 0x03, // 78
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 79
0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 80
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x03, 0x08, 0x03, 0xF0, 0x02, // 81
0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0xC8, 0x00, 0x30, 0x03, // 82
0x00, 0x00, 0x30, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x90, 0x01, // 83
0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0xF8, 0x03, 0x08, 0x00, 0x08, // 84
0x00, 0x00, 0xF8, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 85
0x08, 0x00, 0x70, 0x00, 0x80, 0x01, 0x00, 0x02, 0x80, 0x01, 0x70, 0x00, 0x08, // 86
0x18, 0x00, 0xE0, 0x01, 0x00, 0x02, 0xF0, 0x01, 0x08, 0x00, 0xF0, 0x01, 0x00, 0x02, 0xE0, 0x01, 0x18, // 87
0x00, 0x02, 0x08, 0x01, 0x90, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 88
0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC0, 0x03, 0x20, 0x00, 0x10, 0x00, 0x08, // 89
0x08, 0x03, 0x88, 0x02, 0xC8, 0x02, 0x68, 0x02, 0x38, 0x02, 0x18, 0x02, // 90
0x00, 0x00, 0xF8, 0x0F, 0x08, 0x08, // 91
0x18, 0x00, 0xE0, 0x00, 0x00, 0x03, // 92
0x08, 0x08, 0xF8, 0x0F, // 93
0x40, 0x00, 0x30, 0x00, 0x08, 0x00, 0x30, 0x00, 0x40, // 94
0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, // 95
0x08, 0x00, 0x10, // 96
0x00, 0x00, 0x00, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xE0, 0x03, // 97
0x00, 0x00, 0xF8, 0x03, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 98
0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0x40, 0x01, // 99
0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xF8, 0x03, // 100
0x00, 0x00, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 101
0x20, 0x00, 0xF0, 0x03, 0x28, // 102
0x00, 0x00, 0xC0, 0x05, 0x20, 0x0A, 0x20, 0x0A, 0xE0, 0x07, // 103
0x00, 0x00, 0xF8, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 104
0x00, 0x00, 0xE8, 0x03, // 105
0x00, 0x08, 0xE8, 0x07, // 106
0xF8, 0x03, 0x80, 0x00, 0xC0, 0x01, 0x20, 0x02, // 107
0x00, 0x00, 0xF8, 0x03, // 108
0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 109
0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 110
0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 111
0x00, 0x00, 0xE0, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 112
0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xE0, 0x0F, // 113
0x00, 0x00, 0xE0, 0x03, 0x20, // 114
0x40, 0x02, 0xA0, 0x02, 0xA0, 0x02, 0x20, 0x01, // 115
0x20, 0x00, 0xF8, 0x03, 0x20, 0x02, // 116
0x00, 0x00, 0xE0, 0x01, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 117
0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, // 118
0xE0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xE0, 0x01, // 119
0x20, 0x02, 0x40, 0x01, 0x80, 0x00, 0x40, 0x01, 0x20, 0x02, // 120
0x20, 0x00, 0xC0, 0x09, 0x00, 0x06, 0xC0, 0x01, 0x20, // 121
0x20, 0x02, 0x20, 0x03, 0xA0, 0x02, 0x60, 0x02, 0x20, 0x02, // 122
0x80, 0x00, 0x78, 0x0F, 0x08, 0x08, // 123
0x00, 0x00, 0xF8, 0x0F, // 124
0x08, 0x08, 0x78, 0x0F, 0x80, // 125
0xC0, 0x00, 0x40, 0x00, 0xC0, 0x00, 0x80, 0x00, 0xC0, // 126
0x00, 0x00, 0xA0, 0x0F, // 161
0x00, 0x00, 0xC0, 0x01, 0xA0, 0x0F, 0x78, 0x02, 0x40, 0x01, // 162
0x40, 0x02, 0x70, 0x03, 0xC8, 0x02, 0x48, 0x02, 0x08, 0x02, 0x10, 0x02, // 163
0x00, 0x00, 0xE0, 0x01, 0x20, 0x01, 0x20, 0x01, 0xE0, 0x01, // 164
0x48, 0x01, 0x70, 0x01, 0xC0, 0x03, 0x70, 0x01, 0x48, 0x01, // 165
0x00, 0x00, 0x38, 0x0F, // 166
0xD0, 0x04, 0x28, 0x09, 0x48, 0x09, 0x48, 0x0A, 0x90, 0x05, // 167
0x08, 0x00, 0x00, 0x00, 0x08, // 168
0xE0, 0x00, 0x10, 0x01, 0x48, 0x02, 0xA8, 0x02, 0xA8, 0x02, 0x10, 0x01, 0xE0, // 169
0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x78, // 170
0x00, 0x00, 0x80, 0x01, 0x40, 0x02, 0x80, 0x01, 0x40, 0x02, // 171
0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xE0, // 172
0x80, 0x00, 0x80, // 173
0xE0, 0x00, 0x10, 0x01, 0xE8, 0x02, 0x68, 0x02, 0xC8, 0x02, 0x10, 0x01, 0xE0, // 174
0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 175
0x00, 0x00, 0x38, 0x00, 0x28, 0x00, 0x38, // 176
0x40, 0x02, 0x40, 0x02, 0xF0, 0x03, 0x40, 0x02, 0x40, 0x02, // 177
0x48, 0x00, 0x68, 0x00, 0x58, // 178
0x48, 0x00, 0x58, 0x00, 0x68, // 179
0x00, 0x00, 0x10, 0x00, 0x08, // 180
0x00, 0x00, 0xE0, 0x0F, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 181
0x70, 0x00, 0xF8, 0x0F, 0x08, 0x00, 0xF8, 0x0F, 0x08, // 182
0x00, 0x00, 0x40, // 183
0x00, 0x00, 0x00, 0x14, 0x00, 0x18, // 184
0x00, 0x00, 0x10, 0x00, 0x78, // 185
0x30, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 186
0x00, 0x00, 0x40, 0x02, 0x80, 0x01, 0x40, 0x02, 0x80, 0x01, // 187
0x00, 0x00, 0x10, 0x02, 0x78, 0x01, 0xC0, 0x00, 0x20, 0x01, 0x90, 0x01, 0xC8, 0x03, 0x00, 0x01, // 188
0x00, 0x00, 0x10, 0x02, 0x78, 0x01, 0x80, 0x00, 0x60, 0x00, 0x50, 0x02, 0x48, 0x03, 0xC0, 0x02, // 189
0x48, 0x00, 0x58, 0x00, 0x68, 0x03, 0x80, 0x00, 0x60, 0x01, 0x90, 0x01, 0xC8, 0x03, 0x00, 0x01, // 190
0x00, 0x00, 0x00, 0x06, 0x00, 0x09, 0xA0, 0x09, 0x00, 0x04, // 191
0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x89, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 192
0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x8A, 0x00, 0xB1, 0x00, 0xC0, 0x01, 0x00, 0x02, // 193
0x00, 0x02, 0xC0, 0x01, 0xB2, 0x00, 0x89, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 194
0x00, 0x02, 0xC2, 0x01, 0xB1, 0x00, 0x8A, 0x00, 0xB1, 0x00, 0xC0, 0x01, 0x00, 0x02, // 195
0x00, 0x02, 0xC0, 0x01, 0xB2, 0x00, 0x88, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 196
0x00, 0x02, 0xC0, 0x01, 0xBE, 0x00, 0x8A, 0x00, 0xBE, 0x00, 0xC0, 0x01, 0x00, 0x02, // 197
0x00, 0x03, 0xC0, 0x00, 0xE0, 0x00, 0x98, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 198
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x16, 0x08, 0x1A, 0x10, 0x01, // 199
0x00, 0x00, 0xF8, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x48, 0x02, 0x48, 0x02, // 200
0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x4A, 0x02, 0x49, 0x02, 0x48, 0x02, // 201
0x00, 0x00, 0xFA, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x48, 0x02, 0x48, 0x02, // 202
0x00, 0x00, 0xF8, 0x03, 0x4A, 0x02, 0x48, 0x02, 0x4A, 0x02, 0x48, 0x02, // 203
0x00, 0x00, 0xF9, 0x03, 0x02, // 204
0x02, 0x00, 0xF9, 0x03, // 205
0x01, 0x00, 0xFA, 0x03, // 206
0x02, 0x00, 0xF8, 0x03, 0x02, // 207
0x40, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x10, 0x01, 0xE0, // 208
0x00, 0x00, 0xFA, 0x03, 0x31, 0x00, 0x42, 0x00, 0x81, 0x01, 0xF8, 0x03, // 209
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x08, 0x02, 0xF0, 0x01, // 210
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x08, 0x02, 0xF0, 0x01, // 211
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x0A, 0x02, 0xF0, 0x01, // 212
0x00, 0x00, 0xF0, 0x01, 0x0A, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x09, 0x02, 0xF0, 0x01, // 213
0x00, 0x00, 0xF0, 0x01, 0x0A, 0x02, 0x08, 0x02, 0x0A, 0x02, 0x08, 0x02, 0xF0, 0x01, // 214
0x10, 0x01, 0xA0, 0x00, 0xE0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 215
0x00, 0x00, 0xF0, 0x02, 0x08, 0x03, 0xC8, 0x02, 0x28, 0x02, 0x18, 0x03, 0xE8, // 216
0x00, 0x00, 0xF8, 0x01, 0x01, 0x02, 0x02, 0x02, 0x00, 0x02, 0xF8, 0x01, // 217
0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x01, 0x02, 0x00, 0x02, 0xF8, 0x01, // 218
0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0xF8, 0x01, // 219
0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0xF8, 0x01, // 220
0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC2, 0x03, 0x21, 0x00, 0x10, 0x00, 0x08, // 221
0x00, 0x00, 0xF8, 0x03, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0xE0, // 222
0x00, 0x00, 0xF0, 0x03, 0x08, 0x01, 0x48, 0x02, 0xB0, 0x02, 0x80, 0x01, // 223
0x00, 0x00, 0x00, 0x03, 0xA4, 0x02, 0xA8, 0x02, 0xE0, 0x03, // 224
0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA4, 0x02, 0xE0, 0x03, // 225
0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA4, 0x02, 0xE8, 0x03, // 226
0x00, 0x00, 0x08, 0x03, 0xA4, 0x02, 0xA8, 0x02, 0xE4, 0x03, // 227
0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA0, 0x02, 0xE8, 0x03, // 228
0x00, 0x00, 0x00, 0x03, 0xAE, 0x02, 0xAA, 0x02, 0xEE, 0x03, // 229
0x00, 0x00, 0x40, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 230
0x00, 0x00, 0xC0, 0x01, 0x20, 0x16, 0x20, 0x1A, 0x40, 0x01, // 231
0x00, 0x00, 0xC0, 0x01, 0xA4, 0x02, 0xA8, 0x02, 0xC0, 0x02, // 232
0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA4, 0x02, 0xC0, 0x02, // 233
0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA4, 0x02, 0xC8, 0x02, // 234
0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA0, 0x02, 0xC8, 0x02, // 235
0x00, 0x00, 0xE4, 0x03, 0x08, // 236
0x08, 0x00, 0xE4, 0x03, // 237
0x08, 0x00, 0xE4, 0x03, 0x08, // 238
0x08, 0x00, 0xE0, 0x03, 0x08, // 239
0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x38, 0x02, 0xE0, 0x01, // 240
0x00, 0x00, 0xE8, 0x03, 0x24, 0x00, 0x28, 0x00, 0xC4, 0x03, // 241
0x00, 0x00, 0xC0, 0x01, 0x24, 0x02, 0x28, 0x02, 0xC0, 0x01, // 242
0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x24, 0x02, 0xC0, 0x01, // 243
0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x24, 0x02, 0xC8, 0x01, // 244
0x00, 0x00, 0xC8, 0x01, 0x24, 0x02, 0x28, 0x02, 0xC4, 0x01, // 245
0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x20, 0x02, 0xC8, 0x01, // 246
0x40, 0x00, 0x40, 0x00, 0x50, 0x01, 0x40, 0x00, 0x40, // 247
0x00, 0x00, 0xC0, 0x02, 0xA0, 0x03, 0x60, 0x02, 0xA0, 0x01, // 248
0x00, 0x00, 0xE0, 0x01, 0x04, 0x02, 0x08, 0x02, 0xE0, 0x03, // 249
0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x04, 0x02, 0xE0, 0x03, // 250
0x00, 0x00, 0xE8, 0x01, 0x04, 0x02, 0x08, 0x02, 0xE0, 0x03, // 251
0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x00, 0x02, 0xE8, 0x03, // 252
0x20, 0x00, 0xC0, 0x09, 0x08, 0x06, 0xC4, 0x01, 0x20, // 253
0x00, 0x00, 0xF8, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 254
0x20, 0x00, 0xC8, 0x09, 0x00, 0x06, 0xC8, 0x01, 0x20 // 255
};

16
src/freertosinc.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
// The FreeRTOS includes are in a different directory on ESP32 and I can't figure out how to make that work with platformio gcc options
// so this is my quick hack to make things work
#ifdef ARDUINO_ARCH_ESP32
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/semphr.h>
#include <freertos/queue.h>
#else
#include <FreeRTOS.h>
#include <task.h>
#include <semphr.h>
#include <queue.h>
#endif

View File

@@ -1,11 +1,8 @@
#define SATELLITE_IMAGE_WIDTH 16
#define SATELLITE_IMAGE_HEIGHT 15
const uint8_t SATELLITE_IMAGE[] PROGMEM = {
0x00, 0x08, 0x00, 0x1C, 0x00, 0x0E, 0x20, 0x07, 0x70, 0x02, 0xF8, 0x00,
0xF0, 0x01, 0xE0, 0x03, 0xC8, 0x01, 0x9C, 0x54, 0x0E, 0x52, 0x07, 0x48,
0x02, 0x26, 0x00, 0x10, 0x00, 0x0E
};
const uint8_t SATELLITE_IMAGE[] PROGMEM = {0x00, 0x08, 0x00, 0x1C, 0x00, 0x0E, 0x20, 0x07, 0x70, 0x02,
0xF8, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC8, 0x01, 0x9C, 0x54,
0x0E, 0x52, 0x07, 0x48, 0x02, 0x26, 0x00, 0x10, 0x00, 0x0E};
const
#include "icon.xbm"

35
src/lock.cpp Normal file
View File

@@ -0,0 +1,35 @@
#include "lock.h"
#include <cassert>
namespace meshtastic
{
Lock::Lock()
{
handle = xSemaphoreCreateBinary();
assert(handle);
assert(xSemaphoreGive(handle));
}
void Lock::lock()
{
assert(xSemaphoreTake(handle, portMAX_DELAY));
}
void Lock::unlock()
{
assert(xSemaphoreGive(handle));
}
LockGuard::LockGuard(Lock *lock) : lock(lock)
{
lock->lock();
}
LockGuard::~LockGuard()
{
lock->unlock();
}
} // namespace meshtastic

45
src/lock.h Normal file
View File

@@ -0,0 +1,45 @@
#pragma once
#include "freertosinc.h"
namespace meshtastic
{
// Simple wrapper around FreeRTOS API for implementing a mutex lock.
class Lock
{
public:
Lock();
Lock(const Lock &) = delete;
Lock &operator=(const Lock &) = delete;
/// Locks the lock.
//
// Must not be called from an ISR.
void lock();
// Unlocks the lock.
//
// Must not be called from an ISR.
void unlock();
private:
SemaphoreHandle_t handle;
};
// RAII lock guard.
class LockGuard
{
public:
LockGuard(Lock *lock);
~LockGuard();
LockGuard(const LockGuard &) = delete;
LockGuard &operator=(const LockGuard &) = delete;
private:
Lock *lock;
};
} // namespace meshtastic

433
src/main.cpp Normal file
View File

@@ -0,0 +1,433 @@
/*
Main module
# Modified by Kyle T. Gabriel to fix issue with incorrect GPS data for TTNMapper
Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "GPS.h"
#include "MeshRadio.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "Periodic.h"
#include "PowerFSM.h"
#include "configuration.h"
#include "error.h"
#include "power.h"
// #include "rom/rtc.h"
#include "FloodingRouter.h"
#include "screen.h"
#include "sleep.h"
#include <Wire.h>
// #include <driver/rtc_io.h>
#ifndef NO_ESP32
#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
// Global power status singleton
static meshtastic::PowerStatus powerStatus;
bool ssd1306_found;
bool axp192_found;
FloodingRouter realRouter;
Router &router = realRouter; // Users of router don't care what sort of subclass implements that API
// -----------------------------------------------------------------------------
// Application
// -----------------------------------------------------------------------------
void scanI2Cdevice(void)
{
byte err, addr;
int nDevices = 0;
for (addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
err = Wire.endTransmission();
if (err == 0) {
DEBUG_MSG("I2C device found at address 0x%x\n", addr);
nDevices++;
if (addr == SSD1306_ADDRESS) {
ssd1306_found = true;
DEBUG_MSG("ssd1306 display found\n");
}
#ifdef TBEAM_V10
if (addr == AXP192_SLAVE_ADDRESS) {
axp192_found = true;
DEBUG_MSG("axp192 PMU found\n");
}
#endif
} else if (err == 4) {
DEBUG_MSG("Unknow error at address 0x%x\n", addr);
}
}
if (nDevices == 0)
DEBUG_MSG("No I2C devices found\n");
else
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];
getMacAddr(dmac);
// Meshtastic_ab3c
static char name[20];
sprintf(name, "Meshtastic_%02x%02x", dmac[4], dmac[5]);
return name;
}
static MeshRadio *radio = NULL;
#include "Router.h"
void setup()
{
// Debug
#ifdef DEBUG_PORT
DEBUG_PORT.begin(SERIAL_BAUD);
#endif
initDeepSleep();
#ifdef VEXT_ENABLE
pinMode(VEXT_ENABLE, OUTPUT);
digitalWrite(VEXT_ENABLE, 0); // turn on the display power
#endif
#ifdef RESET_OLED
pinMode(RESET_OLED, OUTPUT);
digitalWrite(RESET_OLED, 1);
#endif
#ifdef I2C_SDA
Wire.begin(I2C_SDA, I2C_SCL);
scanI2Cdevice();
#endif
// Buttons & LED
#ifdef BUTTON_PIN
pinMode(BUTTON_PIN, INPUT_PULLUP);
digitalWrite(BUTTON_PIN, 1);
#endif
#ifdef LED_PIN
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, 1 ^ LED_INVERTED); // turn on for now
#endif
// Hello
DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", xstr(APP_VERSION), xstr(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
#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();
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);
#endif
if (radio && !radio->init())
recordCriticalError(ErrNoRadio);
// 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
// setBluetoothEnable(false); we now don't start bluetooth until we enter the proper state
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
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
void loop()
{
uint32_t msecstosleep = 1000 * 30; // How long can we sleep before we again need to service the main loop?
gps.loop();
router.loop();
powerFSM.run_machine();
service.loop();
ledPeriodic.loop();
// axpDebugOutput.loop();
#ifndef NO_ESP32
loopBLE();
#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
#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
// this boilerplate)
static bool wasPressed = false;
if (!digitalRead(BUTTON_PIN)) {
if (!wasPressed) { // just started a new press
DEBUG_MSG("pressing\n");
// doLightSleep();
// esp_pm_dump_locks(stdout); // FIXME, do this someplace better
wasPressed = true;
powerFSM.trigger(EVENT_PRESS);
}
} else if (wasPressed) {
// we just did a release
wasPressed = false;
}
#endif
// Show boot screen for first 3 seconds, then switch to normal operation.
static bool showingBootScreen = true;
if (showingBootScreen && (millis() > 3000)) {
screen.stopBootScreen();
showingBootScreen = false;
}
// Update the screen last, after we've figured out what to show.
screen.debug()->setNodeNumbersStatus(nodeDB.getNumOnlineNodes(), nodeDB.getNumNodes());
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();
// 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.
// DEBUG_MSG("msecs %d\n", msecstosleep);
// FIXME - until button press handling is done by interrupt (see polling above) we can't sleep very long at all or buttons
// feel slow
msecstosleep = 10;
delay(msecstosleep);
}

View File

@@ -1,4 +1,14 @@
#pragma once
#include "screen.h"
extern bool axp192_found;
extern bool ssd1306_found;
extern bool isCharging;
extern bool isUSBPowered;
// Global Screen singleton.
extern meshtastic::Screen screen;
// Return a human readable string of the form "Meshtastic_ab13"
const char *getDeviceName();

View File

@@ -1,418 +0,0 @@
/*
Main module
# Modified by Kyle T. Gabriel to fix issue with incorrect GPS data for TTNMapper
Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "configuration.h"
#include "rom/rtc.h"
#include <driver/rtc_io.h>
#include <TinyGPS++.h>
#include <Wire.h>
#include "BluetoothUtil.h"
#include "MeshBluetoothService.h"
#include "MeshService.h"
#include "GPS.h"
#include "screen.h"
#include "NodeDB.h"
#include "Periodic.h"
#include "esp32/pm.h"
#include "esp_pm.h"
#include "MeshRadio.h"
#include "sleep.h"
#include "PowerFSM.h"
#ifdef T_BEAM_V10
#include "axp20x.h"
AXP20X_Class axp;
bool pmu_irq = false;
#endif
// these flags are all in bss so they default false
bool isCharging;
bool isUSBPowered;
bool ssd1306_found;
bool axp192_found;
bool bluetoothOn;
#define xstr(s) str(s)
#define str(s) #s
// -----------------------------------------------------------------------------
// Application
// -----------------------------------------------------------------------------
void scanI2Cdevice(void)
{
byte err, addr;
int nDevices = 0;
for (addr = 1; addr < 127; addr++)
{
Wire.beginTransmission(addr);
err = Wire.endTransmission();
if (err == 0)
{
DEBUG_MSG("I2C device found at address 0x%x\n", addr);
nDevices++;
if (addr == SSD1306_ADDRESS)
{
ssd1306_found = true;
DEBUG_MSG("ssd1306 display found\n");
}
#ifdef T_BEAM_V10
if (addr == AXP192_SLAVE_ADDRESS)
{
axp192_found = true;
DEBUG_MSG("axp192 PMU found\n");
}
#endif
}
else if (err == 4)
{
DEBUG_MSG("Unknow error at address 0x%x\n", addr);
}
}
if (nDevices == 0)
DEBUG_MSG("No I2C devices found\n");
else
DEBUG_MSG("done\n");
}
/**
* 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 T_BEAM_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_PULLUP);
attachInterrupt(PMU_IRQ, [] {
pmu_irq = true;
},
RISING);
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
axp.enableIRQ(AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ, 1);
axp.clearIRQ();
#endif
isCharging = axp.isChargeing();
isUSBPowered = axp.isVBUSPlug();
}
else
{
DEBUG_MSG("AXP192 Begin FAIL\n");
}
}
else
{
DEBUG_MSG("AXP192 not found\n");
}
#endif
}
const char *getDeviceName()
{
uint8_t dmac[6];
assert(esp_efuse_mac_get_default(dmac) == ESP_OK);
// Meshtastic_ab3c
static char name[20];
sprintf(name, "Meshtastic_%02x%02x", dmac[4], dmac[5]);
return name;
}
void setup()
{
// Debug
#ifdef DEBUG_PORT
DEBUG_PORT.begin(SERIAL_BAUD);
#endif
initDeepSleep();
#ifdef VEXT_ENABLE
pinMode(VEXT_ENABLE, OUTPUT);
digitalWrite(VEXT_ENABLE, 0); // turn on the display power
#endif
#ifdef RESET_OLED
pinMode(RESET_OLED, OUTPUT);
digitalWrite(RESET_OLED, 1);
#endif
#ifdef I2C_SDA
Wire.begin(I2C_SDA, I2C_SCL);
scanI2Cdevice();
#endif
axp192Init();
// Buttons & LED
#ifdef BUTTON_PIN
pinMode(BUTTON_PIN, INPUT_PULLUP);
digitalWrite(BUTTON_PIN, 1);
#endif
#ifdef LED_PIN
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, 1); // turn on for now
#endif
// Hello
DEBUG_MSG("%s %s\n", xstr(APP_NAME), str(APP_VERSION));
// 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
if (ssd1306_found)
screen.setup();
// Init GPS
gps.setup();
screen_print("Started...\n");
service.init();
// setBluetoothEnable(false); we now don't start bluetooth until we enter the proper state
setCPUFast(false); // 80MHz is fine for our slow peripherals
PowerFSM_setup();
powerFSM.trigger(EVENT_BOOT); // transition to ON, FIXME, only do this for cold boots, not waking from SDS
}
void initBluetooth()
{
DEBUG_MSG("Starting bluetooth\n");
// FIXME - we are leaking like crazy
// AllocatorScope scope(btPool);
BLEServer *serve = initBLE(getDeviceName(), HW_VENDOR, str(APP_VERSION)); // FIXME, use a real name based on the macaddr
createMeshBluetoothService(serve);
// Start advertising - this must be done _after_ creating all services
serve->getAdvertising()->start();
}
void setBluetoothEnable(bool on)
{
if (on != bluetoothOn)
{
DEBUG_MSG("Setting bluetooth enable=%d\n", on);
bluetoothOn = on;
if (on)
{
Serial.printf("Pre BT: %u heap size\n", ESP.getFreeHeap());
//ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );
initBluetooth();
}
else
{
// We have to totally teardown our bluetooth objects to prevent leaks
deinitBLE();
destroyMeshBluetoothService(); // must do after deinit, because it frees our service
Serial.printf("Shutdown BT: %u heap size\n", ESP.getFreeHeap());
//ESP_ERROR_CHECK( heap_trace_stop() );
//heap_trace_dump();
}
}
}
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 isCharging ? 1000 : (ledOn ? 2 : 1000);
}
Periodic ledPeriodic(ledBlinker);
#if 0
// Turn off for now
uint32_t axpReads()
{
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());
return 30 * 1000;
}
Periodic axpDebugOutput(axpReads);
#endif
void loop()
{
uint32_t msecstosleep = 1000 * 30; // How long can we sleep before we again need to service the main loop?
powerFSM.run_machine();
gps.loop();
screen.loop();
service.loop();
ledPeriodic.loop();
// axpDebugOutput.loop();
loopBLE();
#ifdef T_BEAM_V10
if (axp192_found)
{
#ifdef PMU_IRQ
if (pmu_irq)
{
pmu_irq = false;
axp.readIRQ();
DEBUG_MSG("pmu irq!\n");
isCharging = axp.isChargeing();
isUSBPowered = axp.isVBUSPlug();
axp.clearIRQ();
}
#endif
}
#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 this boilerplate)
static bool wasPressed = false;
static uint32_t minPressMs; // what tick should we call this press long enough
static uint32_t lastPingMs;
if (!digitalRead(BUTTON_PIN))
{
if (!wasPressed)
{ // just started a new press
DEBUG_MSG("pressing\n");
//doLightSleep();
// esp_pm_dump_locks(stdout); // FIXME, do this someplace better
wasPressed = true;
uint32_t now = millis();
minPressMs = now + 3000;
if (now - lastPingMs > 60 * 1000)
{ // if more than a minute since our last press, ask other nodes to update their state
service.sendNetworkPing();
lastPingMs = now;
}
powerFSM.trigger(EVENT_PRESS);
}
}
else if (wasPressed)
{
// we just did a release
wasPressed = false;
if (millis() > minPressMs)
{
// held long enough
screen_print("Erasing prefs");
delay(5000); // Give some time to read the screen
// ESP.restart();
}
}
#endif
// 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.
//DEBUG_MSG("msecs %d\n", msecstosleep);
// FIXME - until button press handling is done by interrupt (see polling above) we can't sleep very long at all or buttons feel slow
msecstosleep = 10;
delay(msecstosleep);
}

View File

@@ -1,10 +1,10 @@
#include <Arduino.h>
#include "configuration.h"
#include "mesh-pb-constants.h"
#include <pb_encode.h>
#include <pb_decode.h>
#include <assert.h>
#include "FS.h"
#include "configuration.h"
#include <Arduino.h>
#include <assert.h>
#include <pb_decode.h>
#include <pb_encode.h>
/// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic
/// returns the encoded packet size
@@ -12,42 +12,34 @@ size_t pb_encode_to_bytes(uint8_t *destbuf, size_t destbufsize, const pb_msgdesc
{
pb_ostream_t stream = pb_ostream_from_buffer(destbuf, destbufsize);
if (!pb_encode(&stream, fields, src_struct))
{
if (!pb_encode(&stream, fields, src_struct)) {
DEBUG_MSG("Error: can't encode protobuf %s\n", PB_GET_ERROR(&stream));
assert(0); // FIXME - panic
}
else
{
} else {
return stream.bytes_written;
}
}
/// helper function for decoding a record as a protobuf, we will return false if the decoding failed
bool pb_decode_from_bytes(const uint8_t *srcbuf, size_t srcbufsize, const pb_msgdesc_t *fields, void *dest_struct)
{
pb_istream_t stream = pb_istream_from_buffer(srcbuf, srcbufsize);
if (!pb_decode(&stream, fields, dest_struct))
{
DEBUG_MSG("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream));
if (!pb_decode(&stream, fields, dest_struct)) {
DEBUG_MSG("Error: can't decode protobuf %s, pb_msgdesc 0x%p\n", PB_GET_ERROR(&stream), fields);
return false;
}
else
{
} else {
return true;
}
}
/// Read from an Arduino File
bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count)
{
bool status = false;
#ifndef NO_ESP32
File *file = (File *)stream->state;
bool status;
if (buf == NULL)
{
if (buf == NULL) {
while (count-- && file->read() != EOF)
;
return count == 0;
@@ -57,15 +49,18 @@ bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count)
if (file->available() == 0)
stream->bytes_left = 0;
#endif
return status;
}
/// Write to an arduino file
bool writecb(pb_ostream_t *stream, const uint8_t *buf, size_t count)
{
File *file = (File*) stream->state;
//DEBUG_MSG("writing %d bytes to protobuf file\n", count);
return file->write(buf, count) == count;
#ifndef NO_ESP32
File *file = (File *)stream->state;
// DEBUG_MSG("writing %d bytes to protobuf file\n", count);
return file->write(buf, count) == count;
#else
return false;
#endif
}

View File

@@ -13,7 +13,6 @@
/// max number of nodes allowed in the mesh
#define MAX_NUM_NODES (member_size(DeviceState, node_db) / member_size(DeviceState, node_db[0]))
/// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic
/// returns the encoded packet size
size_t pb_encode_to_bytes(uint8_t *destbuf, size_t destbufsize, const pb_msgdesc_t *fields, const void *src_struct);

View File

@@ -15,6 +15,9 @@ PB_BIND(Data, Data, 2)
PB_BIND(User, User, AUTO)
PB_BIND(RouteDiscovery, RouteDiscovery, AUTO)
PB_BIND(SubPacket, SubPacket, 2)
@@ -49,7 +52,6 @@ PB_BIND(ToRadio, ToRadio, 2)
#ifndef PB_CONVERT_DOUBLE_FLOAT
/* On some platforms (such as AVR), double is really float.
* To be able to encode/decode double on these platforms, you need.

View File

@@ -31,16 +31,13 @@ typedef enum _ChannelSettings_ModemConfig {
ChannelSettings_ModemConfig_Bw125Cr48Sf4096 = 3
} ChannelSettings_ModemConfig;
typedef enum _DeviceState_Version {
DeviceState_Version_Unset = 0,
DeviceState_Version_Minimum = 16,
DeviceState_Version_Current = 16
} DeviceState_Version;
/* Struct definitions */
typedef struct _RouteDiscovery {
pb_callback_t route;
} RouteDiscovery;
typedef struct _ChannelSettings {
int32_t tx_power;
uint32_t channel_num;
ChannelSettings_ModemConfig modem_config;
pb_byte_t psk[16];
char name[12];
@@ -56,6 +53,12 @@ typedef struct _MyNodeInfo {
int32_t my_node_num;
bool has_gps;
int32_t num_channels;
char region[12];
char hw_model[16];
char firmware_version[12];
uint32_t error_code;
uint32_t error_address;
uint32_t error_count;
} MyNodeInfo;
typedef struct _Position {
@@ -63,7 +66,6 @@ typedef struct _Position {
double longitude;
int32_t altitude;
int32_t battery_level;
bool from_hardware;
uint32_t time;
} Position;
@@ -108,12 +110,12 @@ typedef struct _RadioConfig {
} RadioConfig;
typedef struct _SubPacket {
pb_size_t which_variant;
union {
Position position;
Data data;
User user;
} variant;
bool has_position;
Position position;
bool has_data;
Data data;
bool has_user;
User user;
bool want_response;
} SubPacket;
@@ -123,6 +125,8 @@ typedef struct _MeshPacket {
bool has_payload;
SubPacket payload;
uint32_t rx_time;
int32_t rx_snr;
uint32_t id;
} MeshPacket;
typedef struct _DeviceState {
@@ -136,9 +140,9 @@ typedef struct _DeviceState {
NodeInfo node_db[32];
pb_size_t receive_queue_count;
MeshPacket receive_queue[32];
DeviceState_Version version;
bool has_rx_text_message;
MeshPacket rx_text_message;
uint32_t version;
} DeviceState;
typedef struct _FromRadio {
@@ -170,42 +174,40 @@ typedef struct _ToRadio {
#define _ChannelSettings_ModemConfig_MAX ChannelSettings_ModemConfig_Bw125Cr48Sf4096
#define _ChannelSettings_ModemConfig_ARRAYSIZE ((ChannelSettings_ModemConfig)(ChannelSettings_ModemConfig_Bw125Cr48Sf4096+1))
#define _DeviceState_Version_MIN DeviceState_Version_Unset
#define _DeviceState_Version_MAX DeviceState_Version_Current
#define _DeviceState_Version_ARRAYSIZE ((DeviceState_Version)(DeviceState_Version_Current+1))
/* Initializer values for message structs */
#define Position_init_default {0, 0, 0, 0, 0, 0}
#define Position_init_default {0, 0, 0, 0, 0}
#define Data_init_default {_Data_Type_MIN, {0, {0}}}
#define User_init_default {"", "", "", {0}}
#define SubPacket_init_default {0, {Position_init_default}, 0}
#define MeshPacket_init_default {0, 0, false, SubPacket_init_default, 0}
#define ChannelSettings_init_default {0, 0, _ChannelSettings_ModemConfig_MIN, {0}, ""}
#define RouteDiscovery_init_default {{{NULL}, NULL}}
#define SubPacket_init_default {false, Position_init_default, false, Data_init_default, false, User_init_default, 0}
#define MeshPacket_init_default {0, 0, false, SubPacket_init_default, 0, 0, 0}
#define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0}, ""}
#define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default}
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0}
#define MyNodeInfo_init_default {0, 0, 0}
#define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default}, _DeviceState_Version_MIN, false, MeshPacket_init_default}
#define MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0}
#define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default}, false, MeshPacket_init_default, 0}
#define FromRadio_init_default {0, 0, {MeshPacket_init_default}}
#define ToRadio_init_default {0, {MeshPacket_init_default}}
#define Position_init_zero {0, 0, 0, 0, 0, 0}
#define Position_init_zero {0, 0, 0, 0, 0}
#define Data_init_zero {_Data_Type_MIN, {0, {0}}}
#define User_init_zero {"", "", "", {0}}
#define SubPacket_init_zero {0, {Position_init_zero}, 0}
#define MeshPacket_init_zero {0, 0, false, SubPacket_init_zero, 0}
#define ChannelSettings_init_zero {0, 0, _ChannelSettings_ModemConfig_MIN, {0}, ""}
#define RouteDiscovery_init_zero {{{NULL}, NULL}}
#define SubPacket_init_zero {false, Position_init_zero, false, Data_init_zero, false, User_init_zero, 0}
#define MeshPacket_init_zero {0, 0, false, SubPacket_init_zero, 0, 0, 0}
#define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0}, ""}
#define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero}
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0}
#define MyNodeInfo_init_zero {0, 0, 0}
#define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero}, _DeviceState_Version_MIN, false, MeshPacket_init_zero}
#define MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0}
#define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero}, false, MeshPacket_init_zero, 0}
#define FromRadio_init_zero {0, 0, {MeshPacket_init_zero}}
#define ToRadio_init_zero {0, {MeshPacket_init_zero}}
/* Field tags (for use in manual encoding/decoding) */
#define RouteDiscovery_route_tag 2
#define ChannelSettings_tx_power_tag 1
#define ChannelSettings_channel_num_tag 2
#define ChannelSettings_modem_config_tag 3
#define ChannelSettings_psk_tag 4
#define ChannelSettings_name_tag 5
@@ -214,11 +216,16 @@ typedef struct _ToRadio {
#define MyNodeInfo_my_node_num_tag 1
#define MyNodeInfo_has_gps_tag 2
#define MyNodeInfo_num_channels_tag 3
#define MyNodeInfo_region_tag 4
#define MyNodeInfo_hw_model_tag 5
#define MyNodeInfo_firmware_version_tag 6
#define MyNodeInfo_error_code_tag 7
#define MyNodeInfo_error_address_tag 8
#define MyNodeInfo_error_count_tag 9
#define Position_latitude_tag 1
#define Position_longitude_tag 2
#define Position_altitude_tag 3
#define Position_battery_level_tag 4
#define Position_from_hardware_tag 5
#define Position_time_tag 6
#define RadioConfig_UserPreferences_position_broadcast_secs_tag 1
#define RadioConfig_UserPreferences_send_owner_interval_tag 2
@@ -252,12 +259,14 @@ typedef struct _ToRadio {
#define MeshPacket_to_tag 2
#define MeshPacket_payload_tag 3
#define MeshPacket_rx_time_tag 4
#define MeshPacket_rx_snr_tag 5
#define MeshPacket_id_tag 6
#define DeviceState_radio_tag 1
#define DeviceState_my_node_tag 2
#define DeviceState_owner_tag 3
#define DeviceState_node_db_tag 4
#define DeviceState_receive_queue_tag 5
#define DeviceState_version_tag 6
#define DeviceState_version_tag 8
#define DeviceState_rx_text_message_tag 7
#define FromRadio_packet_tag 2
#define FromRadio_num_tag 1
@@ -269,7 +278,6 @@ X(a, STATIC, SINGULAR, DOUBLE, latitude, 1) \
X(a, STATIC, SINGULAR, DOUBLE, longitude, 2) \
X(a, STATIC, SINGULAR, INT32, altitude, 3) \
X(a, STATIC, SINGULAR, INT32, battery_level, 4) \
X(a, STATIC, SINGULAR, BOOL, from_hardware, 5) \
X(a, STATIC, SINGULAR, UINT32, time, 6)
#define Position_CALLBACK NULL
#define Position_DEFAULT NULL
@@ -288,29 +296,35 @@ X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, macaddr, 4)
#define User_CALLBACK NULL
#define User_DEFAULT NULL
#define RouteDiscovery_FIELDLIST(X, a) \
X(a, CALLBACK, REPEATED, INT32, route, 2)
#define RouteDiscovery_CALLBACK pb_default_field_callback
#define RouteDiscovery_DEFAULT NULL
#define SubPacket_FIELDLIST(X, a) \
X(a, STATIC, ONEOF, MESSAGE, (variant,position,variant.position), 1) \
X(a, STATIC, ONEOF, MESSAGE, (variant,data,variant.data), 3) \
X(a, STATIC, ONEOF, MESSAGE, (variant,user,variant.user), 4) \
X(a, STATIC, OPTIONAL, MESSAGE, position, 1) \
X(a, STATIC, OPTIONAL, MESSAGE, data, 3) \
X(a, STATIC, OPTIONAL, MESSAGE, user, 4) \
X(a, STATIC, SINGULAR, BOOL, want_response, 5)
#define SubPacket_CALLBACK NULL
#define SubPacket_DEFAULT NULL
#define SubPacket_variant_position_MSGTYPE Position
#define SubPacket_variant_data_MSGTYPE Data
#define SubPacket_variant_user_MSGTYPE User
#define SubPacket_position_MSGTYPE Position
#define SubPacket_data_MSGTYPE Data
#define SubPacket_user_MSGTYPE User
#define MeshPacket_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, INT32, from, 1) \
X(a, STATIC, SINGULAR, INT32, to, 2) \
X(a, STATIC, OPTIONAL, MESSAGE, payload, 3) \
X(a, STATIC, SINGULAR, UINT32, rx_time, 4)
X(a, STATIC, SINGULAR, UINT32, rx_time, 4) \
X(a, STATIC, SINGULAR, SINT32, rx_snr, 5) \
X(a, STATIC, SINGULAR, UINT32, id, 6)
#define MeshPacket_CALLBACK NULL
#define MeshPacket_DEFAULT NULL
#define MeshPacket_payload_MSGTYPE SubPacket
#define ChannelSettings_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, INT32, tx_power, 1) \
X(a, STATIC, SINGULAR, UINT32, channel_num, 2) \
X(a, STATIC, SINGULAR, UENUM, modem_config, 3) \
X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, psk, 4) \
X(a, STATIC, SINGULAR, STRING, name, 5)
@@ -356,7 +370,13 @@ X(a, STATIC, SINGULAR, INT32, frequency_error, 6)
#define MyNodeInfo_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, INT32, my_node_num, 1) \
X(a, STATIC, SINGULAR, BOOL, has_gps, 2) \
X(a, STATIC, SINGULAR, INT32, num_channels, 3)
X(a, STATIC, SINGULAR, INT32, num_channels, 3) \
X(a, STATIC, SINGULAR, STRING, region, 4) \
X(a, STATIC, SINGULAR, STRING, hw_model, 5) \
X(a, STATIC, SINGULAR, STRING, firmware_version, 6) \
X(a, STATIC, SINGULAR, UINT32, error_code, 7) \
X(a, STATIC, SINGULAR, UINT32, error_address, 8) \
X(a, STATIC, SINGULAR, UINT32, error_count, 9)
#define MyNodeInfo_CALLBACK NULL
#define MyNodeInfo_DEFAULT NULL
@@ -366,8 +386,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, my_node, 2) \
X(a, STATIC, OPTIONAL, MESSAGE, owner, 3) \
X(a, STATIC, REPEATED, MESSAGE, node_db, 4) \
X(a, STATIC, REPEATED, MESSAGE, receive_queue, 5) \
X(a, STATIC, SINGULAR, UENUM, version, 6) \
X(a, STATIC, OPTIONAL, MESSAGE, rx_text_message, 7)
X(a, STATIC, OPTIONAL, MESSAGE, rx_text_message, 7) \
X(a, STATIC, SINGULAR, UINT32, version, 8)
#define DeviceState_CALLBACK NULL
#define DeviceState_DEFAULT NULL
#define DeviceState_radio_MSGTYPE RadioConfig
@@ -393,6 +413,7 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,packet,variant.packet), 1)
extern const pb_msgdesc_t Position_msg;
extern const pb_msgdesc_t Data_msg;
extern const pb_msgdesc_t User_msg;
extern const pb_msgdesc_t RouteDiscovery_msg;
extern const pb_msgdesc_t SubPacket_msg;
extern const pb_msgdesc_t MeshPacket_msg;
extern const pb_msgdesc_t ChannelSettings_msg;
@@ -408,6 +429,7 @@ extern const pb_msgdesc_t ToRadio_msg;
#define Position_fields &Position_msg
#define Data_fields &Data_msg
#define User_fields &User_msg
#define RouteDiscovery_fields &RouteDiscovery_msg
#define SubPacket_fields &SubPacket_msg
#define MeshPacket_fields &MeshPacket_msg
#define ChannelSettings_fields &ChannelSettings_msg
@@ -420,19 +442,20 @@ extern const pb_msgdesc_t ToRadio_msg;
#define ToRadio_fields &ToRadio_msg
/* Maximum encoded size of messages (where known) */
#define Position_size 48
#define Position_size 46
#define Data_size 256
#define User_size 72
#define SubPacket_size 261
#define MeshPacket_size 292
#define ChannelSettings_size 50
#define RadioConfig_size 126
/* RouteDiscovery_size depends on runtime parameters */
#define SubPacket_size 383
#define MeshPacket_size 426
#define ChannelSettings_size 44
#define RadioConfig_size 120
#define RadioConfig_UserPreferences_size 72
#define NodeInfo_size 157
#define MyNodeInfo_size 24
#define DeviceState_size 15085
#define FromRadio_size 301
#define ToRadio_size 295
#define NodeInfo_size 155
#define MyNodeInfo_size 85
#define DeviceState_size 19502
#define FromRadio_size 435
#define ToRadio_size 429
#ifdef __cplusplus
} /* extern "C" */

18
src/power.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
namespace meshtastic
{
/// Describes the state of the power system.
struct PowerStatus {
/// Whether we have a battery connected
bool haveBattery;
/// Battery voltage in mV, valid if haveBattery is true
int batteryVoltageMv;
/// Whether USB is connected
bool usb;
/// Whether we are charging the battery
bool charging;
};
} // namespace meshtastic

View File

@@ -1,27 +1,28 @@
#include "CustomRF95.h"
#include <pb_encode.h>
#include <pb_decode.h>
#include "configuration.h"
#include "NodeDB.h" // FIXME, this class should not need to touch nodedb
#include "assert.h"
#include "NodeDB.h"
#include "configuration.h"
#include <pb_decode.h>
#include <pb_encode.h>
#ifdef RF95_IRQ_GPIO
/// A temporary buffer used for sending/receving packets, sized to hold the biggest buffer we might need
#define MAX_RHPACKETLEN 251
static uint8_t radiobuf[MAX_RHPACKETLEN];
CustomRF95::CustomRF95(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &_rxDest)
: RH_RF95(NSS_GPIO, DIO0_GPIO),
pool(_pool),
rxDest(_rxDest),
txQueue(MAX_TX_QUEUE),
sendingPacket(NULL)
{
}
CustomRF95::CustomRF95() : RH_RF95(NSS_GPIO, RF95_IRQ_GPIO), txQueue(MAX_TX_QUEUE) {}
bool CustomRF95::canSleep()
{
// We allow initializing mode, because sometimes while testing we don't ever call init() to turn on the hardware
return (_mode == RHModeInitialising || _mode == RHModeIdle || _mode == RHModeRx) && !_isReceiving && txQueue.isEmpty();
bool isRx = isReceiving();
bool res = (_mode == RHModeInitialising || _mode == RHModeIdle || _mode == RHModeRx) && !isRx && txQueue.isEmpty();
if (!res) // only print debug messages if we are vetoing sleep
DEBUG_MSG("radio wait to sleep, mode=%d, isRx=%d, txEmpty=%d, txGood=%d\n", _mode, isRx, txQueue.isEmpty(), _txGood);
return res;
}
bool CustomRF95::sleep()
@@ -48,44 +49,47 @@ ErrorCode CustomRF95::send(MeshPacket *p)
// 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.
if (_mode == RHModeIdle || (_mode == RHModeRx && !_isReceiving))
{
if (_mode == RHModeIdle || (_mode == RHModeRx && !isReceiving())) {
// if the radio is idle, we can send right away
DEBUG_MSG("immedate send on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", txGood(), rxGood(), rxBad());
DEBUG_MSG("immediate send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id,
txGood(), rxGood(), rxBad());
waitPacketSent(); // Make sure we dont interrupt an outgoing message
if (!waitCAD())
return false; // Check channel activity
startSend(p);
return ERRNO_OK;
}
else
{
} else {
DEBUG_MSG("enquing packet for send from=0x%x, to=0x%x\n", p->from, p->to);
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
pool.release(p);
packetPool.release(p);
return res;
}
}
// After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as necessary
// After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as
// necessary
void CustomRF95::handleInterrupt()
{
RH_RF95::handleInterrupt();
BaseType_t higherPriWoken = false;
if (_mode == RHModeIdle) // We are now done sending or receiving
{
if (sendingPacket) // Were we sending?
{
// We are done sending that packet, release it
pool.releaseFromISR(sendingPacket, &higherPriWoken);
packetPool.release(sendingPacket);
sendingPacket = NULL;
// DEBUG_MSG("Done with send\n");
}
// If we just finished receiving a packet, forward it into a queue
if (_rxBufValid)
{
if (_rxBufValid) {
// We received a packet
// Skip the 4 headers that are at the beginning of the rxBuf
@@ -94,15 +98,17 @@ void CustomRF95::handleInterrupt()
// FIXME - throws exception if called in ISR context: frequencyError() - probably the floating point math
int32_t freqerr = -1, snr = lastSNR();
//DEBUG_MSG("Received packet from mesh src=0x%x,dest=0x%x,id=%d,len=%d rxGood=%d,rxBad=%d,freqErr=%d,snr=%d\n",
// DEBUG_MSG("Received packet from mesh src=0x%x,dest=0x%x,id=%d,len=%d rxGood=%d,rxBad=%d,freqErr=%d,snr=%d\n",
// srcaddr, destaddr, id, rxlen, rf95.rxGood(), rf95.rxBad(), freqerr, snr);
MeshPacket *mp = pool.allocZeroed();
MeshPacket *mp = packetPool.allocZeroed();
SubPacket *p = &mp->payload;
mp->from = _rxHeaderFrom;
mp->to = _rxHeaderTo;
mp->id = _rxHeaderId;
//_rxHeaderId = _buf[2];
//_rxHeaderFlags = _buf[3];
@@ -110,55 +116,41 @@ void CustomRF95::handleInterrupt()
// Note: we can't create it at this point, because it might be a bogus User node allocation. But odds are we will
// already have a record we can hide this debugging info in.
NodeInfo *info = nodeDB.getNode(mp->from);
if (info)
{
if (info) {
info->snr = snr;
info->frequency_error = freqerr;
}
if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p))
{
pool.releaseFromISR(mp, &higherPriWoken);
}
else
{
if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) {
packetPool.release(mp);
} else {
// parsing was successful, queue for our recipient
mp->has_payload = true;
int res = rxDest.enqueueFromISR(mp, &higherPriWoken); // NOWAIT - fixme, if queue is full, delete older messages
assert(res == pdTRUE);
deliverToReceiver(mp);
}
clearRxBuf(); // This message accepted and cleared
}
higherPriWoken |= handleIdleISR();
handleIdleISR();
}
// If we call this _IT WILL NOT RETURN_
if (higherPriWoken)
portYIELD_FROM_ISR();
}
/** The ISR doesn't have any good work to do, give a new assignment.
*
* Return true if a higher pri task has woken
*/
bool CustomRF95::handleIdleISR()
void CustomRF95::handleIdleISR()
{
BaseType_t higherPriWoken = false;
// First send any outgoing packets we have ready
MeshPacket *txp = txQueue.dequeuePtrFromISR(0);
MeshPacket *txp = txQueue.dequeuePtr(0);
if (txp)
startSend(txp);
else
{
else {
// Nothing to send, let's switch back to receive mode
setModeRx();
}
return higherPriWoken;
}
/// This routine might be called either from user space or ISR
@@ -169,14 +161,18 @@ void CustomRF95::startSend(MeshPacket *txp)
// DEBUG_MSG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", rf95.txGood(), rf95.rxGood(), rf95.rxBad());
assert(txp->has_payload);
lastTxStart = millis();
size_t numbytes = pb_encode_to_bytes(radiobuf, sizeof(radiobuf), SubPacket_fields, &txp->payload);
sendingPacket = txp;
setHeaderTo(txp->to);
setHeaderFrom(nodeDB.getNodeNum()); // We must do this before each send, because we might have just changed our nodenum
setHeaderId(txp->id);
// setHeaderId(0);
// if the sender nodenum is zero, that means uninitialized
assert(txp->from);
setHeaderFrom(txp->from); // We must do this before each send, because we might have just changed our nodenum
assert(numbytes <= 251); // Make sure we don't overflow the tiny max packet size
@@ -185,3 +181,28 @@ void CustomRF95::startSend(MeshPacket *txp)
int res = RH_RF95::send(radiobuf, numbytes);
assert(res);
}
#define TX_WATCHDOG_TIMEOUT 30 * 1000
#include "error.h"
void CustomRF95::loop()
{
RH_RF95::loop();
// It should never take us more than 30 secs to send a packet, if it does, we have a bug, FIXME, move most of this
// into CustomRF95
uint32_t now = millis();
if (lastTxStart != 0 && (now - lastTxStart) > TX_WATCHDOG_TIMEOUT && mode() == RHGenericDriver::RHModeTx) {
DEBUG_MSG("ERROR! Bug! Tx packet took too long to send, forcing radio into rx mode\n");
setModeRx();
if (sendingPacket) { // There was probably a packet we were trying to send, free it
packetPool.release(sendingPacket);
sendingPacket = NULL;
}
recordCriticalError(ErrTxWatchdog);
lastTxStart = 0; // Stop checking for now, because we just warned the developer
}
}
#endif

View File

@@ -1,32 +1,27 @@
#pragma once
#include <RH_RF95.h>
#include <RHMesh.h>
#include "MemoryPool.h"
#include "RadioInterface.h"
#include "mesh.pb.h"
#include "PointerQueue.h"
#include "MeshTypes.h"
#include <RH_RF95.h>
#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission
/**
* A version of the RF95 driver which is smart enough to manage packets via queues (no polling or blocking in user threads!)
*/
class CustomRF95 : public RH_RF95
class CustomRF95 : public RH_RF95, public RadioInterface
{
friend class MeshRadio; // for debugging we let that class touch pool
MemoryPool<MeshPacket> &pool;
PointerQueue<MeshPacket> &rxDest;
PointerQueue<MeshPacket> txQueue;
MeshPacket *sendingPacket; // The packet we are currently sending
public:
uint32_t lastTxStart = 0L;
public:
/** 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
*/
CustomRF95(MemoryPool<MeshPacket> &pool, PointerQueue<MeshPacket> &rxDest);
CustomRF95();
/**
* Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving)
@@ -45,14 +40,17 @@ public:
bool init();
protected:
// After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as necessary
void loop(); // Idle processing
protected:
// After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as
// necessary
virtual void handleInterrupt();
private:
private:
/// Send a new packet - this low level call can be called from either ISR or userspace
void startSend(MeshPacket *txp);
/// Return true if a higher pri task has woken
bool handleIdleISR();
void handleIdleISR();
};

View File

@@ -0,0 +1,99 @@
#include "FloodingRouter.h"
#include "configuration.h"
#include "mesh-pb-constants.h"
/// We clear our old flood record five minute after we see the last of it
#define FLOOD_EXPIRE_TIME (5 * 60 * 1000L)
FloodingRouter::FloodingRouter()
{
recentBroadcasts.reserve(MAX_NUM_NODES); // Prealloc the worst case # of records - to prevent heap fragmentation
}
/**
* 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)
{
// We update our table of recent broadcasts, even for messages we send
wasSeenRecently(p);
return Router::send(p);
}
/**
* Called from loop()
* Handle any packet that is received by an interface on this node.
* Note: some packets may merely being passed through this node and will be forwarded elsewhere.
*
* Note: this method will free the provided packet
*/
void FloodingRouter::handleReceived(MeshPacket *p)
{
if (wasSeenRecently(p)) {
DEBUG_MSG("Ignoring incoming floodmsg, because we've already seen it\n");
packetPool.release(p);
} else {
if (p->to == NODENUM_BROADCAST) {
if (p->id != 0) {
DEBUG_MSG("Rebroadcasting received floodmsg to neighbors fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
// FIXME, wait a random delay
MeshPacket *tosend = packetPool.allocCopy(*p);
// Note: we are careful to resend using the original senders node id
Router::send(tosend); // We are careful not to call our hooked version of send()
} else {
DEBUG_MSG("Ignoring a simple (0 hop) broadcast\n");
}
}
// handle the packet as normal
Router::handleReceived(p);
}
}
/**
* Update recentBroadcasts and return true if we have already seen this packet
*/
bool FloodingRouter::wasSeenRecently(const MeshPacket *p)
{
if (p->to != NODENUM_BROADCAST)
return false; // Not a broadcast, so we don't care
if (p->id == 0) {
DEBUG_MSG("Ignoring message with zero id\n");
return false; // Not a floodable message ID, so we don't care
}
uint32_t now = millis();
for (int i = 0; i < recentBroadcasts.size();) {
BroadcastRecord &r = recentBroadcasts[i];
if ((now - r.rxTimeMsec) >= FLOOD_EXPIRE_TIME) {
DEBUG_MSG("Deleting old broadcast record %d\n", i);
recentBroadcasts.erase(recentBroadcasts.begin() + i); // delete old record
} else {
if (r.id == p->id && r.sender == p->from) {
DEBUG_MSG("Found existing broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
// Update the time on this record to now
r.rxTimeMsec = now;
return true;
}
i++;
}
}
// Didn't find an existing record, make one
BroadcastRecord r;
r.id = p->id;
r.sender = p->from;
r.rxTimeMsec = now;
recentBroadcasts.push_back(r);
DEBUG_MSG("Adding broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
return false;
}

72
src/rf95/FloodingRouter.h Normal file
View File

@@ -0,0 +1,72 @@
#pragma once
#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)
*
* Rules for broadcasting (listing here for now, will move elsewhere eventually):
If to==BROADCAST and id==0, this is a simple broadcast (0 hops). It will be
sent only by the current node and other nodes will not attempt to rebroadcast
it.
If to==BROADCAST and id!=0, this is a "naive flooding" broadcast. The initial
node will send it on all local interfaces.
When other nodes receive this message, they will
first check if their recentBroadcasts table contains the (from, id) pair that
indicates this message. If so, we've already seen it - so we discard it. If
not, we add it to the table and then resend this message on all interfaces.
When resending we are careful to use the "from" ID of the original sender. Not
our own ID. When resending we pick a random delay between 0 and 10 seconds to
decrease the chance of collisions with transmitters we can not even hear.
Any entries in recentBroadcasts that are older than X seconds (longer than the
max time a flood can take) will be discarded.
*/
class FloodingRouter : public Router
{
private:
std::vector<BroadcastRecord> recentBroadcasts;
public:
/**
* Constructor
*
*/
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
*/
virtual ErrorCode send(MeshPacket *p);
protected:
/**
* Called from loop()
* Handle any packet that is received by an interface on this node.
* Note: some packets may merely being passed through this node and will be forwarded elsewhere.
*
* Note: this method will free the provided packet
*/
virtual void handleReceived(MeshPacket *p);
private:
/**
* Update recentBroadcasts and return true if we have already seen this packet
*/
bool wasSeenRecently(const MeshPacket *p);
};

17
src/rf95/LICENSE Normal file
View File

@@ -0,0 +1,17 @@
This software is Copyright (C) 2008 Mike McCauley. Use is subject to license
conditions. The main licensing options available are GPL V2 or Commercial:
Open Source Licensing GPL V2
This is the appropriate option if you want to share the source code of your
application with everyone you distribute it to, and you also want to give them
the right to share who uses it. If you wish to use this software under Open
Source Licensing, you must contribute all your source code to the open source
community in accordance with the GPL Version 2 when your application is
distributed. See http://www.gnu.org/copyleft/gpl.html
Commercial Licensing
This is the appropriate option if you are creating proprietary applications
and you are not prepared to distribute and share the source code of your
application. Contact info@open.com.au for details.

View File

@@ -10,8 +10,7 @@
*
* Eventually this routine will even be safe for ISR use...
*/
template <class T>
class MemoryPool
template <class T> class MemoryPool
{
PointerQueue<T> dead;
@@ -19,22 +18,20 @@ class MemoryPool
size_t maxElements;
public:
public:
MemoryPool(size_t _maxElements) : dead(_maxElements), maxElements(_maxElements)
{
buf = new T[maxElements];
// prefill dead
for (int i = 0; i < maxElements; i++)
for (size_t i = 0; i < maxElements; i++)
release(&buf[i]);
}
~MemoryPool()
{
delete[] buf;
}
~MemoryPool() { delete[] buf; }
/// Return a queable object which has been prefilled with zeros. Panic if no buffer is available
/// Note: this method is safe to call from regular OR ISR code
T *allocZeroed()
{
T *p = allocZeroed(0);
@@ -43,7 +40,8 @@ public:
return p;
}
/// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you probably don't want this version)
/// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you probably
/// don't want this version).
T *allocZeroed(TickType_t maxWait)
{
T *p = dead.dequeuePtr(maxWait);
@@ -66,16 +64,18 @@ public:
/// Return a buffer for use by others
void release(T *p)
{
int res = dead.enqueue(p, 0);
assert(res == pdTRUE);
assert(p >= buf && (p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
assert(dead.enqueue(p, 0));
assert(p >= buf &&
(size_t)(p - buf) <
maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
}
/// Return a buffer from an ISR, if higherPriWoken is set to true you have some work to do ;-)
void releaseFromISR(T *p, BaseType_t *higherPriWoken)
{
int res = dead.enqueueFromISR(p, higherPriWoken);
assert(res == pdTRUE);
assert(p >= buf && (p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
assert(dead.enqueueFromISR(p, higherPriWoken));
assert(p >= buf &&
(size_t)(p - buf) <
maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
}
};

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