Compare commits

..

177 Commits
0.0.9 ... 0.2.3

Author SHA1 Message Date
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
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
78 changed files with 3585 additions and 2280 deletions

6
.clang-format Normal file
View File

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

View File

@@ -1,5 +1,7 @@
name: Continuous Integration
on: push
on:
- push
- pull_request
jobs:
main:

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

View File

@@ -46,6 +46,7 @@
},
"cSpell.words": [
"Meshtastic",
"descs"
"descs",
"protobufs"
]
}

128
README.md
View File

@@ -1,39 +1,43 @@
# Meshtastic-esp32
This is the device side code for the [meshtastic.org](https://www.meshtastic.org) project.
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 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 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
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 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.
See (meshtastic.org) for 3D printable cases.
## Installing the firmware
Prebuilt binaries for the supported radios is 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.
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 is 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.
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/).
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:
1. Purchase a radio (see above) with the correct frequencies for your country (915MHz for US or JP, 470MHz for CN, 433MHz and 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/). If you are using OS-X, see these [special instructions](docs/software/install-OSX.md).
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". The Heltec build also works on the TTGO LORA32 radio. You should see something like:
5. 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:
```
mydir$ esptool.py chip_id
esptool.py v2.6
@@ -51,53 +55,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:
6. cd into the directory where the release zip file was expanded.
7. 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. The board will boot and show the Meshtastic logo.
9. 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/meshtastic/Meshtastic-Android).
Alpha test builds are current available by opting into our alpha test group. See (www.meshtastic.org) for instructions.
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.

193
TODO.md
View File

@@ -1,193 +0,0 @@
# High priority
Items to complete soon (next couple of alpha releases).
* text messages are not showing on local screen if screen was on
* The following three items are all the same:
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).
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.
* 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
* remeasure wake time power draws now that we run CPU down at 80MHz
# AXP192 tasks
* "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.
* good source of battery/signal/gps icons https://materialdesignicons.com/
* 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
* research and implement better mesh algorithm
* 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
* 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 ;-) )
* 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

View File

@@ -4,11 +4,9 @@ set -e
source bin/version.sh
COUNTRIES="US EU CN JP"
# COUNTRIES=US
COUNTRIES="US EU433 EU865 CN JP"
#COUNTRIES=US
SRCMAP=.pio/build/esp32/output.map
SRCBIN=.pio/build/esp32/firmware.bin
OUTDIR=release/latest
# We keep all old builds (and their map files in the archive dir)
@@ -16,27 +14,37 @@ 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
pio run --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
HWVERSTR="1.0-$COUNTRY"
COMMONOPTS="-DAPP_VERSION=$VERSION -DHW_VERSION_$COUNTRY -DHW_VERSION=$HWVERSTR -Wall -Wextra -Wno-missing-field-initializers -Isrc -Os -Wl,-Map,.pio/build/esp32/output.map -DAXP_DEBUG_PORT=Serial"
COMMONOPTS="-DAPP_VERSION=$VERSION -DHW_VERSION_$COUNTRY -DHW_VERSION=$HWVERSTR -Wall -Wextra -Wno-missing-field-initializers -Isrc -Os -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 $OUTDIR/firmware-TBEAM-$COUNTRY-$VERSION.bin
#cp $SRCMAP $ARCHIVEDIR/firmware-TBEAM-$COUNTRY-$VERSION.map
export PLATFORMIO_BUILD_FLAGS="$COMMONOPTS"
export PLATFORMIO_BUILD_FLAGS="-DHELTEC_LORA32 $COMMONOPTS"
rm -f $SRCBIN $SRCMAP
pio run # -v
cp $SRCBIN $OUTDIR/firmware-HELTEC-$COUNTRY-$VERSION.bin
#cp $SRCMAP $ARCHIVEDIR/firmware-HELTEC-$COUNTRY-$VERSION.map
do_build "tbeam0.7"
do_build "ttgo-lora32-v2"
do_build "ttgo-lora32-v1"
do_build "tbeam"
do_build "heltec"
done
# keep the bins in archive also
cp $OUTDIR/firmware* $ARCHIVEDIR
cp $OUTDIR/bins/firmware* $OUTDIR/elfs/firmware* $ARCHIVEDIR
cat >$OUTDIR/curfirmwareversion.xml <<XML
<?xml version="1.0" encoding="utf-8"?>
@@ -51,6 +59,6 @@ Generated by bin/buildall.sh -->
XML
rm -f $ARCHIVEDIR/firmware-$VERSION.zip
zip $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/firmware-*-$VERSION.bin
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)

View File

@@ -3,4 +3,4 @@ set -e
source bin/version.sh
esptool.py --baud 921600 write_flash 0x10000 release/firmware-HELTEC-US-$VERSION.bin
esptool.py --baud 921600 write_flash 0x10000 release/latest/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/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

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

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

View File

@@ -1,3 +1,3 @@
export VERSION=0.0.9
export VERSION=0.2.3

View File

@@ -1,79 +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 friends and they can join your encrypted mesh
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).
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 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).
- 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 project is currently in early alpha - if you have questions please [join our discussion forum](https://meshtastic.discourse.group/).
This software is 100% open source and developed by a group of hobbyist experimenters. No warranty is provided, if you'd like to improve it - we'd love your help. Please post in the [forum](https://meshtastic.discourse.group/).
# Updates
* 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.
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
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)
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:
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/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 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.
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.
Instructions for installing prebuilt firmware can be found [here](https://github.com/meshtastic/Meshtastic-esp32/blob/master/README.md).
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).
For a nice printable cases:
# Disclaimers
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).
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:
# IMPORTANT DISCLAIMERS AND FAQ
* 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 ;-)
For more details see the [device software TODO](https://github.com/meshtastic/Meshtastic-esp32/blob/master/TODO.md) or the [Android app TODO](https://github.com/meshtastic/Meshtastic-Android/blob/master/TODO.md).
For a listing of currently missing features and a FAQ click [here](faq.md).

36
docs/faq.md Normal file
View File

@@ -0,0 +1,36 @@
# 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 current Android GUI is slightly ugly still
* The Android API needs to be documented better
* The mesh protocol is turned off for now, currently we only send packets one hop distant. The mesh feature will be turned on again [soonish](https://github.com/meshtastic/Meshtastic-esp32/issues/3).
* 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.

20
docs/mesh-proto.md Normal file
View File

@@ -0,0 +1,20 @@
TODO:
* reread the radiohead mesh implementation
* read about general mesh flooding solutions
* reread the disaster radio protocol docs
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

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

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

@@ -0,0 +1,204 @@
# 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.
- 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.

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

@@ -0,0 +1,72 @@
# 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)
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.

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

@@ -117,21 +117,7 @@ void bluetoothRebootCheck()
}
/*
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
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
See bluetooth-api.md
*/
BLEService *createUpdateService(BLEServer *server, std::string hwVendor, std::string swVersion, std::string hwVersion)

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

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

@@ -9,11 +9,14 @@
; https://docs.platformio.org/page/projectconf.html
[platformio]
default_envs = tbeam
[env:esp32]
[common]
; default to a US frequency range, change it as needed for your region and hardware (CN, JP, EU433, EU865)
hw_version = US
[env]
platform = espressif32
board = ttgo-t-beam
; board = heltec_wifi_lora_32_V2
framework = arduino
; customize the partition table
@@ -22,23 +25,24 @@ board_build.partitions = partition-table.csv
; note: we add src to our include search path so that lmic_project_config can override
; FIXME: fix lib/BluetoothOTA dependency back on src/ so we can remove -Isrc
build_flags = -Wall -Wextra -Wno-missing-field-initializers -Isrc -Os -Wl,-Map,.pio/build/esp32/output.map -DAXP_DEBUG_PORT=Serial
build_flags = -Wall -Wextra -Wno-missing-field-initializers -Isrc -Os -Wl,-Map,.pio/build/output.map -DAXP_DEBUG_PORT=Serial -DHW_VERSION_${common.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
; leave this commented out to avoid breaking Windows
;upload_port = /dev/ttyUSB0
;monitor_port = /dev/ttyUSB0
upload_speed = 921600
upload_port = /dev/ttyUSB0
; the default is esptool
; upload_protocol = esp-prog
monitor_port = /dev/ttyUSB0
monitor_speed = 115200
# debug_tool = esp-prog
@@ -55,22 +59,50 @@ debug_tool = jlink
debug_init_break = tbreak setup
; Note: some libraries are specified by #ID where there are conflicting library
; names.
lib_deps =
https://github.com/meshtastic/RadioHead.git
1655 ; TinyGPSPlus
https://github.com/meshtastic/RadioHead.git#a87518beacfff53bc2eec5ae33c607713e9596e6
https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
AXP202X_Library
SPI
1260 ; OneButton
; 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/meshtastic/arduino-fsm.git
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git
;[env:tbeam]
;board = ttgo-t-beam
; The 1.0 release of the TBEAM board
[env:tbeam]
board = ttgo-t-beam
lib_deps =
${env.lib_deps}
AXP202X_Library
build_flags =
${env.build_flags} -D TBEAM_V10
;[env:heltec]
; The original TBEAM board without the AXP power chip and a few other changes
[env:tbeam0.7]
board = ttgo-t-beam
build_flags =
${env.build_flags} -D TBEAM_V07
[env:heltec]
;build_type = debug ; to make it possible to step through our jtag debugger
;board = heltec_wifi_lora_32_V2
board = heltec_wifi_lora_32_V2
[env:ttgo-lora32-v1]
board = ttgo-lora32-v1
build_flags =
${env.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]
board = ttgo-lora32-v1
build_flags =
${env.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]
board = ttgo-lora32-v1
build_flags =
${env.build_flags} -D BARE_BOARD

2
proto

Submodule proto updated: e1a48b6e75...d13d741a98

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,9 +0,0 @@
<?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">0.0.5</string>
</resources>

View File

@@ -1,27 +1,26 @@
#include "CustomRF95.h"
#include <pb_encode.h>
#include <pb_decode.h>
#include "configuration.h"
#include "assert.h"
#include "NodeDB.h"
#include "assert.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)
: RH_RF95(NSS_GPIO, RF95_IRQ_GPIO), RadioInterface(_pool, _rxDest), 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();
DEBUG_MSG("canSleep, mode=%d, isRx=%d, txEmpty=%d, txGood=%d\n", _mode, _isReceiving, txQueue.isEmpty(), _txGood);
return (_mode == RHModeInitialising || _mode == RHModeIdle || _mode == RHModeRx) && !_isReceiving && txQueue.isEmpty();
}
bool CustomRF95::sleep()
@@ -46,17 +45,14 @@ bool CustomRF95::init()
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,
// 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());
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;
@@ -67,7 +63,8 @@ ErrorCode CustomRF95::send(MeshPacket *p)
}
}
// 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();
@@ -84,8 +81,7 @@ void CustomRF95::handleInterrupt()
}
// 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,7 +90,7 @@ 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();
@@ -110,23 +106,18 @@ 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))
{
if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) {
pool.releaseFromISR(mp, &higherPriWoken);
}
else
{
} 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);
assert(rxDest.enqueueFromISR(mp, &higherPriWoken)); // NOWAIT - fixme, if queue is full, delete older messages
}
clearRxBuf(); // This message accepted and cleared
@@ -141,7 +132,7 @@ void CustomRF95::handleInterrupt()
}
/** 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()
@@ -152,8 +143,7 @@ bool CustomRF95::handleIdleISR()
MeshPacket *txp = txQueue.dequeuePtrFromISR(0);
if (txp)
startSend(txp);
else
{
else {
// Nothing to send, let's switch back to receive mode
setModeRx();
}
@@ -184,4 +174,6 @@ void CustomRF95::startSend(MeshPacket *txp)
int res = RH_RF95::send(radiobuf, numbytes);
assert(res);
}
}
#endif

View File

@@ -1,28 +1,22 @@
#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 <RHMesh.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:
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
*/
@@ -30,7 +24,7 @@ public:
/**
* Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving)
*
*
* This method must be used before putting the CPU into deep or light sleep.
*/
bool canSleep();
@@ -45,11 +39,12 @@ 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
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);

View File

@@ -1,30 +1,80 @@
#include "GPS.h"
#include "configuration.h"
#include "time.h"
#include <sys/time.h>
#include "configuration.h"
// stuff that really should be in in the instance instead...
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
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 +82,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,8 +94,7 @@ 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);
settimeofday(tv, NULL);
@@ -75,70 +123,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).
*/
/* Convert to unix time
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
*/
struct tm t;
t.tm_sec = 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();
// 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 +210,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,18 +1,24 @@
#pragma once
#include <TinyGPS++.h>
#include "PeriodicTask.h"
#include "Observer.h"
#include "PeriodicTask.h"
#include "SparkFun_Ublox_Arduino_Library.h"
#include "sys/time.h"
/**
* A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading)
*
*
* When new data is available it will notify observers.
*/
class GPS : public PeriodicTask, public Observable, public TinyGPSPlus
class GPS : public PeriodicTask, public Observable
{
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

@@ -7,11 +7,10 @@
/**
* A pool based allocator
*
*
* Eventually this routine will even be safe for ISR use...
*/
template <class T>
class MemoryPool
template <class T> class MemoryPool
{
PointerQueue<T> dead;
@@ -19,7 +18,7 @@ class MemoryPool
size_t maxElements;
public:
public:
MemoryPool(size_t _maxElements) : dead(_maxElements), maxElements(_maxElements)
{
buf = new T[maxElements];
@@ -29,10 +28,7 @@ public:
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
T *allocZeroed()
@@ -43,7 +39,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 +63,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 &&
(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 &&
(p - buf) <
maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
}
};

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);
}
@@ -44,7 +45,7 @@ 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
@@ -59,9 +60,10 @@ protected:
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");
}
@@ -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,13 +184,10 @@ 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
{
} else {
static FromRadio fRadio;
// Encapsulate as a FromRadio packet
@@ -206,10 +206,11 @@ public:
class FromNumCharacteristic : public CallbackCharacteristic
{
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)
{
}
@@ -220,6 +221,27 @@ public:
}
};
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;
/**
@@ -227,8 +249,7 @@ FromNumCharacteristic *meshFromNumCharacteristic;
*/
void bluetoothNotifyFromNum(uint32_t newValue)
{
if (meshFromNumCharacteristic)
{
if (meshFromNumCharacteristic) {
// if bt not running ignore
meshFromNumCharacteristic->setValue(newValue);
meshFromNumCharacteristic->notify();
@@ -238,65 +259,12 @@ void bluetoothNotifyFromNum(uint32_t newValue)
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;
@@ -304,8 +272,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");
@@ -316,8 +283,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());
}
@@ -329,10 +295,15 @@ BLEService *createMeshBluetoothService(BLEServer *server)
return service;
}
void destroyMeshBluetoothService()
void stopMeshBluetoothService()
{
assert(meshService);
meshService->stop();
}
void destroyMeshBluetoothService()
{
assert(meshService);
delete meshService;
meshFromNumCharacteristic = NULL;

View File

@@ -1,10 +1,10 @@
#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();
/**
@@ -12,8 +12,4 @@ void destroyMeshBluetoothService();
*/
void bluetoothNotifyFromNum(uint32_t newValue);
/**
* Super skanky FIXME - when we start a software update we force the mesh service to shutdown.
* If the sw update fails, the user will have to manually reset the board to get things running again.
*/
void stopMeshBluetoothService();

View File

@@ -1,18 +1,14 @@
#include <SPI.h>
#include "RH_RF95.h"
#include "error.h"
#include <RHMesh.h>
#include <SPI.h>
#include <assert.h>
#include <pb_encode.h>
#include <pb_decode.h>
#include "MeshRadio.h"
#include "configuration.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 <pb_decode.h>
#include <pb_encode.h>
/**
* ## LoRaWAN for North America
@@ -21,112 +17,134 @@ 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)
: radioIf(_pool, _rxDest) // , 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");
#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;
}
/** hash a string into an integer
*
* djb2 by Dan Bernstein.
* http://www.cse.yorku.ca/~oz/hash.html
*/
unsigned long hash(char *str)
{
unsigned long hash = 5381;
int c;
while ((c = *str++) != 0)
hash = ((hash << 5) + hash) + (unsigned char)c; /* hash * 33 + c */
return hash;
}
void MeshRadio::reloadConfig()
{
rf95.setModeIdle(); // Need to be idle before doing init
radioIf.setModeIdle(); // Need to be idle before doing init
// 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
// 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
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
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, 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
// 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);
// 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,
channel_num, channelSettings.tx_power);
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();
// Done with init tell radio to start receiving
radioIf.setModeRx();
}
ErrorCode MeshRadio::send(MeshPacket *p)
{
if (useHardware)
return rf95.send(p);
else
{
rf95.pool.release(p);
return ERRNO_OK;
}
lastTxStart = millis();
if (useHardware)
return radioIf.send(p);
else {
radioIf.pool.release(p);
return ERRNO_OK;
}
}
#define TX_WATCHDOG_TIMEOUT 30 * 1000
void MeshRadio::loop()
{
// Currently does nothing, since we do it all in ISRs now
// 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 && radioIf.mode() == RHGenericDriver::RHModeTx) {
DEBUG_MSG("ERROR! Bug! Tx packet took too long to send, forcing radio into rx mode\n");
radioIf.setModeRx();
if (radioIf.sendingPacket) { // There was probably a packet we were trying to send, free it
radioIf.pool.release(radioIf.sendingPacket);
radioIf.sendingPacket = NULL;
}
recordCriticalError(ErrTxWatchdog);
lastTxStart = 0; // Stop checking for now, because we just warned the developer
}
}

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 "PointerQueue.h"
#include "configuration.h"
#include "mesh.pb.h"
#include <RHMesh.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
@@ -47,15 +56,22 @@
#define NUM_CHANNELS NUM_CHANNELS_JP
#else
#error "HW_VERSION not set"
#endif
#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
@@ -76,18 +92,10 @@ public:
/// The radioConfig object just changed, call this to force the hw to change to the new settings
void reloadConfig();
private:
// RHDatagram manager;
private:
// RHReliableDatagram manager; // don't use mesh yet
RHMesh manager;
// MeshRXHandler rxHandler;
// RHMesh manager;
/// 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);
/// Used for the tx timer watchdog, to check for bugs in our transmit code, msec of last time we did a send
uint32_t lastTxStart = 0;
};

View File

@@ -2,36 +2,41 @@
#include <Arduino.h>
#include <assert.h>
#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.
*/
@@ -39,15 +44,15 @@ FIXME in the initial proof of concept we just skip the entire want/deny flow and
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)
#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)
#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 MAX_RX_FROMRADIO \
4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big
MeshService::MeshService()
: packetPool(MAX_PACKETS),
toPhoneQueue(MAX_RX_TOPHONE),
fromRadioQueue(MAX_RX_FROMRADIO),
fromNum(0),
: packetPool(MAX_PACKETS), toPhoneQueue(MAX_RX_TOPHONE), fromRadioQueue(MAX_RX_FROMRADIO), fromNum(0),
radio(packetPool, fromRadioQueue)
{
// assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro
@@ -66,10 +71,11 @@ void MeshService::init()
// sendOwnerPeriod();
}
void MeshService::sendOurOwner(NodeNum dest)
void MeshService::sendOurOwner(NodeNum dest, bool wantReplies)
{
MeshPacket *p = allocForSending();
p->to = dest;
p->payload.want_response = wantReplies;
p->payload.which_variant = SubPacket_user_tag;
User &u = p->payload.variant.user;
u = owner;
@@ -87,29 +93,25 @@ MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp)
// we win if we have a lower macaddr
bool weWin = memcmp(&owner.macaddr, &mp->payload.variant.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
releaseToPool(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);
@@ -117,7 +119,7 @@ MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp)
sendOurOwner(mp->from);
String lcd = String("Joined: ") + mp->payload.variant.user.long_name + "\n";
screen_print(lcd.c_str());
screen.print(lcd.c_str());
}
return mp;
@@ -125,12 +127,10 @@ MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp)
void MeshService::handleIncomingPosition(MeshPacket *mp)
{
if (mp->has_payload && mp->payload.which_variant == SubPacket_position_tag)
{
if (mp->has_payload && mp->payload.which_variant == SubPacket_position_tag) {
DEBUG_MSG("handled incoming position time=%u\n", mp->payload.variant.position.time);
if (mp->payload.variant.position.time)
{
if (mp->payload.variant.position.time) {
struct timeval tv;
uint32_t secs = mp->payload.variant.position.time;
@@ -139,52 +139,55 @@ void MeshService::handleIncomingPosition(MeshPacket *mp)
gps.perhapsSetRTC(&tv);
}
} else {
DEBUG_MSG("Ignoring incoming packet - not a position\n");
}
}
void MeshService::handleFromRadio(MeshPacket *mp)
{
powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping
mp->rx_time = gps.getValidTime(); // store the arrival timestamp for the phone
// 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(!myNodeInfo.has_gps)
handleIncomingPosition(mp);
if (!gps.isConnected)
handleIncomingPosition(mp);
else {
DEBUG_MSG("Ignoring incoming time, because we have a GPS\n");
}
if (mp->has_payload && mp->payload.which_variant == SubPacket_user_tag)
{
if (mp->has_payload && mp->payload.which_variant == SubPacket_user_tag) {
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
assert(toPhoneQueue.enqueue(mp, 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)
{
while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) {
handleFromRadio(mp);
}
if (oldFromNum != fromNum) // We don't want to generate extra notifies for multiple new packets
@@ -215,6 +218,7 @@ void MeshService::loop()
void MeshService::reloadConfig()
{
// If we can successfully set this radio to these settings, save them to disk
nodeDB.resetRadioConfig(); // Don't let the phone send us fatally bad settings
radio.reloadConfig();
nodeDB.saveToDisk();
}
@@ -224,23 +228,20 @@ 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
handleIncomingPosition(&r.variant.packet); // If it is a position packet, perhaps set our clock
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)
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)
// Send the packet into the mesh
sendToMesh(packetPool.allocCopy(r.variant.packet));
bool loopback = false; // if true send any packet the phone sends back itself (for testing)
if (loopback)
{
if (loopback) {
MeshPacket *mp = packetPool.allocCopy(r.variant.packet);
handleFromRadio(mp);
bluetoothNotifyFromNum(fromNum); // tell the phone a new packet arrived
@@ -251,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");
}
}
@@ -258,21 +261,21 @@ 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)
// 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.which_variant == SubPacket_position_tag)
{
if (!myNodeInfo.has_gps)
// 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.which_variant == SubPacket_position_tag) {
if (!gps.isConnected) {
DEBUG_MSG("Stripping time %u from position send\n", p->payload.variant.position.time);
p->payload.variant.position.time = 0;
else
} else
DEBUG_MSG("Providing time to mesh %u\n", p->payload.variant.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");
@@ -291,18 +294,19 @@ MeshPacket *MeshService::allocForSending()
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);
@@ -313,7 +317,9 @@ void MeshService::sendOurPosition(NodeNum dest)
p->to = dest;
p->payload.which_variant = SubPacket_position_tag;
p->payload.variant.position = node->position;
p->payload.variant.position.time = gps.getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid.
p->payload.want_response = wantReplies;
p->payload.variant.position.time =
gps.getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid.
sendToMesh(p);
}
@@ -322,25 +328,26 @@ void MeshService::onGPSChanged()
// 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();
// !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);
@@ -352,4 +359,4 @@ void MeshService::onNotify(Observable *o)
{
DEBUG_MSG("got gps notify\n");
onGPSChanged();
}
}

View File

@@ -3,17 +3,17 @@
#include <Arduino.h>
#include <assert.h>
#include "mesh.pb.h"
#include "MeshRadio.h"
#include "PointerQueue.h"
#include "MemoryPool.h"
#include "MeshRadio.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 : private Observer
{
MemoryPool<MeshPacket> packetPool;
@@ -30,14 +30,13 @@ class MeshService: private Observer
/// The current nonce for the newest packet which has been queued for the phone
uint32_t fromNum;
public:
public:
MeshRadio radio;
MeshService();
void init();
/// Do idle processing (mostly processing messages which have been queued from the radio)
void loop();
@@ -56,21 +55,24 @@ public:
/// The owner User record just got updated, update our node DB and broadcast the info into the mesh
void reloadOwner() { sendOurOwner(); }
/// Allocate and return a meshpacket which defaults as send to broadcast from the current node.
MeshPacket *allocForSending();
/// Called when the user wakes up our GUI, normally sends our latest location to the mesh (if we have it), otherwise at least sends our owner
void sendNetworkPing(NodeNum dest = 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:
/// Send our owner info to a particular node
void sendOurOwner(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
private:
/// Broadcasts our last known position
void sendOurPosition(NodeNum dest = NODENUM_BROADCAST);
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
/// 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
@@ -81,7 +83,7 @@ private:
/// handle all the packets that just arrived from the mesh radio
void handleFromRadio();
/// Handle a packet that just arrived from the radio
/// Handle a packet that just arrived from the radio. We will either eventually enqueue the message to the phone or return it to the free pool
void handleFromRadio(MeshPacket *p);
/// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all
@@ -92,4 +94,3 @@ private:
};
extern MeshService service;

View File

@@ -1,6 +1,6 @@
#pragma once
// low level types
// low level types
#include <Arduino.h>

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,10 +22,18 @@ 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
#define FS SPIFFS
/**
*
/**
*
* Normally userids are unique and start with +country code to look like Signal phone numbers.
* But there are some special ids used when we haven't yet been configured by a user. In that case
* we use !macaddr (no colons).
@@ -33,45 +42,64 @@ 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 = 30;
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");
}
}
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();
// default to no GPS, until one has been found by probing
myNodeInfo.has_gps = false;
#ifdef GPS_RX_PIN
// some hardware defaults to have a built in GPS
myNodeInfo.has_gps = true;
#endif
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]);
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)
@@ -96,6 +124,7 @@ void NodeDB::init()
// saveToDisk();
loadFromDisk();
resetRadioConfig(); // If bogus settings got saved, then fix them
DEBUG_MSG("NODENUM=0x%x, dbsize=%d\n", myNodeInfo.my_node_num, *numNodes);
}
@@ -104,7 +133,7 @@ void NodeDB::init()
#define NUM_RESERVED 4
/**
* get our starting (provisional) nodenum from flash.
* get our starting (provisional) nodenum from flash.
*/
void NodeDB::pickNewNodeNum()
{
@@ -114,8 +143,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;
@@ -132,35 +160,29 @@ void NodeDB::loadFromDisk()
static DeviceState scratch;
File f = FS.open(preffile);
if (f)
{
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");
}
}
@@ -168,17 +190,15 @@ void NodeDB::loadFromDisk()
void NodeDB::saveToDisk()
{
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
}
@@ -190,9 +210,7 @@ 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
}
}
@@ -236,8 +254,7 @@ 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);
@@ -247,16 +264,13 @@ 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:
{
switch (p.which_variant) {
case SubPacket_position_tag: {
// we carefully preserve the old time, because we always trust our local timestamps more
uint32_t oldtime = info->position.time;
info->position = p.variant.position;
@@ -266,14 +280,12 @@ void NodeDB::updateFrom(const MeshPacket &mp)
break;
}
case SubPacket_data_tag:
{
case SubPacket_data_tag: {
// 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.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()) {
// We only store/display messages destined for us.
devicestate.rx_text_message = mp;
devicestate.has_rx_text_message = true;
@@ -284,18 +296,17 @@ void NodeDB::updateFrom(const MeshPacket &mp)
break;
}
case SubPacket_user_tag:
{
case SubPacket_user_tag: {
DEBUG_MSG("old user %s/%s/%s\n", info->user.id, info->user.long_name, info->user.short_name);
bool changed = memcmp(&info->user, &p.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.variant.user,
sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay
info->user = p.variant.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);
@@ -328,8 +339,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)++];
@@ -340,4 +350,13 @@ 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

@@ -10,14 +10,14 @@ class Observer
{
Observable *observed;
public:
public:
Observer() : observed(NULL) {}
virtual ~Observer();
void observe(Observable *o);
private:
private:
friend class Observable;
virtual void onNotify(Observable *o) = 0;
@@ -27,23 +27,15 @@ class Observable
{
std::list<Observer *> observers;
public:
public:
void notifyObservers()
{
for (std::list<Observer *>::const_iterator iterator = observers.begin(); iterator != observers.end(); ++iterator)
{
for (std::list<Observer *>::const_iterator iterator = observers.begin(); iterator != observers.end(); ++iterator) {
(*iterator)->onNotify(this);
}
}
void addObserver(Observer *o)
{
observers.push_back(o);
}
void addObserver(Observer *o) { observers.push_back(o); }
void removeObserver(Observer *o)
{
observers.remove(o);
}
void removeObserver(Observer *o) { observers.remove(o); }
};

View File

@@ -1,22 +1,21 @@
#pragma once
#include <Arduino.h>
#include "PeriodicTask.h"
#include <Arduino.h>
/**
* Periodically invoke a callback.
*
*
* This just provides C style callback conventions rather than a virtual function - FIXME, remove?
*/
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,23 +1,25 @@
#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()
{
uint32_t p = callback();
setPeriod(p);
}
}

View File

@@ -1,11 +1,12 @@
#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
*
*
* FIXME: currently just syntatic sugar for polling in loop (you must call .loop), but eventually
* generalize with the freertos scheduler so we can save lots of power by having everything either in
* something like this or triggered off of an irq.
@@ -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

@@ -5,20 +5,17 @@
/**
* A wrapper for freertos queues that assumes each element is a pointer
*/
template <class T>
class PointerQueue : public TypedQueue<T *>
template <class T> class PointerQueue : public TypedQueue<T *>
{
public:
PointerQueue(int maxElements) : TypedQueue<T *>(maxElements)
{
}
public:
PointerQueue(int maxElements) : TypedQueue<T *>(maxElements) {}
// returns a ptr or null if the queue was empty
T *dequeuePtr(TickType_t maxWait = portMAX_DELAY)
{
T *p;
return this->dequeue(&p, maxWait) == pdTRUE ? p : NULL;
return this->dequeue(&p, maxWait) ? p : nullptr;
}
// returns a ptr or null if the queue was empty
@@ -26,6 +23,6 @@ public:
{
T *p;
return this->dequeueFromISR(&p, higherPriWoken) == pdTRUE ? p : NULL;
return this->dequeueFromISR(&p, higherPriWoken) ? p : nullptr;
}
};

View File

@@ -1,12 +1,12 @@
#include "sleep.h"
#include "PowerFSM.h"
#include "GPS.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "configuration.h"
#include "screen.h"
#include "PowerFSM.h"
#include "GPS.h"
#include "main.h"
#include "screen.h"
#include "sleep.h"
static void sdsEnter()
{
@@ -23,24 +23,41 @@ static void sdsEnter()
doDeepSleep(radioConfig.preferences.sds_secs * 1000LL);
}
#include "error.h"
static void lsEnter()
{
while (!service.radio.rf95.canSleep())
DEBUG_MSG("lsEnter begin, ls_secs=%u\n", radioConfig.preferences.ls_secs);
screen.setOn(false);
uint32_t now = millis();
while (!service.radio.radioIf.canSleep()) {
delay(10); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives)
if (millis() - now > 30 * 1000) { // If we wait too long just report an error and go to sleep
recordCriticalError(ErrSleepEnterWait);
break;
}
}
gps.prepareSleep(); // abandon in-process parsing
if(!isUSBPowered) // FIXME - temp hack until we can put gps in sleep mode, if we have AC when we go to sleep then leave GPS on
setGPSPower(false); // kill GPS power
// if (!isUSBPowered) // FIXME - temp hack until we can put gps in sleep mode, if we have AC when we go to sleep then
// leave GPS on
// 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);
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;
@@ -55,21 +72,40 @@ 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);
}
}
}
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
@@ -77,6 +113,7 @@ static void nbEnter()
static void darkEnter()
{
setBluetoothEnable(true);
screen.setOn(false);
}
@@ -84,41 +121,47 @@ 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
@@ -145,7 +188,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,4 +1,4 @@
#pragma once
#pragma once
#include <Fsm.h>
@@ -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;

15
src/RadioInterface.cpp Normal file
View File

@@ -0,0 +1,15 @@
#include "CustomRF95.h"
#include "NodeDB.h"
#include "assert.h"
#include "configuration.h"
#include <pb_decode.h>
#include <pb_encode.h>
RadioInterface::RadioInterface(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &_rxDest) : pool(_pool), rxDest(_rxDest) {}
ErrorCode SimRadio::send(MeshPacket *p)
{
DEBUG_MSG("SimRadio.send\n");
pool.release(p);
return ERRNO_OK;
}

123
src/RadioInterface.h Normal file
View File

@@ -0,0 +1,123 @@
#pragma once
#include "MemoryPool.h"
#include "MeshTypes.h"
#include "PointerQueue.h"
#include "mesh.pb.h"
#include <RH_RF95.h>
#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission
/**
* Basic operations all radio chipsets must implement.
*
* This defines the SOLE API for talking to radios (because soon we will have alternate radio implementations)
*/
class RadioInterface
{
friend class MeshRadio; // for debugging we let that class touch pool
protected:
MemoryPool<MeshPacket> &pool;
PointerQueue<MeshPacket> &rxDest;
MeshPacket *sendingPacket = NULL; // The packet we are currently sending
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
*/
RadioInterface(MemoryPool<MeshPacket> &pool, PointerQueue<MeshPacket> &rxDest);
/**
* Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving)
*
* This method must be used before putting the CPU into deep or light sleep.
*/
virtual bool canSleep() { return true; }
/// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep.
/// return true for success
virtual bool sleep() { return true; }
/// Send a packet (possibly by enquing in a private fifo). This routine will
/// later free() the packet to pool. This routine is not allowed to stall because it is called from
/// bluetooth comms code. If the txmit queue is empty it might return an error
virtual ErrorCode send(MeshPacket *p) = 0;
};
class SimRadio : public RadioInterface
{
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
*/
SimRadio(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &_rxDest) : RadioInterface(_pool, _rxDest) {}
virtual ErrorCode send(MeshPacket *p);
// methods from radiohead
/// Sets the address of this node. Defaults to 0xFF. Subclasses or the user may want to change this.
/// This will be used to test the adddress in incoming messages. In non-promiscuous mode,
/// only messages with a TO header the same as thisAddress or the broadcast addess (0xFF) will be accepted.
/// In promiscuous mode, all messages will be accepted regardless of the TO header.
/// In a conventional multinode system, all nodes will have a unique address
/// (which you could store in EEPROM).
/// You would normally set the header FROM address to be the same as thisAddress (though you dont have to,
/// allowing the possibilty of address spoofing).
/// \param[in] thisAddress The address of this node.
virtual void setThisAddress(uint8_t thisAddress) {}
/// Initialise the Driver transport hardware and software.
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
virtual bool init() { return true; }
/// Sets the transmitter and receiver
/// centre frequency.
/// \param[in] centre Frequency in MHz. 137.0 to 1020.0. Caution: RFM95/96/97/98 comes in several
/// different frequency ranges, and setting a frequency outside that range of your radio will probably not work
/// \return true if the selected frquency centre is within range
bool setFrequency(float centre) { return true; }
/// Select one of the predefined modem configurations. If you need a modem configuration not provided
/// here, use setModemRegisters() with your own ModemConfig.
/// Caution: the slowest protocols may require a radio module with TCXO temperature controlled oscillator
/// for reliable operation.
/// \param[in] index The configuration choice.
/// \return true if index is a valid choice.
bool setModemConfig(RH_RF95::ModemConfigChoice index) { return true; }
/// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running,
/// disables them.
void setModeIdle() {}
/// If current mode is Tx or Idle, changes it to Rx.
/// Starts the receiver in the RF95/96/97/98.
void setModeRx() {}
/// Returns the operating mode of the library.
/// \return the current mode, one of RF69_MODE_*
virtual RHGenericDriver::RHMode mode() { return RHGenericDriver::RHModeIdle; }
/// Sets the transmitter power output level, and configures the transmitter pin.
/// Be a good neighbour and set the lowest power level you need.
/// Some SX1276/77/78/79 and compatible modules (such as RFM95/96/97/98)
/// use the PA_BOOST transmitter pin for high power output (and optionally the PA_DAC)
/// while some (such as the Modtronix inAir4 and inAir9)
/// use the RFO transmitter pin for lower power but higher efficiency.
/// You must set the appropriate power level and useRFO argument for your module.
/// Check with your module manufacturer which transmtter pin is used on your module
/// to ensure you are setting useRFO correctly.
/// Failure to do so will result in very low
/// transmitter power output.
/// Caution: legal power limits may apply in certain countries.
/// After init(), the power will be set to 13dBm, with useRFO false (ie PA_BOOST enabled).
/// \param[in] power Transmitter power level in dBm. For RFM95/96/97/98 LORA with useRFO false,
/// valid values are from +5 to +23.
/// For Modtronix inAir4 and inAir9 with useRFO true (ie RFO pins in use),
/// valid values are from -1 to 14.
/// \param[in] useRFO If true, enables the use of the RFO transmitter pins instead of
/// the PA_BOOST pin (false). Choose the correct setting for your module.
void setTxPower(int8_t power, bool useRFO = false) {}
};

View File

@@ -1,58 +1,38 @@
#pragma once
#include <Arduino.h>
#include <assert.h>
#include <cassert>
#include <type_traits>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
/**
* A wrapper for freertos queues. Note: each element object must be quite small, so T should be only
* pointer types or ints
* A wrapper for freertos queues. Note: each element object should be small
* and POD (Plain Old Data type) as elements are memcpied by value.
*/
template <class T>
class TypedQueue
template <class T> class TypedQueue
{
static_assert(std::is_pod<T>::value, "T must be pod");
QueueHandle_t h;
public:
public:
TypedQueue(int maxElements)
{
h = xQueueCreate(maxElements, sizeof(T));
assert(h);
}
~TypedQueue()
{
vQueueDelete(h);
}
~TypedQueue() { vQueueDelete(h); }
int numFree()
{
return uxQueueSpacesAvailable(h);
}
int numFree() { return uxQueueSpacesAvailable(h); }
bool isEmpty()
{
return uxQueueMessagesWaiting(h) == 0;
}
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);
}
bool enqueue(T x, TickType_t maxWait = portMAX_DELAY) { return xQueueSendToBack(h, &x, maxWait) == pdTRUE; }
BaseType_t enqueueFromISR(T x, BaseType_t *higherPriWoken)
{
return xQueueSendToBackFromISR(h, &x, higherPriWoken);
}
bool enqueueFromISR(T x, BaseType_t *higherPriWoken) { return xQueueSendToBackFromISR(h, &x, higherPriWoken) == pdTRUE; }
// pdTRUE for success else failure
BaseType_t dequeue(T *p, TickType_t maxWait = portMAX_DELAY)
{
return xQueueReceive(h, p, maxWait);
}
bool dequeue(T *p, TickType_t maxWait = portMAX_DELAY) { return xQueueReceive(h, p, maxWait) == pdTRUE; }
BaseType_t dequeueFromISR(T *p, BaseType_t *higherPriWoken)
{
return xQueueReceiveFromISR(h, p, higherPriWoken);
}
bool dequeueFromISR(T *p, BaseType_t *higherPriWoken) { return xQueueReceiveFromISR(h, p, higherPriWoken); }
};

View File

@@ -39,17 +39,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// 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
@@ -77,6 +69,10 @@ 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
// -----------------------------------------------------------------------------
// GPS
// -----------------------------------------------------------------------------
@@ -84,14 +80,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define GPS_SERIAL_NUM 1
#define GPS_BAUDRATE 9600
#if defined(T_BEAM_V10)
#define GPS_RX_PIN 34
#ifdef USE_JTAG
#define GPS_TX_PIN -1
#else
#define GPS_TX_PIN 12
#endif
#endif
// -----------------------------------------------------------------------------
// LoRa SPI
@@ -102,11 +96,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MOSI_GPIO 27
#define NSS_GPIO 18
#if defined(T_BEAM_V10)
#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 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
@@ -116,34 +110,98 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef USE_JTAG
#define RESET_GPIO 14
#endif
#define DIO0_GPIO 26
#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
#elif defined(HELTEC_LORA32)
#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 "HELTEC"
#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 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 BUTTON_PIN 39
#ifndef USE_JTAG
#define RESET_GPIO 23
#endif
#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
#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
#define BUTTON_PIN 0
#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
#define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio
#endif
#define DIO0_GPIO 26
#define DIO1_GPIO 35
#define DIO2_GPIO 34
#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 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 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 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 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"
#endif
// -----------------------------------------------------------------------------

20
src/debug.cpp Normal file
View File

@@ -0,0 +1,20 @@
#include "debug.h"
#include <cstdint>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "configuration.h"
namespace meshtastic
{
void printThreadInfo(const char *extra)
{
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));
}
} // 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

7
src/error.h Normal file
View File

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

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

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"
@@ -14,7 +11,7 @@ const
#if 0
const
#include "compass.xbm"
#endif
#endif
#if 0
const uint8_t activeSymbol[] PROGMEM = {

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

46
src/lock.h Normal file
View File

@@ -0,0 +1,46 @@
#pragma once
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.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

View File

@@ -21,33 +21,40 @@
*/
#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 "MeshBluetoothService.h"
#include "MeshRadio.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "Periodic.h"
#include "PowerFSM.h"
#include "configuration.h"
#include "esp32/pm.h"
#include "esp_pm.h"
#include "MeshRadio.h"
#include "power.h"
#include "rom/rtc.h"
#include "screen.h"
#include "sleep.h"
#include "PowerFSM.h"
#include <Wire.h>
#include <driver/rtc_io.h>
#ifdef T_BEAM_V10
#ifdef TBEAM_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;
// 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;
@@ -60,84 +67,89 @@ bool bluetoothOn;
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);
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++;
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");
}
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);
}
}
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");
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
*
* 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");
#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.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
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");
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.
@@ -158,154 +170,154 @@ void axp192Init()
//val = 0x46;
//axp._writeByte(AXP202_OFF_CTL, 1, &val); // enable bat detection
#endif
axp.debugCharging();
axp.debugCharging();
#ifdef PMU_IRQ
pinMode(PMU_IRQ, INPUT_PULLUP);
attachInterrupt(PMU_IRQ, [] {
pmu_irq = true;
},
RISING);
pinMode(PMU_IRQ, INPUT);
attachInterrupt(
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
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();
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
isCharging = axp.isChargeing() ? 1 : 0;
isUSBPowered = axp.isVBUSPlug() ? 1 : 0;
readPowerStatus();
} else {
DEBUG_MSG("AXP192 Begin FAIL\n");
}
} else {
DEBUG_MSG("AXP192 not found\n");
}
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);
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;
// 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);
DEBUG_PORT.begin(SERIAL_BAUD);
#endif
initDeepSleep();
initDeepSleep();
#ifdef VEXT_ENABLE
pinMode(VEXT_ENABLE, OUTPUT);
digitalWrite(VEXT_ENABLE, 0); // turn on the display power
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);
pinMode(RESET_OLED, OUTPUT);
digitalWrite(RESET_OLED, 1);
#endif
#ifdef I2C_SDA
Wire.begin(I2C_SDA, I2C_SCL);
scanI2Cdevice();
Wire.begin(I2C_SDA, I2C_SCL);
scanI2Cdevice();
#endif
axp192Init();
// Buttons & LED
// Buttons & LED
#ifdef BUTTON_PIN
pinMode(BUTTON_PIN, INPUT_PULLUP);
digitalWrite(BUTTON_PIN, 1);
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
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, 1); // turn on for now
#endif
// Hello
DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", xstr(APP_VERSION), xstr(HW_VERSION));
// Hello
DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", xstr(APP_VERSION), xstr(HW_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
// 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();
// Initialize the screen first so we can show the logo while we start up everything else.
if (ssd1306_found)
screen.setup();
// Init GPS
gps.setup();
axp192Init();
screen_print("Started...\n");
screen.print("Started...\n");
service.init();
// Init GPS
gps.setup();
// setBluetoothEnable(false); we now don't start bluetooth until we enter the proper state
setCPUFast(false); // 80MHz is fine for our slow peripherals
service.init();
PowerFSM_setup();
powerFSM.trigger(EVENT_BOOT); // transition to ON, FIXME, only do this for cold boots, not waking from SDS
// 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
}
void initBluetooth()
{
DEBUG_MSG("Starting bluetooth\n");
DEBUG_MSG("Starting bluetooth\n");
// FIXME - we are leaking like crazy
// AllocatorScope scope(btPool);
// FIXME - we are leaking like crazy
// AllocatorScope scope(btPool);
BLEServer *serve = initBLE(getDeviceName(), HW_VENDOR, xstr(APP_VERSION), xstr(HW_VERSION)); // FIXME, use a real name based on the macaddr
createMeshBluetoothService(serve);
// 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();
// 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);
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();
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
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();
}
}
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;
static bool ledOn;
ledOn ^= 1;
setLed(ledOn);
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);
// 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);
@@ -313,105 +325,115 @@ Periodic ledPeriodic(ledBlinker);
#if 0
// Turn off for now
uint32_t axpReads()
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(axpReads);
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?
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();
powerFSM.run_machine();
gps.loop();
service.loop();
ledPeriodic.loop();
// axpDebugOutput.loop();
loopBLE();
ledPeriodic.loop();
// axpDebugOutput.loop();
loopBLE();
// for debug printing
// service.radio.rf95.canSleep();
#ifdef T_BEAM_V10
if (axp192_found)
{
#ifdef PMU_IRQ
if (pmu_irq)
{
pmu_irq = false;
axp.readIRQ();
if (pmu_irq) {
pmu_irq = false;
axp.readIRQ();
DEBUG_MSG("pmu irq!\n");
DEBUG_MSG("pmu irq!\n");
isCharging = axp.isChargeing() ? 1 : 0;
isUSBPowered = axp.isVBUSPlug() ? 1 : 0;
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");
}
axp.clearIRQ();
readPowerStatus();
axp.clearIRQ();
}
// FIXME AXP192 interrupt is not firing, remove this temporary polling of battery state
isCharging = axp.isChargeing() ? 1 : 0;
isUSBPowered = axp.isVBUSPlug() ? 1 : 0;
#endif
}
#endif
#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;
static uint32_t minPressMs; // what tick should we call this press long enough
static uint32_t lastPingMs;
// 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");
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;
// 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);
powerFSM.trigger(EVENT_PRESS);
}
} else if (wasPressed) {
// we just did a release
wasPressed = false;
}
}
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);
// Show boot screen for first 3 seconds, then switch to normal operation.
static bool showingBootScreen = true;
if (showingBootScreen && (millis() > 3000)) {
screen.stopBootScreen();
showingBootScreen = false;
}
// 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;
// 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();
delay(msecstosleep);
}
// 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,6 +1,11 @@
#pragma once
#include "screen.h"
extern bool axp192_found;
extern bool ssd1306_found;
extern bool isCharging;
extern bool isUSBPowered;
extern bool isUSBPowered;
// Global Screen singleton.
extern meshtastic::Screen screen;

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,33 @@ 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)
{
File *file = (File *)stream->state;
bool status;
if (buf == NULL)
{
if (buf == NULL) {
while (count-- && file->read() != EOF)
;
return count == 0;
@@ -61,11 +52,10 @@ bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count)
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;
File *file = (File *)stream->state;
// DEBUG_MSG("writing %d bytes to protobuf file\n", count);
return file->write(buf, count) == count;
}

View File

@@ -8,11 +8,10 @@
#define member_size(type, member) sizeof(((type *)0)->member)
/// max number of packets which can be waiting for delivery to android - note, this value comes from mesh.options protobuf
#define MAX_RX_TOPHONE (member_size(DeviceState, receive_queue) / member_size(DeviceState, receive_queue[0]))
#define MAX_RX_TOPHONE (member_size(DeviceState, receive_queue) / member_size(DeviceState, receive_queue[0]))
/// max number of nodes allowed in the mesh
#define MAX_NUM_NODES (member_size(DeviceState, node_db) / member_size(DeviceState, node_db[0]))
#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

View File

@@ -49,7 +49,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,9 @@ typedef enum _ChannelSettings_ModemConfig {
ChannelSettings_ModemConfig_Bw125Cr48Sf4096 = 3
} ChannelSettings_ModemConfig;
typedef enum _DeviceState_Version {
DeviceState_Version_Unset = 0,
DeviceState_Version_Minimum = 17,
DeviceState_Version_Current = 17
} DeviceState_Version;
/* Struct definitions */
typedef struct _ChannelSettings {
int32_t tx_power;
uint32_t channel_num;
ChannelSettings_ModemConfig modem_config;
pb_byte_t psk[16];
char name[12];
@@ -57,8 +50,11 @@ typedef struct _MyNodeInfo {
bool has_gps;
int32_t num_channels;
char region[12];
char hw_model[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 {
@@ -138,9 +134,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 {
@@ -172,10 +168,6 @@ 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}
@@ -183,12 +175,12 @@ typedef struct _ToRadio {
#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 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}
@@ -196,18 +188,17 @@ typedef struct _ToRadio {
#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 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 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
@@ -219,6 +210,9 @@ typedef struct _ToRadio {
#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
@@ -261,7 +255,7 @@ typedef struct _ToRadio {
#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
@@ -313,7 +307,6 @@ X(a, STATIC, SINGULAR, UINT32, rx_time, 4)
#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)
@@ -362,7 +355,10 @@ X(a, STATIC, SINGULAR, BOOL, has_gps, 2) \
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, 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
@@ -372,8 +368,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
@@ -431,12 +427,12 @@ extern const pb_msgdesc_t ToRadio_msg;
#define User_size 72
#define SubPacket_size 261
#define MeshPacket_size 292
#define ChannelSettings_size 50
#define RadioConfig_size 126
#define ChannelSettings_size 44
#define RadioConfig_size 120
#define RadioConfig_UserPreferences_size 72
#define NodeInfo_size 155
#define MyNodeInfo_size 63
#define DeviceState_size 15060
#define MyNodeInfo_size 85
#define DeviceState_size 15080
#define FromRadio_size 301
#define ToRadio_size 295

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

@@ -20,54 +20,37 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <OLEDDisplay.h>
#include <Wire.h>
#include "SSD1306Wire.h"
#include "OLEDDisplay.h"
#include "images.h"
#include "fonts.h"
#include "GPS.h"
#include "OLEDDisplayUi.h"
#include "screen.h"
#include "mesh-pb-constants.h"
#include "NodeDB.h"
#include "configuration.h"
#include "fonts.h"
#include "images.h"
#include "main.h"
#include "mesh-pb-constants.h"
#include "screen.h"
#define FONT_HEIGHT 14 // actually 13 for "ariel 10" but want a little extra space
#define FONT_HEIGHT_16 (ArialMT_Plain_16[1] + 1)
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#ifdef I2C_SDA
SSD1306Wire dispdev(SSD1306_ADDRESS, I2C_SDA, I2C_SCL);
#else
SSD1306Wire dispdev(SSD1306_ADDRESS, 0, 0); // fake values to keep build happy, we won't ever init
#endif
bool disp; // true if we are using display
bool screenOn; // true if the display is currently powered
OLEDDisplayUi ui(&dispdev);
#define TRANSITION_FRAMERATE 30 // fps
#define IDLE_FRAMERATE 10 // in fps
#define COMPASS_DIAM 44
#define NUM_EXTRA_FRAMES 2 // text message and debug frame
// A text message frame + debug frame + all the node infos
FrameCallback nonBootFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
Screen screen;
static bool showingBluetooth;
/// If set to true (possibly from an ISR), we should turn on the screen the next time our idle loop runs.
static bool showingBootScreen = true; // start by showing the bootscreen
bool Screen::isOn() { return screenOn; }
void msOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
namespace meshtastic
{
display->setTextAlignment(TEXT_ALIGN_RIGHT);
display->setFont(ArialMT_Plain_10);
display->drawString(128, 0, String(millis()));
}
void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
// A text message frame + debug frame + all the node infos
static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
static uint32_t targetFramerate = IDLE_FRAMERATE;
static char btPIN[16] = "888888";
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// draw an xbm image.
// Please note that everything that should be transitioned
@@ -78,16 +61,10 @@ void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
display->setFont(ArialMT_Plain_16);
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT_16, "meshtastic.org");
ui.disableIndicator();
}
static char btPIN[16] = "888888";
void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// Demonstrates the 3 included default sizes. The fonts come from SSD1306Fonts.h file
// Besides the default fonts there will be a program to convert TrueType fonts into this format
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(ArialMT_Plain_16);
display->drawString(64 + x, 2 + y, "Bluetooth");
@@ -98,80 +75,44 @@ void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(ArialMT_Plain_24);
display->drawString(64 + x, 22 + y, btPIN);
ui.disableIndicator();
}
void drawFrame2(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// Demonstrates the 3 included default sizes. The fonts come from SSD1306Fonts.h file
// Besides the default fonts there will be a program to convert TrueType fonts into this format
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(ArialMT_Plain_10);
display->drawString(0 + x, 10 + y, "Arial 10");
display->setFont(ArialMT_Plain_16);
display->drawString(0 + x, 20 + y, "Arial 16");
display->setFont(ArialMT_Plain_24);
display->drawString(0 + x, 34 + y, "Arial 24");
}
void drawFrame3(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// Text alignment demo
display->setFont(ArialMT_Plain_10);
// The coordinates define the left starting point of the text
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->drawString(0 + x, 11 + y, "Left aligned (0,10)");
// The coordinates define the center of the text
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->drawString(64 + x, 22 + y, "Center aligned (64,22)");
// The coordinates define the right end of the text
display->setTextAlignment(TEXT_ALIGN_RIGHT);
display->drawString(128 + x, 33 + y, "Right aligned (128,33)");
}
/// Draw the last text message we received
void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
MeshPacket &mp = devicestate.rx_text_message;
NodeInfo *node = nodeDB.getNode(mp.from);
// DEBUG_MSG("drawing text message from 0x%x: %s\n", mp.from,
// mp.payload.variant.data.payload.bytes);
// Demo for drawStringMaxWidth:
// with the third parameter you can define the width after which words will be wrapped.
// Currently only spaces and "-" are allowed for wrapping
// with the third parameter you can define the width after which words will
// be wrapped. Currently only spaces and "-" are allowed for wrapping
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(ArialMT_Plain_16);
String sender = (node && node->has_user) ? node->user.short_name : "???";
display->drawString(0 + x, 0 + y, sender);
display->setFont(ArialMT_Plain_10);
// the max length of this buffer is much longer than we can possibly print
static char tempBuf[96];
snprintf(tempBuf, sizeof(tempBuf), " %s", mp.payload.variant.data.payload.bytes); // the max length of this buffer is much longer than we can possibly print
snprintf(tempBuf, sizeof(tempBuf), " %s", mp.payload.variant.data.payload.bytes);
display->drawStringMaxWidth(4 + x, 10 + y, 128, tempBuf);
// ui.disableIndicator();
}
/// Draw a series of fields in a column, wrapping to multiple colums if needed
void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
{
// The coordinates define the left starting point of the text
display->setTextAlignment(TEXT_ALIGN_LEFT);
const char **f = fields;
int xo = x, yo = y;
while (*f)
{
while (*f) {
display->drawString(xo, yo, *f);
yo += FONT_HEIGHT;
if (yo > SCREEN_HEIGHT - FONT_HEIGHT)
{
if (yo > SCREEN_HEIGHT - FONT_HEIGHT) {
xo += SCREEN_WIDTH / 2;
yo = 0;
}
@@ -181,32 +122,37 @@ void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields
/// Draw a series of fields in a row, wrapping to multiple rows if needed
/// @return the max y we ended up printing to
uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
static uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
{
// The coordinates define the left starting point of the text
display->setTextAlignment(TEXT_ALIGN_LEFT);
const char **f = fields;
int xo = x, yo = y;
while (*f)
{
const int COLUMNS = 2; // hardwired for two columns per row....
int col = 0; // track which column we are on
while (*f) {
display->drawString(xo, yo, *f);
xo += SCREEN_WIDTH / 2; // hardwired for two columns per row....
if (xo >= SCREEN_WIDTH)
{
xo += SCREEN_WIDTH / COLUMNS;
// Wrap to next row, if needed.
if (++col >= COLUMNS) {
xo = x;
yo += FONT_HEIGHT;
xo = 0;
col = 0;
}
f++;
}
yo += FONT_HEIGHT; // include the last line in our total
if (col != 0) {
// Include last incomplete line in our total.
yo += FONT_HEIGHT;
}
return yo;
}
/// Ported from my old java code, returns distance in meters along the globe surface (by magic?)
float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
/// Ported from my old java code, returns distance in meters along the globe
/// surface (by magic?)
static float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
{
double pk = (180 / 3.14169);
double a1 = lat_a / pk;
@@ -215,10 +161,8 @@ float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
double b2 = lng_b / pk;
double cos_b1 = cos(b1);
double cos_a1 = cos(a1);
double t1 =
cos_a1 * cos(a2) * cos_b1 * cos(b2);
double t2 =
cos_a1 * sin(a2) * cos_b1 * sin(b2);
double t1 = cos_a1 * cos(a2) * cos_b1 * cos(b2);
double t2 = cos_a1 * sin(a2) * cos_b1 * sin(b2);
double t3 = sin(a1) * sin(b1);
double tt = acos(t1 + t2 + t3);
if (isnan(tt))
@@ -227,31 +171,32 @@ float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
return (float)(6366000 * tt);
}
inline double toRadians(double deg)
static inline double toRadians(double deg)
{
return deg * PI / 180;
}
inline double toDegrees(double r)
static inline double toDegrees(double r)
{
return r * 180 / PI;
}
/**
* Computes the bearing in degrees between two points on Earth. Ported from my old Gaggle android app.
*
* @param lat1
* Latitude of the first point
* @param lon1
* Longitude of the first point
* @param lat2
* Latitude of the second point
* @param lon2
* Longitude of the second point
* @return Bearing between the two points in radians. A value of 0 means due
* north.
*/
float bearing(double lat1, double lon1, double lat2, double lon2)
* Computes the bearing in degrees between two points on Earth. Ported from my
* old Gaggle android app.
*
* @param lat1
* Latitude of the first point
* @param lon1
* Longitude of the first point
* @param lat2
* Latitude of the second point
* @param lon2
* Longitude of the second point
* @return Bearing between the two points in radians. A value of 0 means due
* north.
*/
static float bearing(double lat1, double lon1, double lat2, double lon2)
{
double lat1Rad = toRadians(lat1);
double lat2Rad = toRadians(lat2);
@@ -261,10 +206,13 @@ float bearing(double lat1, double lon1, double lat2, double lon2)
return atan2(y, x);
}
namespace
{
/// A basic 2D point class for drawing
class Point
{
public:
public:
float x, y;
Point(float _x, float _y) : x(_x), y(_y) {}
@@ -272,10 +220,8 @@ public:
/// Apply a rotation around zero (standard rotation matrix math)
void rotate(float radian)
{
float cos = cosf(radian),
sin = sinf(radian);
float rx = x * cos - y * sin,
ry = x * sin + y * cos;
float cos = cosf(radian), sin = sinf(radian);
float rx = x * cos - y * sin, ry = x * sin + y * cos;
x = rx;
y = ry;
@@ -294,23 +240,25 @@ public:
}
};
void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2)
} // namespace
static void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2)
{
d->drawLine(p1.x, p1.y, p2.x, p2.y);
}
/**
* Given a recent lat/lon return a guess of the heading the user is walking on.
*
* We keep a series of "after you've gone 10 meters, what is your heading since the last reference point?"
*
* We keep a series of "after you've gone 10 meters, what is your heading since
* the last reference point?"
*/
float estimatedHeading(double lat, double lon)
static float estimatedHeading(double lat, double lon)
{
static double oldLat, oldLon;
static float b;
if (oldLat == 0)
{
if (oldLat == 0) {
// just prepare for next time
oldLat = lat;
oldLon = lon;
@@ -329,28 +277,28 @@ float estimatedHeading(double lat, double lon)
return b;
}
/// Sometimes we will have Position objects that only have a time, so check for valid lat/lon
bool hasPosition(NodeInfo *n)
/// Sometimes we will have Position objects that only have a time, so check for
/// valid lat/lon
static bool hasPosition(NodeInfo *n)
{
return n->has_position && (n->position.latitude != 0 || n->position.longitude != 0);
}
#define COMPASS_DIAM 44
/// We will skip one node - the one for us, so we just blindly loop over all nodes
/// We will skip one node - the one for us, so we just blindly loop over all
/// nodes
static size_t nodeIndex;
static int8_t prevFrame = -1;
void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// We only advance our nodeIndex if the frame # has changed - because drawNodeInfo will be called repeatedly while the frame is shown
if (state->currentFrame != prevFrame)
{
// We only advance our nodeIndex if the frame # has changed - because
// drawNodeInfo will be called repeatedly while the frame is shown
if (state->currentFrame != prevFrame) {
prevFrame = state->currentFrame;
nodeIndex = (nodeIndex + 1) % nodeDB.getNumNodes();
NodeInfo *n = nodeDB.getNodeByIndex(nodeIndex);
if (n->num == nodeDB.getNodeNum())
{
if (n->num == nodeDB.getNodeNum()) {
// Don't show our node, just skip to next
nodeIndex = (nodeIndex + 1) % nodeDB.getNumNodes();
}
@@ -378,14 +326,14 @@ void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, in
snprintf(lastStr, sizeof(lastStr), "%d hours ago", agoSecs / 60 / 60);
static float simRadian;
simRadian += 0.1; // For testing, have the compass spin unless both locations are valid
simRadian += 0.1; // For testing, have the compass spin unless both
// locations are valid
static char distStr[20];
*distStr = 0; // might not have location data
float headingRadian = simRadian;
NodeInfo *ourNode = nodeDB.getNode(nodeDB.getNodeNum());
if (ourNode && hasPosition(ourNode) && hasPosition(node))
{
if (ourNode && hasPosition(ourNode) && hasPosition(node)) {
Position &op = ourNode->position, &p = node->position;
float d = latLongToMeter(p.latitude, p.longitude, op.latitude, op.longitude);
if (d < 2000)
@@ -393,23 +341,23 @@ void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, in
else
snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
// FIXME, also keep the guess at the operators heading and add/substract it. currently we don't do this and instead draw north up only.
// FIXME, also keep the guess at the operators heading and add/substract
// it. currently we don't do this and instead draw north up only.
float bearingToOther = bearing(p.latitude, p.longitude, op.latitude, op.longitude);
float myHeading = estimatedHeading(p.latitude, p.longitude);
headingRadian = bearingToOther - myHeading;
} else {
// Debug info for gps lock errors
// DEBUG_MSG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasPosition(ourNode), hasPosition(node));
}
const char *fields[] = {
username,
distStr,
signalStr,
lastStr,
NULL};
const char *fields[] = {username, distStr, signalStr, lastStr, NULL};
drawColumns(display, x, y, fields);
// coordinates for the center of the compass
int16_t compassX = x + SCREEN_WIDTH - COMPASS_DIAM / 2 - 1, compassY = y + SCREEN_HEIGHT / 2;
// display->drawXbm(compassX, compassY, compass_width, compass_height, (const uint8_t *)compass_bits);
// display->drawXbm(compassX, compassY, compass_width, compass_height,
// (const uint8_t *)compass_bits);
Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially
float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f;
@@ -417,8 +365,7 @@ void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, in
Point *points[] = {&tip, &tail, &leftArrow, &rightArrow};
for (int i = 0; i < 4; i++)
{
for (int i = 0; i < 4; i++) {
points[i]->rotate(headingRadian);
points[i]->scale(COMPASS_DIAM * 0.6);
points[i]->translate(compassX, compassY);
@@ -430,45 +377,6 @@ void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, in
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
}
void drawDebugInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setFont(ArialMT_Plain_10);
// The coordinates define the left starting point of the text
display->setTextAlignment(TEXT_ALIGN_LEFT);
static char usersStr[20];
snprintf(usersStr, sizeof(usersStr), "Users %d/%d", nodeDB.getNumOnlineNodes(), nodeDB.getNumNodes());
static char channelStr[20];
snprintf(channelStr, sizeof(channelStr), "%s", channelSettings.name);
// We don't show battery levels yet - for now just lie and show debug info
static char batStr[20];
snprintf(batStr, sizeof(channelStr), "Batt %x%%", (isCharging << 1) + isUSBPowered);
const char *fields[] = {
batStr,
"GPS 75%",
usersStr,
channelStr,
NULL};
uint32_t yo = drawRows(display, x, y, fields);
display->drawLogBuffer(x, yo);
}
// This array keeps function pointers to all frames
// frames are the single views that slide in
FrameCallback bootFrames[] = {drawBootScreen};
// Overlays are statically drawn on top of a frame eg. a clock
OverlayCallback overlays[] = {/* msOverlay */};
// how many frames are there?
const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
const int overlaysCount = sizeof(overlays) / sizeof(overlays[0]);
#if 0
void _screen_header()
{
@@ -493,181 +401,161 @@ void _screen_header()
}
#endif
void Screen::setOn(bool on)
Screen::Screen(uint8_t address, uint8_t sda, uint8_t scl) : cmdQueue(32), dispdev(address, sda, scl), ui(&dispdev) {}
void Screen::handleSetOn(bool on)
{
if (!disp)
if (!useDisplay)
return;
if (on != screenOn)
{
if (on != screenOn) {
if (on) {
DEBUG_MSG("Turning on screen\n");
dispdev.displayOn();
setPeriod(1); // redraw ASAP
}
else
} else {
DEBUG_MSG("Turning off screen\n");
dispdev.displayOff();
}
screenOn = on;
}
}
void screen_print(const char *text, uint8_t x, uint8_t y, uint8_t alignment)
{
DEBUG_MSG(text);
if (!disp)
return;
dispdev.setTextAlignment((OLEDDISPLAY_TEXT_ALIGNMENT)alignment);
dispdev.drawString(x, y, text);
}
void screen_print(const char *text)
{
DEBUG_MSG("Screen: %s", text);
if (!disp)
return;
dispdev.print(text);
// ui.update();
}
void Screen::setup()
{
#ifdef I2C_SDA
// Display instance
disp = true;
// 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.
useDisplay = true;
// The ESP is capable of rendering 60fps in 80Mhz mode
// but that won't give you much time for anything else
// run it in 160Mhz mode or just set it to 30 fps
// We do this now in loop()
// ui.setTargetFPS(30);
// Customize the active and inactive symbol
//ui.setActiveSymbol(activeSymbol);
//ui.setInactiveSymbol(inactiveSymbol);
ui.setTimePerTransition(300); // msecs
// You can change this to
// TOP, LEFT, BOTTOM, RIGHT
ui.setIndicatorPosition(BOTTOM);
// Defines where the first frame is located in the bar.
ui.setIndicatorDirection(LEFT_RIGHT);
// You can change the transition that is used
// SLIDE_LEFT, SLIDE_RIGHT, SLIDE_UP, SLIDE_DOWN
ui.setFrameAnimation(SLIDE_LEFT);
// Add frames - we subtract one from the framecount so there won't be a visual glitch when we take the boot screen out of the sequence.
ui.setFrames(bootFrames, bootFrameCount);
// Add overlays
ui.setOverlays(overlays, overlaysCount);
dispdev.resetOrientation();
// Initialising the UI will init the display too.
ui.init();
ui.setTimePerTransition(300); // msecs
ui.setIndicatorPosition(BOTTOM);
// Defines where the first frame is located in the bar.
ui.setIndicatorDirection(LEFT_RIGHT);
ui.setFrameAnimation(SLIDE_LEFT);
// Don't show the page swipe dots while in boot screen.
ui.disableAllIndicators();
// Store a pointer to Screen so we can get to it from static functions.
ui.getUiState()->userData = this;
// Scroll buffer
// Add frames.
static FrameCallback bootFrames[] = {drawBootScreen};
static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
ui.setFrames(bootFrames, bootFrameCount);
// No overlays.
ui.setOverlays(nullptr, 0);
// Require presses to switch between frames.
ui.disableAutoTransition();
// Set up a log buffer with 3 lines, 32 chars each.
dispdev.setLogBuffer(3, 32);
setOn(true); // update our screenOn bool
#ifdef BICOLOR_DISPLAY
dispdev.flipScreenVertically(); // looks better without this on lora32
#ifdef FLIP_SCREEN_VERTICALLY
dispdev.flipScreenVertically();
#endif
// dispdev.setFont(Custom_ArialMT_Plain_10);
// Turn on the display.
handleSetOn(true);
ui.disableAutoTransition(); // we now require presses
#endif
// On some ssd1306 clones, the first draw command is discarded, so draw it
// twice initially.
ui.update();
ui.update();
}
#define TRANSITION_FRAMERATE 30 // fps
#define IDLE_FRAMERATE 10 // in fps
static uint32_t targetFramerate = IDLE_FRAMERATE;
void Screen::doTask()
{
if (!disp)
{ // If we don't have a screen, don't ever spend any CPU for us
// If we don't have a screen, don't ever spend any CPU for us.
if (!useDisplay) {
setPeriod(0);
return;
}
if (!screenOn)
{ // If we didn't just wake and the screen is still off, then stop updating until it is on again
// Process incoming commands.
for (;;) {
CmdItem cmd;
if (!cmdQueue.dequeue(&cmd, 0)) {
break;
}
switch (cmd.cmd) {
case Cmd::SET_ON:
handleSetOn(true);
break;
case Cmd::SET_OFF:
handleSetOn(false);
break;
case Cmd::ON_PRESS:
handleOnPress();
break;
case Cmd::START_BLUETOOTH_PIN_SCREEN:
handleStartBluetoothPinScreen(cmd.bluetooth_pin);
break;
case Cmd::STOP_BLUETOOTH_PIN_SCREEN:
case Cmd::STOP_BOOT_SCREEN:
setFrames();
break;
case Cmd::PRINT:
handlePrint(cmd.print_text);
free(cmd.print_text);
break;
default:
DEBUG_MSG("BUG: invalid cmd");
}
}
if (!screenOn) { // If we didn't just wake and the screen is still off, then
// stop updating until it is on again
setPeriod(0);
return;
}
// Switch to a low framerate (to save CPU) when we are not in transition
// but we should only call setTargetFPS when framestate changes, because otherwise that breaks
// animations.
if (targetFramerate != IDLE_FRAMERATE && ui.getUiState()->frameState == FIXED)
{
// but we should only call setTargetFPS when framestate changes, because
// otherwise that breaks animations.
if (targetFramerate != IDLE_FRAMERATE && ui.getUiState()->frameState == FIXED) {
// oldFrameState = ui.getUiState()->frameState;
DEBUG_MSG("Setting idle framerate\n");
targetFramerate = IDLE_FRAMERATE;
ui.setTargetFPS(targetFramerate);
}
ui.update();
// While showing the bluetooth pair screen all of our standard screen switching is stopped
if (!showingBluetooth)
{
// Once we finish showing the bootscreen, remove it from the loop
if (showingBootScreen)
{
if (millis() > 3 * 1000) // we show the boot screen for a few seconds only
{
showingBootScreen = false;
screen_set_frames();
}
}
else // standard screen loop handling ehre
{
// If the # nodes changes, we need to regen our list of screens
if (nodeDB.updateGUI || nodeDB.updateTextMessage)
{
screen_set_frames();
nodeDB.updateGUI = false;
nodeDB.updateTextMessage = false;
}
// While showing the bootscreen or Bluetooth pair screen all of our
// standard screen switching is stopped.
if (showingNormalScreen) {
// TODO(girts): decouple nodeDB from screen.
// standard screen loop handling ehre
// If the # nodes changes, we need to regen our list of screens
if (nodeDB.updateGUI || nodeDB.updateTextMessage) {
setFrames();
nodeDB.updateGUI = false;
nodeDB.updateTextMessage = false;
}
}
// DEBUG_MSG("want fps %d, fixed=%d\n", targetFramerate, ui.getUiState()->frameState);
// If we are scrolling we need to be called soon, otherwise just 1 fps (to save CPU)
// We also ask to be called twice as fast as we really need so that any rounding errors still result
// with the correct framerate
ui.update();
// DEBUG_MSG("want fps %d, fixed=%d\n", targetFramerate,
// ui.getUiState()->frameState); If we are scrolling we need to be called
// soon, otherwise just 1 fps (to save CPU) We also ask to be called twice
// as fast as we really need so that any rounding errors still result with
// the correct framerate
setPeriod(1000 / targetFramerate);
}
#include "PowerFSM.h"
// Show the bluetooth PIN screen
void screen_start_bluetooth(uint32_t pin)
void Screen::drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
static FrameCallback btFrames[] = {drawFrameBluetooth};
snprintf(btPIN, sizeof(btPIN), "%06d", pin);
DEBUG_MSG("showing bluetooth screen\n");
showingBluetooth = true;
powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
ui.setFrames(btFrames, 1); // Just show the bluetooth frame
// we rely on our main loop to show this screen (because we are invoked deep inside of bluetooth callbacks)
// ui.update(); // manually draw once, because I'm not sure if loop is getting called
Screen *screen = reinterpret_cast<Screen *>(state->userData);
screen->debugInfo.drawFrame(display, state, x, y);
}
// restore our regular frame list
void screen_set_frames()
void Screen::setFrames()
{
DEBUG_MSG("showing standard frames\n");
showingNormalScreen = true;
size_t numnodes = nodeDB.getNumNodes();
// We don't show the node info our our node (if we have it yet - we should)
@@ -678,28 +566,52 @@ void screen_set_frames()
// If we have a text message - show it first
if (devicestate.has_rx_text_message)
nonBootFrames[numframes++] = drawTextMessageFrame;
normalFrames[numframes++] = drawTextMessageFrame;
// then all the nodes
for (size_t i = 0; i < numnodes; i++)
nonBootFrames[numframes++] = drawNodeInfo;
normalFrames[numframes++] = drawNodeInfo;
// then the debug info
nonBootFrames[numframes++] = drawDebugInfo;
//
// Since frames are basic function pointers, we have to use a helper to
// call a method on debugInfo object.
normalFrames[numframes++] = &Screen::drawDebugInfoTrampoline;
ui.setFrames(nonBootFrames, numframes);
showingBluetooth = false;
ui.setFrames(normalFrames, numframes);
ui.enableAllIndicators();
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list just changed)
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
// just changed)
}
/// handle press of the button
void Screen::onPress()
void Screen::handleStartBluetoothPinScreen(uint32_t pin)
{
DEBUG_MSG("showing bluetooth screen\n");
showingNormalScreen = false;
static FrameCallback btFrames[] = {drawFrameBluetooth};
snprintf(btPIN, sizeof(btPIN), "%06d", pin);
ui.disableAllIndicators();
ui.setFrames(btFrames, 1);
}
void Screen::handlePrint(const char *text)
{
DEBUG_MSG("Screen: %s", text);
if (!useDisplay)
return;
dispdev.print(text);
}
void Screen::handleOnPress()
{
// If screen was off, just wake it, otherwise advance to next frame
// If we are in a transition, the press must have bounced, drop it.
if (ui.getUiState()->frameState == FIXED)
{
if (ui.getUiState()->frameState == FIXED) {
setPeriod(1); // redraw ASAP
ui.nextFrame();
@@ -710,3 +622,43 @@ void Screen::onPress()
ui.setTargetFPS(targetFramerate);
}
}
void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setFont(ArialMT_Plain_10);
// The coordinates define the left starting point of the text
display->setTextAlignment(TEXT_ALIGN_LEFT);
char usersStr[20];
char channelStr[20];
char batStr[20];
char gpsStr[20];
{
LockGuard guard(&lock);
snprintf(usersStr, sizeof(usersStr), "Users %d/%d", nodesOnline, nodesTotal);
snprintf(channelStr, sizeof(channelStr), "%s", channelName.c_str());
if (powerStatus.haveBattery) {
// TODO: draw a battery icon instead of letter "B".
int batV = powerStatus.batteryVoltageMv / 1000;
int batCv = (powerStatus.batteryVoltageMv % 1000) / 10;
snprintf(batStr, sizeof(batStr), "B %01d.%02dV%c%c", batV, batCv, powerStatus.charging ? '+' : ' ',
powerStatus.usb ? 'U' : ' ');
} else {
snprintf(batStr, sizeof(batStr), "%s", powerStatus.usb ? "USB" : "");
}
if (!gpsStatus.empty()) {
snprintf(gpsStr, sizeof(gpsStr), "GPS %s", gpsStatus.c_str());
} else {
gpsStr[0] = '\0'; // Just show empty string.
}
}
const char *fields[] = {batStr, gpsStr, usersStr, channelStr, nullptr};
uint32_t yo = drawRows(display, x, y, fields);
display->drawLogBuffer(x, yo);
}
} // namespace meshtastic

View File

@@ -1,40 +1,211 @@
#pragma once
#include <cstring>
#include <OLEDDisplayUi.h>
#include <SSD1306Wire.h>
#include "PeriodicTask.h"
#include "TypedQueue.h"
#include "lock.h"
#include "power.h"
void screen_print(const char * text);
void screen_print(const char * text, uint8_t x, uint8_t y, uint8_t alignment);
// Show the bluetooth PIN screen
void screen_start_bluetooth(uint32_t pin);
// restore our regular frame list
void screen_set_frames();
/**
* Slowly I'm moving screen crap into this class
*/
class Screen : public PeriodicTask
namespace meshtastic
{
public:
void setup();
// Forward declarations
class Screen;
virtual void doTask();
/// Handles gathering and displaying debug information.
class DebugInfo
{
public:
DebugInfo(const DebugInfo &) = delete;
DebugInfo &operator=(const DebugInfo &) = delete;
/// Turn on the screen asap
void doWakeScreen();
/// Sets user statistics.
void setNodeNumbersStatus(int online, int total)
{
LockGuard guard(&lock);
nodesOnline = online;
nodesTotal = total;
}
/// Is the screen currently on
bool isOn();
/// Sets the name of the channel.
void setChannelNameStatus(const char *name)
{
LockGuard guard(&lock);
channelName = name;
}
/// Turn the screen on/off
void setOn(bool on);
/// Sets battery/charging/etc status.
//
void setPowerStatus(const PowerStatus &status)
{
LockGuard guard(&lock);
powerStatus = status;
}
/// Handle a button press
void onPress();
/// Sets GPS status.
//
// If this function never gets called, we assume GPS does not exist on this
// device.
// TODO(girts): figure out what the format should be.
void setGPSStatus(const char *status)
{
LockGuard guard(&lock);
gpsStatus = status;
}
private:
friend Screen;
DebugInfo() {}
/// Renders the debug screen.
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
int nodesOnline = 0;
int nodesTotal = 0;
PowerStatus powerStatus;
std::string channelName;
std::string gpsStatus;
/// Protects all of internal state.
Lock lock;
};
extern Screen screen;
/// Deals with showing things on the screen of the device.
//
// Other than setup(), this class is thread-safe. All state-changing calls are
// queued and executed when the main loop calls us.
//
// This class is thread-safe (as long as drawFrame is not called multiple times
// simultaneously).
class Screen : public PeriodicTask
{
public:
Screen(uint8_t address, uint8_t sda, uint8_t scl);
Screen(const Screen &) = delete;
Screen &operator=(const Screen &) = delete;
/// Initializes the UI, turns on the display, starts showing boot screen.
//
// Not thread safe - must be called before any other methods are called.
void setup();
/// Turns the screen on/off.
void setOn(bool on) { enqueueCmd(CmdItem{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF}); }
/// Handles a button press.
void onPress() { enqueueCmd(CmdItem{.cmd = Cmd::ON_PRESS}); }
/// Starts showing the Bluetooth PIN screen.
//
// Switches over to a static frame showing the Bluetooth pairing screen
// with the PIN.
void startBluetoothPinScreen(uint32_t pin)
{
CmdItem cmd;
cmd.cmd = Cmd::START_BLUETOOTH_PIN_SCREEN;
cmd.bluetooth_pin = pin;
enqueueCmd(cmd);
}
/// Stops showing the bluetooth PIN screen.
void stopBluetoothPinScreen() { enqueueCmd(CmdItem{.cmd = Cmd::STOP_BLUETOOTH_PIN_SCREEN}); }
/// Stops showing the boot screen.
void stopBootScreen() { enqueueCmd(CmdItem{.cmd = Cmd::STOP_BOOT_SCREEN}); }
/// Writes a string to the screen.
void print(const char *text)
{
CmdItem cmd;
cmd.cmd = Cmd::PRINT;
// TODO(girts): strdup() here is scary, but we can't use std::string as
// FreeRTOS queue is just dumbly copying memory contents. It would be
// nice if we had a queue that could copy objects by value.
cmd.print_text = strdup(text);
if (!enqueueCmd(cmd)) {
free(cmd.print_text);
}
}
/// Returns a handle to the DebugInfo screen.
//
// Use this handle to set things like battery status, user count, GPS status, etc.
DebugInfo *debug() { return &debugInfo; }
protected:
/// Updates the UI.
//
// Called periodically from the main loop.
void doTask() final;
private:
enum class Cmd {
INVALID,
SET_ON,
SET_OFF,
ON_PRESS,
START_BLUETOOTH_PIN_SCREEN,
STOP_BLUETOOTH_PIN_SCREEN,
STOP_BOOT_SCREEN,
PRINT,
};
struct CmdItem {
Cmd cmd;
union {
uint32_t bluetooth_pin;
char *print_text;
};
};
/// Enques given command item to be processed by main loop().
bool enqueueCmd(const CmdItem &cmd)
{
if (!useDisplay)
return true; // claim success if our display is not in use
else {
bool success = cmdQueue.enqueue(cmd, 0);
setPeriod(1); // handle ASAP
return success;
}
}
// Implementations of various commands, called from doTask().
void handleSetOn(bool on);
void handleOnPress();
void handleStartBluetoothPinScreen(uint32_t pin);
void handlePrint(const char *text);
/// Rebuilds our list of frames (screens) to default ones.
void setFrames();
/// Called when debug screen is to be drawn, calls through to debugInfo.drawFrame.
static void drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
/// Queue of commands to execute in doTask.
TypedQueue<CmdItem> cmdQueue;
/// Whether we are using a display
bool useDisplay = false;
/// Whether the display is currently powered
bool screenOn = false;
// Whether we are showing the regular screen (as opposed to booth screen or
// Bluetooth PIN screen)
bool showingNormalScreen = false;
/// Holds state for debug information
DebugInfo debugInfo;
/// Display device
SSD1306Wire dispdev;
/// UI helper for rendering to frames and switching between them
OLEDDisplayUi ui;
};
} // namespace meshtastic

View File

@@ -1,25 +1,23 @@
#include "configuration.h"
#include "rom/rtc.h"
#include <driver/rtc_io.h>
#include <TinyGPS++.h>
#include <Wire.h>
#include "sleep.h"
#include "BluetoothUtil.h"
#include "MeshBluetoothService.h"
#include "MeshService.h"
#include "GPS.h"
#include "screen.h"
#include "MeshBluetoothService.h"
#include "MeshRadio.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "Periodic.h"
#include "configuration.h"
#include "esp32/pm.h"
#include "esp_pm.h"
#include "MeshRadio.h"
#include "main.h"
#include "sleep.h"
#include "rom/rtc.h"
#include <Wire.h>
#include <driver/rtc_io.h>
#ifdef T_BEAM_V10
#ifdef TBEAM_V10
#include "axp20x.h"
extern AXP20X_Class axp;
#endif
#endif
// deep sleep support
RTC_DATA_ATTR int bootCount = 0;
@@ -28,36 +26,33 @@ esp_sleep_source_t wakeCause; // the reason we booted this time
#define xstr(s) str(s)
#define str(s) #s
// -----------------------------------------------------------------------------
// Application
// -----------------------------------------------------------------------------
/**
* Control CPU core speed (80MHz vs 240MHz)
*
*
* We leave CPU at full speed during init, but once loop is called switch to low speed (for a 50% power savings)
*
*
*/
void setCPUFast(bool on)
{
setCpuFrequencyMhz(on ? 240 : 80);
setCpuFrequencyMhz(on ? 240 : 80);
}
void setLed(bool ledOn)
{
#ifdef LED_PIN
// toggle the led so we can get some rough sense of how often loop is pausing
digitalWrite(LED_PIN, ledOn);
// toggle the led so we can get some rough sense of how often loop is pausing
digitalWrite(LED_PIN, ledOn);
#endif
#ifdef T_BEAM_V10
if (axp192_found)
{
// blink the axp led
axp.setChgLEDMode(ledOn ? AXP20X_LED_LOW_LEVEL : AXP20X_LED_OFF);
}
#ifdef TBEAM_V10
if (axp192_found) {
// blink the axp led
axp.setChgLEDMode(ledOn ? AXP20X_LED_LOW_LEVEL : AXP20X_LED_OFF);
}
#endif
}
@@ -65,147 +60,176 @@ void setGPSPower(bool on)
{
DEBUG_MSG("Setting GPS power=%d\n", on);
#ifdef T_BEAM_V10
if (axp192_found)
axp.setPowerOutPut(AXP192_LDO3, on ? AXP202_ON : AXP202_OFF); // GPS main power
#ifdef TBEAM_V10
if (axp192_found)
axp.setPowerOutPut(AXP192_LDO3, on ? AXP202_ON : AXP202_OFF); // GPS main power
#endif
}
// Perform power on init that we do on each wake from deep sleep
void initDeepSleep()
{
bootCount++;
wakeCause = esp_sleep_get_wakeup_cause();
/*
Not using yet because we are using wake on all buttons being low
bootCount++;
wakeCause = esp_sleep_get_wakeup_cause();
/*
Not using yet because we are using wake on all buttons being low
wakeButtons = esp_sleep_get_ext1_wakeup_status(); // If one of these buttons is set it was the reason we woke
if (wakeCause == ESP_SLEEP_WAKEUP_EXT1 && !wakeButtons) // we must have been using the 'all buttons rule for waking' to support busted boards, assume button one was pressed
wakeButtons = ((uint64_t)1) << buttons.gpios[0];
*/
wakeButtons = esp_sleep_get_ext1_wakeup_status(); // If one of these buttons is set it was the reason we woke
if (wakeCause == ESP_SLEEP_WAKEUP_EXT1 && !wakeButtons) // we must have been using the 'all buttons rule for waking' to
support busted boards, assume button one was pressed wakeButtons = ((uint64_t)1) << buttons.gpios[0];
*/
DEBUG_MSG("booted, wake cause %d (boot count %d)\n", wakeCause, bootCount);
// If we booted because our timer ran out or the user pressed reset, send those as fake events
const char *reason = "reset"; // our best guess
RESET_REASON hwReason = rtc_get_reset_reason(0);
if (hwReason == RTCWDT_BROWN_OUT_RESET)
reason = "brownout";
if (hwReason == TG0WDT_SYS_RESET)
reason = "taskWatchdog";
if (hwReason == TG1WDT_SYS_RESET)
reason = "intWatchdog";
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER)
reason = "timeout";
DEBUG_MSG("booted, wake cause %d (boot count %d), reset_reason=%s\n", wakeCause, bootCount, reason);
}
void doDeepSleep(uint64_t msecToWake)
{
DEBUG_MSG("Entering deep sleep for %llu seconds\n", msecToWake / 1000);
DEBUG_MSG("Entering deep sleep for %llu seconds\n", msecToWake / 1000);
// not using wifi yet, but once we are this is needed to shutoff the radio hw
// esp_wifi_stop();
// not using wifi yet, but once we are this is needed to shutoff the radio hw
// esp_wifi_stop();
BLEDevice::deinit(false); // We are required to shutdown bluetooth before deep or light sleep
BLEDevice::deinit(false); // We are required to shutdown bluetooth before deep or light sleep
screen.setOn(false); // datasheet says this will draw only 10ua
screen.setOn(false); // datasheet says this will draw only 10ua
// Put radio in sleep mode (will still draw power but only 0.2uA)
service.radio.rf95.sleep();
// Put radio in sleep mode (will still draw power but only 0.2uA)
service.radio.radioIf.sleep();
nodeDB.saveToDisk();
nodeDB.saveToDisk();
#ifdef RESET_OLED
digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power
digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power
#endif
#ifdef VEXT_ENABLE
digitalWrite(VEXT_ENABLE, 1); // turn off the display power
digitalWrite(VEXT_ENABLE, 1); // turn off the display power
#endif
setLed(false);
setLed(false);
#ifdef T_BEAM_V10
if (axp192_found)
{
// No need to turn this off if the power draw in sleep mode really is just 0.2uA and turning it off would
// leave floating input for the IRQ line
#ifdef TBEAM_V10
if (axp192_found) {
// No need to turn this off if the power draw in sleep mode really is just 0.2uA and turning it off would
// leave floating input for the IRQ line
// If we want to leave the radio receving in would be 11.5mA current draw, but most of the time it is just waiting
// in its sequencer (true?) so the average power draw should be much lower even if we were listinging for packets
// all the time.
// If we want to leave the radio receving in would be 11.5mA current draw, but most of the time it is just waiting
// in its sequencer (true?) so the average power draw should be much lower even if we were listinging for packets
// all the time.
// axp.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // LORA radio
// axp.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // LORA radio
setGPSPower(false);
}
setGPSPower(false);
}
#endif
/*
Some ESP32 IOs have internal pullups or pulldowns, which are enabled by default.
If an external circuit drives this pin in deep sleep mode, current consumption may
increase due to current flowing through these pullups and pulldowns.
/*
Some ESP32 IOs have internal pullups or pulldowns, which are enabled by default.
If an external circuit drives this pin in deep sleep mode, current consumption may
increase due to current flowing through these pullups and pulldowns.
To isolate a pin, preventing extra current draw, call rtc_gpio_isolate() function.
For example, on ESP32-WROVER module, GPIO12 is pulled up externally.
GPIO12 also has an internal pulldown in the ESP32 chip. This means that in deep sleep,
some current will flow through these external and internal resistors, increasing deep
sleep current above the minimal possible value.
To isolate a pin, preventing extra current draw, call rtc_gpio_isolate() function.
For example, on ESP32-WROVER module, GPIO12 is pulled up externally.
GPIO12 also has an internal pulldown in the ESP32 chip. This means that in deep sleep,
some current will flow through these external and internal resistors, increasing deep
sleep current above the minimal possible value.
Note: we don't isolate pins that are used for the LORA, LED, i2c, spi or the wake button
*/
static const uint8_t rtcGpios[] = {/* 0, */ 2,
/* 4, */
Note: we don't isolate pins that are used for the LORA, LED, i2c, spi or the wake button
*/
static const uint8_t rtcGpios[] = {/* 0, */ 2,
/* 4, */
#ifndef USE_JTAG
12, 13, /* 14, */ /* 15, */
12, 13,
/* 14, */ /* 15, */
#endif
/* 25, */ 26, /* 27, */
32, 33, 34, 35, 36, 37, /* 38, */ 39};
/* 25, */ 26, /* 27, */
32, 33, 34, 35,
36, 37
/* 38, 39 */};
for (int i = 0; i < sizeof(rtcGpios); i++)
rtc_gpio_isolate((gpio_num_t)rtcGpios[i]);
for (int i = 0; i < sizeof(rtcGpios); i++)
rtc_gpio_isolate((gpio_num_t)rtcGpios[i]);
// FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using
// to detect wake and in normal operation the external part drives them hard.
// FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using
// to detect wake and in normal operation the external part drives them hard.
// We want RTC peripherals to stay on
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// We want RTC peripherals to stay on
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
#ifdef BUTTON_PIN
// Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39.
uint64_t gpioMask = (1ULL << BUTTON_PIN);
// Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39.
uint64_t gpioMask = (1ULL << BUTTON_PIN);
// Not needed because both of the current boards have external pullups
// FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead of just the first)
// gpio_pullup_en((gpio_num_t)BUTTON_PIN);
esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW);
#ifdef BUTTON_NEED_PULLUP
gpio_pullup_en((gpio_num_t)BUTTON_PIN);
#endif
esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs
esp_deep_sleep_start(); // TBD mA sleep current (battery)
// Not needed because both of the current boards have external pullups
// FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead of
// just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN);
esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW);
#endif
esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs
esp_deep_sleep_start(); // TBD mA sleep current (battery)
}
/**
* enter light sleep (preserves ram but stops everything about CPU).
*
*
* Returns (after restoring hw state) when the user presses a button or we get a LoRa interrupt
*/
esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more reasonable default
{
//DEBUG_MSG("Enter light sleep\n");
uint64_t sleepUsec = sleepMsec * 1000LL;
// DEBUG_MSG("Enter light sleep\n");
uint64_t sleepUsec = sleepMsec * 1000LL;
Serial.flush(); // send all our characters before we stop cpu clock
setBluetoothEnable(false); // has to be off before calling light sleep
Serial.flush(); // send all our characters before we stop cpu clock
setBluetoothEnable(false); // has to be off before calling light sleep
// NOTE! ESP docs say we must disable bluetooth and wifi before light sleep
// NOTE! ESP docs say we must disable bluetooth and wifi before light sleep
// We want RTC peripherals to stay on
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// We want RTC peripherals to stay on
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
gpio_wakeup_enable((gpio_num_t)BUTTON_PIN, GPIO_INTR_LOW_LEVEL); // when user presses, this button goes low
gpio_wakeup_enable((gpio_num_t)DIO0_GPIO, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high
#ifdef PMU_IRQ
gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_HIGH_LEVEL); // pmu irq
#ifdef BUTTON_NEED_PULLUP
gpio_pullup_en((gpio_num_t)BUTTON_PIN);
#endif
assert(esp_sleep_enable_gpio_wakeup() == ESP_OK);
assert(esp_sleep_enable_timer_wakeup(sleepUsec) == ESP_OK);
assert(esp_light_sleep_start() == ESP_OK);
//DEBUG_MSG("Exit light sleep\n");
return esp_sleep_get_wakeup_cause();
#ifdef BUTTON_PIN
gpio_wakeup_enable((gpio_num_t)BUTTON_PIN, GPIO_INTR_LOW_LEVEL); // when user presses, this button goes low
#endif
#ifdef RF95_IRQ_GPIO
gpio_wakeup_enable((gpio_num_t)RF95_IRQ_GPIO, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high
#endif
#ifdef PMU_IRQ
// FIXME, disable wake due to PMU because it seems to fire all the time?
if (axp192_found)
gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_LOW_LEVEL); // pmu irq
#endif
assert(esp_sleep_enable_gpio_wakeup() == ESP_OK);
assert(esp_sleep_enable_timer_wakeup(sleepUsec) == ESP_OK);
assert(esp_light_sleep_start() == ESP_OK);
// DEBUG_MSG("Exit light sleep b=%d, rf95=%d, pmu=%d\n", digitalRead(BUTTON_PIN), digitalRead(RF95_IRQ_GPIO),
// digitalRead(PMU_IRQ));
return esp_sleep_get_wakeup_cause();
}
#if 0
@@ -227,4 +251,4 @@ void enableModemSleep()
config.light_sleep_enable = false;
DEBUG_MSG("Sleep request result %x\n", esp_pm_configure(&config));
}
#endif
#endif

View File

@@ -15,7 +15,7 @@ void setCPUFast(bool on);
void setLed(bool ledOn);
extern int bootCount;
extern esp_sleep_source_t wakeCause;
extern esp_sleep_source_t wakeCause;
// is bluetooth sw currently running?
extern bool bluetoothOn;