mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-27 21:20:33 +00:00
Compare commits
504 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21751da5a2 | ||
|
|
c2e8ac7173 | ||
|
|
2fd5ce00ce | ||
|
|
952c216bf7 | ||
|
|
ff4b03b8c1 | ||
|
|
c5903a790b | ||
|
|
bbc36f7b6f | ||
|
|
d7368d5a51 | ||
|
|
47bbde3c60 | ||
|
|
04942a3570 | ||
|
|
62a8c968e8 | ||
|
|
b9a1cae72d | ||
|
|
6b442784f3 | ||
|
|
cfcb62bd18 | ||
|
|
f698883c02 | ||
|
|
4a5cef886e | ||
|
|
f6ec129288 | ||
|
|
17763034a0 | ||
|
|
4ad562b9f4 | ||
|
|
6b838002d4 | ||
|
|
5e0d53a1e3 | ||
|
|
7597d5b3fd | ||
|
|
1a8891c33d | ||
|
|
f0eeaf01d4 | ||
|
|
d4e95e95a6 | ||
|
|
0767c8be03 | ||
|
|
18bbf3523e | ||
|
|
b081a6da56 | ||
|
|
a102e49fdb | ||
|
|
c078c08c3e | ||
|
|
4dd50df810 | ||
|
|
14c4022c18 | ||
|
|
a5d7bacdbf | ||
|
|
0b3c25f6d9 | ||
|
|
ad7a474a52 | ||
|
|
430186ec53 | ||
|
|
227c6fc27e | ||
|
|
a37844d7e5 | ||
|
|
ff20b29c3c | ||
|
|
64c29c4a35 | ||
|
|
d4df3f8a7e | ||
|
|
a16c3af30a | ||
|
|
3061860dab | ||
|
|
2450d98b59 | ||
|
|
a371592ad9 | ||
|
|
f7aaf48ae9 | ||
|
|
1fb604ebc8 | ||
|
|
df2733a3b5 | ||
|
|
8fd3cb1aac | ||
|
|
485c476f17 | ||
|
|
7dd4ce32d2 | ||
|
|
7f12af73d4 | ||
|
|
63113d57b3 | ||
|
|
32850ff39d | ||
|
|
2901f773a4 | ||
|
|
a7c54e4ad7 | ||
|
|
e1f0e11cb8 | ||
|
|
c73ee98739 | ||
|
|
c2a1141dfa | ||
|
|
5b4472ab56 | ||
|
|
3262f732d8 | ||
|
|
cff21ca130 | ||
|
|
81ce04d3da | ||
|
|
f4d2b10840 | ||
|
|
2370cb8aac | ||
|
|
59ec87f5b0 | ||
|
|
0d9481b6ea | ||
|
|
8f0105ccd9 | ||
|
|
05ca3c3d56 | ||
|
|
ba549d8fcd | ||
|
|
b9df2c00fa | ||
|
|
d9dcb33576 | ||
|
|
f698231be7 | ||
|
|
8414f4a6a3 | ||
|
|
8505020be5 | ||
|
|
f3b93d55fb | ||
|
|
9e0731a956 | ||
|
|
2b373048c6 | ||
|
|
22f23bb07d | ||
|
|
b32e3f1269 | ||
|
|
68ddb712f5 | ||
|
|
2fb5cd8c1c | ||
|
|
79aea8231f | ||
|
|
b0837c10c6 | ||
|
|
cd811951b1 | ||
|
|
df2976dad0 | ||
|
|
4ccbe6ff71 | ||
|
|
038ddb887f | ||
|
|
9134faaed1 | ||
|
|
649a120fe0 | ||
|
|
4db0c4a563 | ||
|
|
5f2f3c94b9 | ||
|
|
3b2f5fa5e3 | ||
|
|
97adb598b6 | ||
|
|
152ebf0dff | ||
|
|
5b1511c930 | ||
|
|
7ef2cc8623 | ||
|
|
b41a32c6b6 | ||
|
|
1ebd7b0c3e | ||
|
|
5457541244 | ||
|
|
ca77d48b20 | ||
|
|
965c2bda8d | ||
|
|
02e3438d5e | ||
|
|
02b1ece6ac | ||
|
|
9fdef366f7 | ||
|
|
284816229e | ||
|
|
10008d4eef | ||
|
|
58cfd1317c | ||
|
|
3d3f7869d4 | ||
|
|
ef325289eb | ||
|
|
40c63c0615 | ||
|
|
a9de8b9bb3 | ||
|
|
66a7f896c8 | ||
|
|
45a36f5571 | ||
|
|
876d32c9ee | ||
|
|
b9ce75b09c | ||
|
|
62493efc40 | ||
|
|
2848b76cc9 | ||
|
|
ef8bea478d | ||
|
|
a8e4bbbe65 | ||
|
|
9a414d9c77 | ||
|
|
48e6a60a07 | ||
|
|
ca48079545 | ||
|
|
76b4be3b87 | ||
|
|
d39cc3d57b | ||
|
|
b17a8d7a6a | ||
|
|
3d21794039 | ||
|
|
beac614e65 | ||
|
|
87f2673fc4 | ||
|
|
999b292717 | ||
|
|
c16acb904e | ||
|
|
5b777219be | ||
|
|
32ea11d2af | ||
|
|
8330c3270e | ||
|
|
0c8e0efed2 | ||
|
|
c44d8a0433 | ||
|
|
49b4ed2a89 | ||
|
|
db8faa9faf | ||
|
|
4b9ea4f808 | ||
|
|
c3beca3e23 | ||
|
|
95cb6b06e4 | ||
|
|
c46a884558 | ||
|
|
2044427e97 | ||
|
|
514ebdf013 | ||
|
|
10f64590a9 | ||
|
|
4a70ba1f7a | ||
|
|
dd6a402ea0 | ||
|
|
bed7d8a619 | ||
|
|
6d178ebc91 | ||
|
|
f75a256631 | ||
|
|
4f659b7563 | ||
|
|
1b6e8e36d3 | ||
|
|
7a4b8cde11 | ||
|
|
113859e791 | ||
|
|
a6b82ccfd9 | ||
|
|
e8b8ec69f1 | ||
|
|
023f1c24fb | ||
|
|
f00d07baa3 | ||
|
|
62c228b986 | ||
|
|
1a3cc40c7e | ||
|
|
bdcd5c3981 | ||
|
|
fc82e872d6 | ||
|
|
dffcea1f4d | ||
|
|
b47c54b5b6 | ||
|
|
c0c83ad389 | ||
|
|
23aecbdc38 | ||
|
|
eca7242a1f | ||
|
|
ef899425b8 | ||
|
|
269f90c510 | ||
|
|
7a5832ab8a | ||
|
|
044cc26340 | ||
|
|
4ccd03623f | ||
|
|
7854a22fbf | ||
|
|
943d5cb08d | ||
|
|
7480eb1826 | ||
|
|
c32c97c389 | ||
|
|
b4b1b24c84 | ||
|
|
ef146fc0b5 | ||
|
|
f6861a8fe2 | ||
|
|
736642455f | ||
|
|
3c1c11e439 | ||
|
|
b072eec4ac | ||
|
|
ff9b49ddaa | ||
|
|
b8863c8a07 | ||
|
|
4d7cd0a09d | ||
|
|
fed8e80ae4 | ||
|
|
b38bcffafb | ||
|
|
530432411e | ||
|
|
71cdbb1a73 | ||
|
|
200aa27cc0 | ||
|
|
f7752e4f9d | ||
|
|
7f0e8a8d6b | ||
|
|
f30c84012f | ||
|
|
1f6877606f | ||
|
|
1907873831 | ||
|
|
bacc6caf04 | ||
|
|
56d4250197 | ||
|
|
d66cede7fc | ||
|
|
f7ffd196e3 | ||
|
|
3a638090a2 | ||
|
|
4342ae74fb | ||
|
|
5150d15997 | ||
|
|
7d6dbcfa3f | ||
|
|
5a7962896d | ||
|
|
cfb9a600e4 | ||
|
|
7f3217d69e | ||
|
|
d94be0f534 | ||
|
|
17a3e6e975 | ||
|
|
423cbc2c6d | ||
|
|
124a82888d | ||
|
|
fec7a6bf17 | ||
|
|
bc50b39a3b | ||
|
|
158e3edbe7 | ||
|
|
116fe6d109 | ||
|
|
6a4ef7e1d1 | ||
|
|
a0fd83428f | ||
|
|
e5d4fbb164 | ||
|
|
26bafb4082 | ||
|
|
154dd3990c | ||
|
|
78fe41710b | ||
|
|
1f38404e60 | ||
|
|
848760e5bf | ||
|
|
5ebac0cd54 | ||
|
|
9b4079317b | ||
|
|
0f64332f93 | ||
|
|
fd62edbcab | ||
|
|
46abb9ae3f | ||
|
|
b5361ef89f | ||
|
|
bf808f57fe | ||
|
|
648589ed16 | ||
|
|
28ec0e310d | ||
|
|
956d9e96f2 | ||
|
|
266ba03bb7 | ||
|
|
04c54840f4 | ||
|
|
db33200468 | ||
|
|
d7fbcf89bf | ||
|
|
a8b1bc735a | ||
|
|
ba8c640d6e | ||
|
|
d88d2780f4 | ||
|
|
b0bbf95b03 | ||
|
|
7ca150bf07 | ||
|
|
9c7aa02db8 | ||
|
|
6f444ed4b5 | ||
|
|
28119bf1bf | ||
|
|
7fdb5b594d | ||
|
|
6013fceb10 | ||
|
|
2996c7c8e2 | ||
|
|
d1c3078698 | ||
|
|
3e5f81bf2a | ||
|
|
772d045166 | ||
|
|
1a064a4666 | ||
|
|
e81c89dcae | ||
|
|
1f36139e99 | ||
|
|
6f77244af3 | ||
|
|
05351ce3e4 | ||
|
|
a79aff4778 | ||
|
|
86d6f88787 | ||
|
|
5fbeaee0b0 | ||
|
|
fed4dfd410 | ||
|
|
0d3b8bdb22 | ||
|
|
3c4f56f3bf | ||
|
|
d3c00584a2 | ||
|
|
ce7fa65595 | ||
|
|
9c0a0ad220 | ||
|
|
22d0ef36b8 | ||
|
|
829c5f493c | ||
|
|
2d8eb8e205 | ||
|
|
52a3927585 | ||
|
|
b39f6c96bd | ||
|
|
a96c2e0eac | ||
|
|
10b24c0269 | ||
|
|
4140883684 | ||
|
|
3352fae64c | ||
|
|
55cb0c52ee | ||
|
|
626d82614c | ||
|
|
d890068acb | ||
|
|
468ad39a94 | ||
|
|
5cbe06c2b0 | ||
|
|
3412ecfe7b | ||
|
|
adb16a334c | ||
|
|
377f0bda5d | ||
|
|
51ab853658 | ||
|
|
464a42258f | ||
|
|
9e9c50e6d8 | ||
|
|
945f726b65 | ||
|
|
7c44daf8f4 | ||
|
|
c57a9a8613 | ||
|
|
3c0429deee | ||
|
|
5d8f541e70 | ||
|
|
bd126b866c | ||
|
|
036a1991b8 | ||
|
|
6e3b22c624 | ||
|
|
82fbedbf41 | ||
|
|
0929b86d62 | ||
|
|
65fc1cf4a6 | ||
|
|
6f753799fd | ||
|
|
4d72afebe6 | ||
|
|
b1643e6036 | ||
|
|
362d8cb831 | ||
|
|
b203c95dd1 | ||
|
|
8d4672964c | ||
|
|
6e5e5822aa | ||
|
|
5fb0bf2575 | ||
|
|
9af2045dc1 | ||
|
|
9624cc3798 | ||
|
|
3541228c1f | ||
|
|
cc95361fdc | ||
|
|
9b1d1ad0a5 | ||
|
|
7e467f1466 | ||
|
|
d3e28e3e2c | ||
|
|
1a1a0fbfbe | ||
|
|
91305c2c84 | ||
|
|
48dd6d388d | ||
|
|
64710a6a04 | ||
|
|
c83ff03d66 | ||
|
|
73b47a78aa | ||
|
|
493b25f23e | ||
|
|
004f1f625b | ||
|
|
fc20f658e6 | ||
|
|
8e988cc926 | ||
|
|
8c240b59f6 | ||
|
|
f847e30a3c | ||
|
|
7050ae4ba1 | ||
|
|
3e64d8439d | ||
|
|
435c955acd | ||
|
|
c656a95a84 | ||
|
|
27ad8472c1 | ||
|
|
3fcd4a61aa | ||
|
|
c6d93d1a28 | ||
|
|
b0c82dcb5b | ||
|
|
7d4058f49d | ||
|
|
31fc8fafec | ||
|
|
313cee9a3f | ||
|
|
066d9d48a4 | ||
|
|
16de4a0d2e | ||
|
|
621fcb598e | ||
|
|
b2a6a4000b | ||
|
|
f5c939fb10 | ||
|
|
e508306395 | ||
|
|
cc6b500029 | ||
|
|
887b1b5dcd | ||
|
|
62e333b235 | ||
|
|
d859700497 | ||
|
|
f129b458ad | ||
|
|
bf68ad7cf5 | ||
|
|
6d9bdbb628 | ||
|
|
6782c2d3d1 | ||
|
|
b6f71ca1db | ||
|
|
92df77f228 | ||
|
|
caeea41867 | ||
|
|
bc17dd1a0f | ||
|
|
2a3175470b | ||
|
|
8ca6bbfb78 | ||
|
|
9777762052 | ||
|
|
da01f0ab7f | ||
|
|
1cd81208c0 | ||
|
|
2394075d94 | ||
|
|
de7b9877f9 | ||
|
|
ee27c15c2c | ||
|
|
076f8bd77b | ||
|
|
288363b3a6 | ||
|
|
48256d6e9e | ||
|
|
c007302564 | ||
|
|
0a9f7147f3 | ||
|
|
46ba36511a | ||
|
|
170f0693c6 | ||
|
|
d900509fbc | ||
|
|
8018c27dcd | ||
|
|
8df5ac9d3f | ||
|
|
c3196f47ef | ||
|
|
81a49d4e3c | ||
|
|
4f32c36db8 | ||
|
|
5db0bb3368 | ||
|
|
f3d38d84c9 | ||
|
|
f8bb6bbcb4 | ||
|
|
e0d5b9dce1 | ||
|
|
94e4b30125 | ||
|
|
2a067e7f6b | ||
|
|
97ab07e05c | ||
|
|
b8d39845cf | ||
|
|
cbea36a151 | ||
|
|
49dea6d6bd | ||
|
|
7d4c6c7086 | ||
|
|
fefd3d78f3 | ||
|
|
f41a77c46d | ||
|
|
6a475d8288 | ||
|
|
c629b94333 | ||
|
|
5423f4e06c | ||
|
|
88f8bbe21e | ||
|
|
eda4862f0d | ||
|
|
7ca752cd32 | ||
|
|
c5ffebc498 | ||
|
|
baa12aa5b3 | ||
|
|
ccf3522ada | ||
|
|
d14cf5aa94 | ||
|
|
810429b54f | ||
|
|
915427c964 | ||
|
|
347484baaf | ||
|
|
bb6913a56a | ||
|
|
6cdaf8c600 | ||
|
|
c370eb4a88 | ||
|
|
8dc4492ba3 | ||
|
|
901cc536ef | ||
|
|
41c2732e4f | ||
|
|
2d12a363db | ||
|
|
28455f0056 | ||
|
|
5125126aec | ||
|
|
f1ca1ee3c0 | ||
|
|
dffb6c2f06 | ||
|
|
7f214ffbb0 | ||
|
|
e049eac38a | ||
|
|
338445d175 | ||
|
|
8eb492d356 | ||
|
|
a5341d766e | ||
|
|
c78142b235 | ||
|
|
9ebaa2b962 | ||
|
|
79498580b1 | ||
|
|
41901aed97 | ||
|
|
2729a513ab | ||
|
|
af046e7dbd | ||
|
|
0caf534b65 | ||
|
|
f650222e94 | ||
|
|
5c40378805 | ||
|
|
780b7e3628 | ||
|
|
83ae3c7714 | ||
|
|
25ebb9adb8 | ||
|
|
7f6a0e7ddc | ||
|
|
f62e6793c5 | ||
|
|
babd57ecde | ||
|
|
de196810a2 | ||
|
|
82fe55471d | ||
|
|
83726086a9 | ||
|
|
60d90c4533 | ||
|
|
9145945efa | ||
|
|
7b09fbe049 | ||
|
|
a90bab5455 | ||
|
|
3d9cc8a056 | ||
|
|
ff885ef215 | ||
|
|
eb4286b560 | ||
|
|
9c90de0f6f | ||
|
|
d7a1cef046 | ||
|
|
6a359e2124 | ||
|
|
ca75dcd64d | ||
|
|
aba05ba5ce | ||
|
|
99882a675b | ||
|
|
9c9347df23 | ||
|
|
b66856c53f | ||
|
|
285069703c | ||
|
|
d91ab5480f | ||
|
|
e3b74ece74 | ||
|
|
66557241f3 | ||
|
|
3c09c3e520 | ||
|
|
781077e799 | ||
|
|
22946b5e51 | ||
|
|
c0307cbcb0 | ||
|
|
6b568ab2fb | ||
|
|
67bad9a689 | ||
|
|
559a790286 | ||
|
|
08e5bd728b | ||
|
|
0cfeeba2e2 | ||
|
|
5007624ba5 | ||
|
|
bba4677915 | ||
|
|
ac969cdb26 | ||
|
|
1c3eff0ee5 | ||
|
|
cba9546a4d | ||
|
|
ceae60cf13 | ||
|
|
3de1607cea | ||
|
|
029b2f3139 | ||
|
|
ab6c97bfef | ||
|
|
a61b15e861 | ||
|
|
8c7aa07c70 | ||
|
|
6a402b13fa | ||
|
|
c30b570e16 | ||
|
|
9b25818a50 | ||
|
|
5311e44660 | ||
|
|
55dafcbecb | ||
|
|
178958c165 | ||
|
|
d7cf7e2eb4 | ||
|
|
fce8c16d52 | ||
|
|
b690868bb1 | ||
|
|
dec88a368b | ||
|
|
17394d8c1c | ||
|
|
a7da7cd32e | ||
|
|
f37dc9c776 | ||
|
|
d6658dbb2e | ||
|
|
05531b2684 | ||
|
|
8b1fb39ce1 | ||
|
|
da46d4ca0e | ||
|
|
047141eb34 | ||
|
|
cb1053850d | ||
|
|
7652331e8c | ||
|
|
12bf3795ea | ||
|
|
7f45184d90 | ||
|
|
829763af2c | ||
|
|
75806ee666 | ||
|
|
91ec29db03 | ||
|
|
d191b12801 | ||
|
|
e0d6456618 | ||
|
|
70eda2ee06 | ||
|
|
7c4eb3eddd | ||
|
|
91b4cadb1b | ||
|
|
0b1a78c028 | ||
|
|
b8e1b28958 | ||
|
|
a23f327461 |
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -23,4 +23,4 @@ jobs:
|
||||
run: |
|
||||
pip install -U adafruit-nrfutil
|
||||
- name: Build
|
||||
run: platformio run -e tbeam -e heltec -e nrf52840dk -e rak815
|
||||
run: platformio run -e tbeam -e heltec -e lora-relay-v1 -e linux
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -17,3 +17,5 @@ Thumbs.db
|
||||
.cproject
|
||||
.idea/*
|
||||
.vagrant
|
||||
|
||||
flash.uf2
|
||||
|
||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -48,13 +48,17 @@
|
||||
"optional": "cpp",
|
||||
"string_view": "cpp",
|
||||
"cassert": "cpp",
|
||||
"iterator": "cpp"
|
||||
"iterator": "cpp",
|
||||
"shared_mutex": "cpp"
|
||||
},
|
||||
"cSpell.words": [
|
||||
"Blox",
|
||||
"EINK",
|
||||
"HFSR",
|
||||
"Meshtastic",
|
||||
"NEMAGPS",
|
||||
"NMEAGPS",
|
||||
"RDEF",
|
||||
"Ublox",
|
||||
"bkpt",
|
||||
"cfsr",
|
||||
|
||||
15
README.md
15
README.md
@@ -25,11 +25,16 @@ We currently support three models of radios.
|
||||
- TTGO T-Beam (usually the recommended choice)
|
||||
- [T-Beam V1.1 w/ NEO-6M - special Meshtastic version](https://www.aliexpress.com/item/4001178678568.html) (Includes built-in OLED display and they have **preinstalled** the meshtastic software)
|
||||
- [T-Beam V1.1 w/ NEO-M8N](https://www.aliexpress.com/item/33047631119.html) (slightly better GPS)
|
||||
- [T-Beam V1.1 w/ NEO-M8N /w SX1262](https://www.aliexpress.com/item/4001287221970.html) (slightly better GPS + LoRa)
|
||||
- board labels "TTGO T22_V1.1 20191212"
|
||||
- [T-Beam V0.7 w/ NEO-6M](https://www.aliexpress.com/item/4000574335430.html) (will work but **you must use the tbeam0.7 firmware ** - but the T-Beam V1.0 or later are better!)
|
||||
- board labels "TTGO T22_V07 20180711"
|
||||
- 3D printable cases
|
||||
- [T-Beam V0](https://www.thingiverse.com/thing:3773717)
|
||||
- [T-Beam V1](https://www.thingiverse.com/thing:3830711)
|
||||
- [T-Beam V1 (SMA-antenna)](https://www.thingiverse.com/thing:3830711)
|
||||
- [T-Beam V1 (IPEX-antenna)](https://www.thingiverse.com/thing:4587297)
|
||||
- Laser-cut cases
|
||||
- [T-Beam V1](https://www.thingiverse.com/thing:4552771)
|
||||
|
||||
- [TTGO LORA32](https://www.aliexpress.com/item/4000211331316.html) - No GPS
|
||||
- version 2.1
|
||||
@@ -98,10 +103,10 @@ Hard resetting via RTS pin...
|
||||
```
|
||||
|
||||
5. cd into the directory where the release zip file was expanded.
|
||||
6. Install the correct firmware for your board with `device-install.sh firmware-_board_-_country_.bin`.
|
||||
- Example: `./device-install.sh firmware-HELTEC-US-0.0.3.bin`.
|
||||
7. To update run `device-update.sh firmware-_board_-_country_.bin`
|
||||
- Example: `./device-update.sh firmware-HELTEC-US-0.0.3.bin`.
|
||||
6. Install the correct firmware for your board with `device-install.sh -f firmware-_board_-_country_.bin`.
|
||||
- Example: `./device-install.sh -f firmware-HELTEC-US-0.0.3.bin`.
|
||||
7. To update run `device-update.sh -f firmware-_board_-_country_.bin`
|
||||
- Example: `./device-update.sh -f 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_-_version_.bin`. This will be faster, also all of your current preferences will be preserved.
|
||||
|
||||
|
||||
@@ -4,12 +4,15 @@ set -e
|
||||
|
||||
source bin/version.sh
|
||||
|
||||
COUNTRIES="US EU433 EU865 CN JP"
|
||||
COUNTRIES="US EU433 EU865 CN JP ANZ KR"
|
||||
#COUNTRIES=US
|
||||
#COUNTRIES=CN
|
||||
|
||||
BOARDS="tlora-v2 tlora-v1 tlora-v2-1-1.6 tbeam heltec tbeam0.7"
|
||||
#BOARDS=tbeam
|
||||
BOARDS_ESP32="tlora-v2 tlora-v1 tlora-v2-1-1.6 tbeam heltec tbeam0.7"
|
||||
# BOARDS_ESP32=tbeam
|
||||
|
||||
# FIXME note nrf52840dk build is for some reason only generating a BIN file but not a HEX file nrf52840dk-geeksville is fine
|
||||
BOARDS_NRF52="lora-relay-v1"
|
||||
|
||||
OUTDIR=release/latest
|
||||
|
||||
@@ -18,25 +21,61 @@ ARCHIVEDIR=release/archive
|
||||
|
||||
rm -f $OUTDIR/firmware*
|
||||
|
||||
mkdir -p $OUTDIR/bins $OUTDIR/elfs
|
||||
rm -f $OUTDIR/bins/*
|
||||
mkdir -p $OUTDIR/bins
|
||||
rm -r $OUTDIR/bins/*
|
||||
mkdir -p $OUTDIR/bins/universal $OUTDIR/elfs/universal
|
||||
|
||||
# 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
|
||||
function do_build() {
|
||||
BOARD=$1
|
||||
COUNTRY=$2
|
||||
isNrf=$3
|
||||
|
||||
echo "Building $COUNTRY for $BOARD with $PLATFORMIO_BUILD_FLAGS"
|
||||
rm -f .pio/build/$BOARD/firmware.*
|
||||
|
||||
# The shell vars the build tool expects to find
|
||||
export HW_VERSION="1.0-$COUNTRY"
|
||||
export APP_VERSION=$VERSION
|
||||
export COUNTRY
|
||||
|
||||
pio run --jobs 4 --environment $ENV_NAME # -v
|
||||
cp $SRCBIN $OUTDIR/bins/firmware-$ENV_NAME-$COUNTRY-$VERSION.bin
|
||||
cp $SRCELF $OUTDIR/elfs/firmware-$ENV_NAME-$COUNTRY-$VERSION.elf
|
||||
# Are we building a universal/regionless rom?
|
||||
if [ "x$COUNTRY" != "x" ]
|
||||
then
|
||||
export HW_VERSION="1.0-$COUNTRY"
|
||||
export COUNTRY
|
||||
basename=firmware-$BOARD-$COUNTRY-$VERSION
|
||||
else
|
||||
export HW_VERSION="1.0"
|
||||
unset COUNTRY
|
||||
basename=universal/firmware-$BOARD-$VERSION
|
||||
fi
|
||||
|
||||
pio run --jobs 4 --environment $BOARD # -v
|
||||
SRCELF=.pio/build/$BOARD/firmware.elf
|
||||
cp $SRCELF $OUTDIR/elfs/$basename.elf
|
||||
|
||||
if [ "$isNrf" = "false" ]
|
||||
then
|
||||
echo "Copying ESP32 bin file"
|
||||
SRCBIN=.pio/build/$BOARD/firmware.bin
|
||||
cp $SRCBIN $OUTDIR/bins/$basename.bin
|
||||
else
|
||||
echo "Generating NRF52 uf2 file"
|
||||
SRCHEX=.pio/build/$BOARD/firmware.hex
|
||||
bin/uf2conv.py $SRCHEX -c -o $OUTDIR/bins/$basename.uf2 -f 0xADA52840
|
||||
fi
|
||||
}
|
||||
|
||||
function do_boards() {
|
||||
declare boards=$1
|
||||
declare isNrf=$2
|
||||
for board in $boards; do
|
||||
for country in $COUNTRIES; do
|
||||
do_build $board $country "$isNrf"
|
||||
done
|
||||
|
||||
# Build universal
|
||||
do_build $board "" "$isNrf"
|
||||
done
|
||||
}
|
||||
|
||||
# Make sure our submodules are current
|
||||
@@ -45,14 +84,16 @@ git submodule update
|
||||
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
|
||||
platformio lib update
|
||||
|
||||
for COUNTRY in $COUNTRIES; do
|
||||
for BOARD in $BOARDS; do
|
||||
do_build $BOARD
|
||||
done
|
||||
done
|
||||
do_boards "$BOARDS_ESP32" "false"
|
||||
do_boards "$BOARDS_NRF52" "true"
|
||||
|
||||
# keep the bins in archive also
|
||||
cp $OUTDIR/bins/firmware* $OUTDIR/elfs/firmware* $ARCHIVEDIR
|
||||
cp $OUTDIR/bins/firmware* $OUTDIR/elfs/firmware* $OUTDIR/bins/universal/firmware* $OUTDIR/elfs/universal/firmware* $ARCHIVEDIR
|
||||
|
||||
echo Updating android bins $OUTDIR/forandroid
|
||||
rm -rf $OUTDIR/forandroid
|
||||
mkdir -p $OUTDIR/forandroid
|
||||
cp -a $OUTDIR/bins/universal/*.bin $OUTDIR/forandroid/
|
||||
|
||||
cat >$OUTDIR/curfirmwareversion.xml <<XML
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
@@ -66,6 +107,7 @@ Generated by bin/buildall.sh -->
|
||||
</resources>
|
||||
XML
|
||||
|
||||
echo Generating $ARCHIVEDIR/firmware-$VERSION.zip
|
||||
rm -f $ARCHIVEDIR/firmware-$VERSION.zip
|
||||
zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/bins/firmware-*-$VERSION.* images/system-info.bin bin/device-install.sh bin/device-update.sh
|
||||
|
||||
|
||||
@@ -1,11 +1,45 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
# Usage info
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Usage: ${0##*/} [-h] [-p ESPTOOL_PORT] [-f FILENAME]
|
||||
Flash image file to device, but first erasing and writing system information"
|
||||
|
||||
FILENAME=$1
|
||||
-h Display this help and exit
|
||||
-p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerrous).
|
||||
-f FILENAME The .bin file to flash. Custom to your device type and region.
|
||||
EOF
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
while getopts ":h:p:f:" opt; do
|
||||
case "${opt}" in
|
||||
h)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
p) export ESPTOOL_PORT=${OPTARG}
|
||||
;;
|
||||
f) FILENAME=${OPTARG}
|
||||
;;
|
||||
*)
|
||||
echo "Invalid flag."
|
||||
show_help >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift "$((OPTIND-1))"
|
||||
|
||||
if [ -f "${FILENAME}" ]; then
|
||||
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}
|
||||
else
|
||||
echo "Invalid file: ${FILENAME}"
|
||||
show_help
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -1,8 +1,43 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
# Usage info
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Usage: ${0##*/} [-h] [-p ESPTOOL_PORT] -f FILENAME
|
||||
Flash image file to device, leave existing system intact."
|
||||
|
||||
FILENAME=$1
|
||||
-h Display this help and exit
|
||||
-p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerrous).
|
||||
-f FILENAME The .bin file to flash. Custom to your device type and region.
|
||||
EOF
|
||||
}
|
||||
|
||||
echo "Trying to update $FILENAME"
|
||||
esptool.py --baud 921600 write_flash 0x10000 $FILENAME
|
||||
|
||||
while getopts ":h:p:f:" opt; do
|
||||
case "${opt}" in
|
||||
h)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
p) export ESPTOOL_PORT=${OPTARG}
|
||||
;;
|
||||
f) FILENAME=${OPTARG}
|
||||
;;
|
||||
*)
|
||||
echo "Invalid flag."
|
||||
show_help >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift "$((OPTIND-1))"
|
||||
|
||||
if [ -f "${FILENAME}" ]; then
|
||||
echo "Trying to flash update ${FILENAME}."
|
||||
esptool.py --baud 921600 write_flash 0x10000 ${FILENAME}
|
||||
else
|
||||
echo "Invalid file: ${FILENAME}"
|
||||
show_help
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
35
bin/install-bootloader.sh
Executable file
35
bin/install-bootloader.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
# You probably don't want to use this script, it programs a custom bootloader build onto a nrf52 board
|
||||
|
||||
set -e
|
||||
|
||||
# dependencies
|
||||
# apt install srecord
|
||||
|
||||
BOOTDIR=/home/kevinh/development/meshtastic/Adafruit_nRF52_Bootloader
|
||||
BOARD=othernet_ppr1
|
||||
BOOTVER=0.3.2
|
||||
BOOTNUM=128
|
||||
BOOTSHA=gc01b9ea
|
||||
SDCODE=s113
|
||||
SDVER=7.2.0
|
||||
PROJ=ppr1
|
||||
|
||||
# FIXME for nRF52840 use 0xff000, for nRF52833 use 0x7f000
|
||||
BOOTSET=0x7f000
|
||||
|
||||
nrfjprog --eraseall -f nrf52
|
||||
|
||||
# this generates an intel hex file that can be programmed into a NRF52 to tell the adafruit bootloader that the current app image is valid
|
||||
# Bootloader settings are at BOOTLOADER_SETTINGS (rw) : ORIGIN = 0xFF000, LENGTH = 0x1000
|
||||
# first 4 bytes should be 0x01 to indicate valid app image
|
||||
# second 4 bytes should be 0x00 to indicate no CRC required for image
|
||||
echo "01 00 00 00 00 00 00 00" | xxd -r -p - >/tmp/bootconf.bin
|
||||
srec_cat /tmp/bootconf.bin -binary -offset $BOOTSET -output /tmp/bootconf.hex -intel
|
||||
|
||||
echo Generating merged hex file from .pio/build/$PROJ/firmware.hex
|
||||
mergehex -o ${BOARD}_full.hex -m $BOOTDIR/_build/build-$BOARD/${BOARD}_bootloader-$BOOTVER-$BOOTNUM-$BOOTSHA-dirty_${SDCODE}_$SDVER.hex .pio/build/$PROJ/firmware.hex /tmp/bootconf.hex
|
||||
|
||||
echo Telling bootloader app region is valid and telling CPU to run
|
||||
nrfjprog --program ${BOARD}_full.hex -f nrf52 --reset
|
||||
|
||||
# nrfjprog --readuicr /tmp/uicr.hex; objdump -s /tmp/uicr.hex | less
|
||||
22
bin/install-eink.sh
Executable file
22
bin/install-eink.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
# You probably don't want to use this script, it programs a custom bootloader build onto a nrf52 board
|
||||
|
||||
set -e
|
||||
|
||||
BOOTDIR=/home/kevinh/development/meshtastic/Adafruit_nRF52_Bootloader
|
||||
|
||||
nrfjprog --eraseall -f nrf52
|
||||
|
||||
# this generates an intel hex file that can be programmed into a NRF52 to tell the adafruit bootloader that the current app image is valid
|
||||
# Bootloader settings are at BOOTLOADER_SETTINGS (rw) : ORIGIN = 0xFF000, LENGTH = 0x1000
|
||||
# first 4 bytes should be 0x01 to indicate valid app image
|
||||
# second 4 bytes should be 0x00 to indicate no CRC required for image
|
||||
echo "01 00 00 00 00 00 00 00" | xxd -r -p - >/tmp/bootconf.bin
|
||||
srec_cat /tmp/bootconf.bin -binary -offset 0xff000 -output /tmp/bootconf.hex -intel
|
||||
|
||||
echo Generating merged hex file
|
||||
mergehex -m $BOOTDIR/_build/build-ttgo_eink/ttgo_eink_bootloader-0.3.2-125-gf38f8f4-dirty_s140_6.1.1.hex .pio/build/eink/firmware.hex /tmp/bootconf.hex -o ttgo_eink_full.hex
|
||||
|
||||
echo Telling bootloader app region is valid and telling CPU to run
|
||||
nrfjprog --program ttgo_eink_full.hex -f nrf52 --reset
|
||||
|
||||
# nrfjprog --readuicr /tmp/uicr.hex; objdump -s /tmp/uicr.hex | less
|
||||
@@ -1,3 +1,3 @@
|
||||
|
||||
|
||||
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52840_XXAA
|
||||
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52832_XXAA
|
||||
|
||||
3
bin/nrf52833-gdbserver.sh
Executable file
3
bin/nrf52833-gdbserver.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52833_XXAA
|
||||
@@ -1,3 +1,3 @@
|
||||
|
||||
|
||||
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52832_XXAA
|
||||
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52840_XXAA
|
||||
|
||||
6
bin/qspi-flash-test.sh
Executable file
6
bin/qspi-flash-test.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
# You probably don't need this - it is a basic test of the serial flash on the TTGO eink board
|
||||
|
||||
nrfjprog -qspiini nrf52/ttgo_eink_qpsi.ini --qspieraseall
|
||||
nrfjprog --qspiini nrf52/ttgo_eink_qpsi.ini --memwr 0x12000000 --val 0xdeadbeef --verify
|
||||
nrfjprog --qspiini nrf52/ttgo_eink_qpsi.ini --readqspi spi.hex
|
||||
objdump -s spi.hex | less
|
||||
314
bin/uf2conv.py
Executable file
314
bin/uf2conv.py
Executable file
@@ -0,0 +1,314 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import struct
|
||||
import subprocess
|
||||
import re
|
||||
import os
|
||||
import os.path
|
||||
import argparse
|
||||
|
||||
|
||||
UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
|
||||
UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
|
||||
UF2_MAGIC_END = 0x0AB16F30 # Ditto
|
||||
|
||||
families = {
|
||||
'SAMD21': 0x68ed2b88,
|
||||
'SAML21': 0x1851780a,
|
||||
'SAMD51': 0x55114460,
|
||||
'NRF52': 0x1b57745f,
|
||||
'STM32F0': 0x647824b6,
|
||||
'STM32F1': 0x5ee21072,
|
||||
'STM32F2': 0x5d1a0a2e,
|
||||
'STM32F3': 0x6b846188,
|
||||
'STM32F4': 0x57755a57,
|
||||
'STM32F7': 0x53b80f00,
|
||||
'STM32G0': 0x300f5633,
|
||||
'STM32G4': 0x4c71240a,
|
||||
'STM32H7': 0x6db66082,
|
||||
'STM32L0': 0x202e3a91,
|
||||
'STM32L1': 0x1e1f432d,
|
||||
'STM32L4': 0x00ff6919,
|
||||
'STM32L5': 0x04240bdf,
|
||||
'STM32WB': 0x70d16653,
|
||||
'STM32WL': 0x21460ff0,
|
||||
'ATMEGA32': 0x16573617,
|
||||
'MIMXRT10XX': 0x4FB2D5BD
|
||||
}
|
||||
|
||||
INFO_FILE = "/INFO_UF2.TXT"
|
||||
|
||||
appstartaddr = 0x2000
|
||||
familyid = 0x0
|
||||
|
||||
|
||||
def is_uf2(buf):
|
||||
w = struct.unpack("<II", buf[0:8])
|
||||
return w[0] == UF2_MAGIC_START0 and w[1] == UF2_MAGIC_START1
|
||||
|
||||
def is_hex(buf):
|
||||
try:
|
||||
w = buf[0:30].decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
return False
|
||||
if w[0] == ':' and re.match(b"^[:0-9a-fA-F\r\n]+$", buf):
|
||||
return True
|
||||
return False
|
||||
|
||||
def convert_from_uf2(buf):
|
||||
global appstartaddr
|
||||
numblocks = len(buf) // 512
|
||||
curraddr = None
|
||||
outp = b""
|
||||
for blockno in range(numblocks):
|
||||
ptr = blockno * 512
|
||||
block = buf[ptr:ptr + 512]
|
||||
hd = struct.unpack(b"<IIIIIIII", block[0:32])
|
||||
if hd[0] != UF2_MAGIC_START0 or hd[1] != UF2_MAGIC_START1:
|
||||
print("Skipping block at " + ptr + "; bad magic")
|
||||
continue
|
||||
if hd[2] & 1:
|
||||
# NO-flash flag set; skip block
|
||||
continue
|
||||
datalen = hd[4]
|
||||
if datalen > 476:
|
||||
assert False, "Invalid UF2 data size at " + ptr
|
||||
newaddr = hd[3]
|
||||
if curraddr == None:
|
||||
appstartaddr = newaddr
|
||||
curraddr = newaddr
|
||||
padding = newaddr - curraddr
|
||||
if padding < 0:
|
||||
assert False, "Block out of order at " + ptr
|
||||
if padding > 10*1024*1024:
|
||||
assert False, "More than 10M of padding needed at " + ptr
|
||||
if padding % 4 != 0:
|
||||
assert False, "Non-word padding size at " + ptr
|
||||
while padding > 0:
|
||||
padding -= 4
|
||||
outp += b"\x00\x00\x00\x00"
|
||||
outp += block[32 : 32 + datalen]
|
||||
curraddr = newaddr + datalen
|
||||
return outp
|
||||
|
||||
def convert_to_carray(file_content):
|
||||
outp = "const unsigned char bindata[] __attribute__((aligned(16))) = {"
|
||||
for i in range(len(file_content)):
|
||||
if i % 16 == 0:
|
||||
outp += "\n"
|
||||
outp += "0x%02x, " % ord(file_content[i])
|
||||
outp += "\n};\n"
|
||||
return outp
|
||||
|
||||
def convert_to_uf2(file_content):
|
||||
global familyid
|
||||
datapadding = b""
|
||||
while len(datapadding) < 512 - 256 - 32 - 4:
|
||||
datapadding += b"\x00\x00\x00\x00"
|
||||
numblocks = (len(file_content) + 255) // 256
|
||||
outp = b""
|
||||
for blockno in range(numblocks):
|
||||
ptr = 256 * blockno
|
||||
chunk = file_content[ptr:ptr + 256]
|
||||
flags = 0x0
|
||||
if familyid:
|
||||
flags |= 0x2000
|
||||
hd = struct.pack(b"<IIIIIIII",
|
||||
UF2_MAGIC_START0, UF2_MAGIC_START1,
|
||||
flags, ptr + appstartaddr, 256, blockno, numblocks, familyid)
|
||||
while len(chunk) < 256:
|
||||
chunk += b"\x00"
|
||||
block = hd + chunk + datapadding + struct.pack(b"<I", UF2_MAGIC_END)
|
||||
assert len(block) == 512
|
||||
outp += block
|
||||
return outp
|
||||
|
||||
class Block:
|
||||
def __init__(self, addr):
|
||||
self.addr = addr
|
||||
self.bytes = bytearray(256)
|
||||
|
||||
def encode(self, blockno, numblocks):
|
||||
global familyid
|
||||
flags = 0x0
|
||||
if familyid:
|
||||
flags |= 0x2000
|
||||
hd = struct.pack("<IIIIIIII",
|
||||
UF2_MAGIC_START0, UF2_MAGIC_START1,
|
||||
flags, self.addr, 256, blockno, numblocks, familyid)
|
||||
hd += self.bytes[0:256]
|
||||
while len(hd) < 512 - 4:
|
||||
hd += b"\x00"
|
||||
hd += struct.pack("<I", UF2_MAGIC_END)
|
||||
return hd
|
||||
|
||||
def convert_from_hex_to_uf2(buf):
|
||||
global appstartaddr
|
||||
appstartaddr = None
|
||||
upper = 0
|
||||
currblock = None
|
||||
blocks = []
|
||||
for line in buf.split('\n'):
|
||||
if line[0] != ":":
|
||||
continue
|
||||
i = 1
|
||||
rec = []
|
||||
while i < len(line) - 1:
|
||||
rec.append(int(line[i:i+2], 16))
|
||||
i += 2
|
||||
tp = rec[3]
|
||||
if tp == 4:
|
||||
upper = ((rec[4] << 8) | rec[5]) << 16
|
||||
elif tp == 2:
|
||||
upper = ((rec[4] << 8) | rec[5]) << 4
|
||||
assert (upper & 0xffff) == 0
|
||||
elif tp == 1:
|
||||
break
|
||||
elif tp == 0:
|
||||
addr = upper | (rec[1] << 8) | rec[2]
|
||||
if appstartaddr == None:
|
||||
appstartaddr = addr
|
||||
i = 4
|
||||
while i < len(rec) - 1:
|
||||
if not currblock or currblock.addr & ~0xff != addr & ~0xff:
|
||||
currblock = Block(addr & ~0xff)
|
||||
blocks.append(currblock)
|
||||
currblock.bytes[addr & 0xff] = rec[i]
|
||||
addr += 1
|
||||
i += 1
|
||||
numblocks = len(blocks)
|
||||
resfile = b""
|
||||
for i in range(0, numblocks):
|
||||
resfile += blocks[i].encode(i, numblocks)
|
||||
return resfile
|
||||
|
||||
def to_str(b):
|
||||
return b.decode("utf-8")
|
||||
|
||||
def get_drives():
|
||||
drives = []
|
||||
if sys.platform == "win32":
|
||||
r = subprocess.check_output(["wmic", "PATH", "Win32_LogicalDisk",
|
||||
"get", "DeviceID,", "VolumeName,",
|
||||
"FileSystem,", "DriveType"])
|
||||
for line in to_str(r).split('\n'):
|
||||
words = re.split('\s+', line)
|
||||
if len(words) >= 3 and words[1] == "2" and words[2] == "FAT":
|
||||
drives.append(words[0])
|
||||
else:
|
||||
rootpath = "/media"
|
||||
if sys.platform == "darwin":
|
||||
rootpath = "/Volumes"
|
||||
elif sys.platform == "linux":
|
||||
tmp = rootpath + "/" + os.environ["USER"]
|
||||
if os.path.isdir(tmp):
|
||||
rootpath = tmp
|
||||
for d in os.listdir(rootpath):
|
||||
drives.append(os.path.join(rootpath, d))
|
||||
|
||||
|
||||
def has_info(d):
|
||||
try:
|
||||
return os.path.isfile(d + INFO_FILE)
|
||||
except:
|
||||
return False
|
||||
|
||||
return list(filter(has_info, drives))
|
||||
|
||||
|
||||
def board_id(path):
|
||||
with open(path + INFO_FILE, mode='r') as file:
|
||||
file_content = file.read()
|
||||
return re.search("Board-ID: ([^\r\n]*)", file_content).group(1)
|
||||
|
||||
|
||||
def list_drives():
|
||||
for d in get_drives():
|
||||
print(d, board_id(d))
|
||||
|
||||
|
||||
def write_file(name, buf):
|
||||
with open(name, "wb") as f:
|
||||
f.write(buf)
|
||||
print("Wrote %d bytes to %s" % (len(buf), name))
|
||||
|
||||
|
||||
def main():
|
||||
global appstartaddr, familyid
|
||||
def error(msg):
|
||||
print(msg)
|
||||
sys.exit(1)
|
||||
parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.')
|
||||
parser.add_argument('input', metavar='INPUT', type=str, nargs='?',
|
||||
help='input file (HEX, BIN or UF2)')
|
||||
parser.add_argument('-b' , '--base', dest='base', type=str,
|
||||
default="0x2000",
|
||||
help='set base address of application for BIN format (default: 0x2000)')
|
||||
parser.add_argument('-o' , '--output', metavar="FILE", dest='output', type=str,
|
||||
help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible')
|
||||
parser.add_argument('-d' , '--device', dest="device_path",
|
||||
help='select a device path to flash')
|
||||
parser.add_argument('-l' , '--list', action='store_true',
|
||||
help='list connected devices')
|
||||
parser.add_argument('-c' , '--convert', action='store_true',
|
||||
help='do not flash, just convert')
|
||||
parser.add_argument('-D' , '--deploy', action='store_true',
|
||||
help='just flash, do not convert')
|
||||
parser.add_argument('-f' , '--family', dest='family', type=str,
|
||||
default="0x0",
|
||||
help='specify familyID - number or name (default: 0x0)')
|
||||
parser.add_argument('-C' , '--carray', action='store_true',
|
||||
help='convert binary file to a C array, not UF2')
|
||||
args = parser.parse_args()
|
||||
appstartaddr = int(args.base, 0)
|
||||
|
||||
if args.family.upper() in families:
|
||||
familyid = families[args.family.upper()]
|
||||
else:
|
||||
try:
|
||||
familyid = int(args.family, 0)
|
||||
except ValueError:
|
||||
error("Family ID needs to be a number or one of: " + ", ".join(families.keys()))
|
||||
|
||||
if args.list:
|
||||
list_drives()
|
||||
else:
|
||||
if not args.input:
|
||||
error("Need input file")
|
||||
with open(args.input, mode='rb') as f:
|
||||
inpbuf = f.read()
|
||||
from_uf2 = is_uf2(inpbuf)
|
||||
ext = "uf2"
|
||||
if args.deploy:
|
||||
outbuf = inpbuf
|
||||
elif from_uf2:
|
||||
outbuf = convert_from_uf2(inpbuf)
|
||||
ext = "bin"
|
||||
elif is_hex(inpbuf):
|
||||
outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8"))
|
||||
elif args.carray:
|
||||
outbuf = convert_to_carray(inpbuf)
|
||||
ext = "h"
|
||||
else:
|
||||
outbuf = convert_to_uf2(inpbuf)
|
||||
print("Converting to %s, output size: %d, start address: 0x%x" %
|
||||
(ext, len(outbuf), appstartaddr))
|
||||
if args.convert or ext != "uf2":
|
||||
drives = []
|
||||
if args.output == None:
|
||||
args.output = "flash." + ext
|
||||
else:
|
||||
drives = get_drives()
|
||||
|
||||
if args.output:
|
||||
write_file(args.output, outbuf)
|
||||
else:
|
||||
if len(drives) == 0:
|
||||
error("No drive to deploy.")
|
||||
for d in drives:
|
||||
print("Flashing %s (%s)" % (d, board_id(d)))
|
||||
write_file(d + "/NEW.UF2", outbuf)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
4
bin/upload-to-bootloader.sh
Executable file
4
bin/upload-to-bootloader.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
|
||||
echo "Converting to uf2 for NRF52 Adafruit bootloader"
|
||||
bin/uf2conv.py .pio/build/lora-relay-v2/firmware.hex -f 0xADA52840
|
||||
# cp flash.uf2 /media/kevinh/FTH*BOOT/
|
||||
@@ -1,3 +1,3 @@
|
||||
|
||||
|
||||
export VERSION=0.9.1
|
||||
export VERSION=1.1.8
|
||||
2
bin/view-map.sh
Executable file
2
bin/view-map.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
echo using amap tool to display memory map
|
||||
amap .pio/build/output.map
|
||||
61
boards/eink.json
Normal file
61
boards/eink.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_TTGO_EINK -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
[
|
||||
"0x239A",
|
||||
"0x4405"
|
||||
]
|
||||
],
|
||||
"usb_product": "TTGO_eink",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "eink",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": [
|
||||
"bluetooth"
|
||||
],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"onboard_tools": [
|
||||
"jlink"
|
||||
],
|
||||
"svd_path": "nrf52840.svd"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
],
|
||||
"name": "TTGO eink (Adafruit BSP)",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"require_upload_port": true,
|
||||
"speed": 115200,
|
||||
"protocol": "jlink",
|
||||
"protocols": [
|
||||
"jlink",
|
||||
"nrfjprog",
|
||||
"stlink"
|
||||
]
|
||||
},
|
||||
"url": "FIXME",
|
||||
"vendor": "TTGO"
|
||||
}
|
||||
46
boards/lora-relay-v1.json
Normal file
46
boards/lora-relay-v1.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_LORA_RELAY_V1 -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [["0x239A", "0x4404"]],
|
||||
"usb_product": "LORA_RELAY",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "lora_relay_v1",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52840.svd"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Meshtastic Lora Relay V1 (Adafruit BSP)",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"require_upload_port": true,
|
||||
"speed": 115200,
|
||||
"protocol": "jlink",
|
||||
"protocols": ["jlink", "nrfjprog", "stlink"]
|
||||
},
|
||||
"url": "https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay",
|
||||
"vendor": "BigCorvus"
|
||||
}
|
||||
46
boards/lora-relay-v2.json
Normal file
46
boards/lora-relay-v2.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_LORA_RELAY_V2 -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [["0x239A", "0x4406"]],
|
||||
"usb_product": "LORA_RELAY",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "lora_relay_v2",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52840.svd"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Meshtastic Lora Relay V1 (Adafruit BSP)",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"require_upload_port": true,
|
||||
"speed": 115200,
|
||||
"protocol": "jlink",
|
||||
"protocols": ["jlink", "nrfjprog", "stlink"]
|
||||
},
|
||||
"url": "https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay",
|
||||
"vendor": "BigCorvus"
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
"ldscript": "nrf52840_s113_v7.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
@@ -16,9 +16,9 @@
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_flags": "-DS113",
|
||||
"sd_name": "s113",
|
||||
"sd_version": "7.2.0",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
|
||||
46
boards/ppr1.json
Normal file
46
boards/ppr1.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52833_s113_v7.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52833_PPR -DNRF52833_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [["0x239A", "0x4406"]],
|
||||
"usb_product": "PPR",
|
||||
"mcu": "nrf52833",
|
||||
"variant": "ppr",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS113",
|
||||
"sd_name": "s113",
|
||||
"sd_version": "7.2.0",
|
||||
"sd_fwid": "0x00b6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52833_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52833.svd"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Meshtastic PPR1 (Adafruit BSP)",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"require_upload_port": true,
|
||||
"speed": 115200,
|
||||
"protocol": "jlink",
|
||||
"protocols": ["jlink", "nrfjprog", "stlink"]
|
||||
},
|
||||
"url": "https://meshtastic.org/",
|
||||
"vendor": "Othernet"
|
||||
}
|
||||
277
data/style.css
Normal file
277
data/style.css
Normal file
@@ -0,0 +1,277 @@
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Lato Regular'), local('Lato-Regular'), url(./Google.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
|
||||
|
||||
*, *:before, *:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #C5DDEB;
|
||||
font: 14px/20px "Lato", Arial, sans-serif;
|
||||
padding: 40px 0;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns:
|
||||
1fr 4fr;
|
||||
grid-template-areas:
|
||||
"header header"
|
||||
"sidebar content";
|
||||
margin: 0 auto;
|
||||
width: 750px;
|
||||
background: #444753;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.top {grid-area: header;}
|
||||
.side {grid-area: sidebar;}
|
||||
.main {grid-area: content;}
|
||||
|
||||
.top {
|
||||
border-bottom: 2px solid white;
|
||||
}
|
||||
.top-text {
|
||||
font-weight: bold;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.side {
|
||||
width: 260px;
|
||||
float: left;
|
||||
}
|
||||
.side .side-header {
|
||||
padding: 20px;
|
||||
border-bottom: 2px solid white;
|
||||
}
|
||||
|
||||
.side .side-header .side-text {
|
||||
padding-left: 10px;
|
||||
margin-top: 6px;
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
|
||||
}
|
||||
|
||||
.channel-list ul {
|
||||
padding: 20px;
|
||||
height: 570px;
|
||||
list-style-type: none;
|
||||
}
|
||||
.channel-list ul li {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.channel-list .channel-name {
|
||||
font-size: 20px;
|
||||
margin-top: 8px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.channel-list .message-count {
|
||||
padding-left: 16px;
|
||||
color: #92959E;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
stroke-width: 0;
|
||||
stroke: currentColor;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.icon-map-marker {
|
||||
width: 0.5714285714285714em;
|
||||
}
|
||||
|
||||
.icon-circle {
|
||||
width: 0.8571428571428571em;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
/* width: 490px; */
|
||||
float: left;
|
||||
background: #F2F5F8;
|
||||
/* border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px; */
|
||||
color: #434651;
|
||||
}
|
||||
.content .content-header {
|
||||
flex-grow: 0;
|
||||
padding: 20px;
|
||||
border-bottom: 2px solid white;
|
||||
}
|
||||
|
||||
.content .content-header .content-from {
|
||||
padding-left: 10px;
|
||||
margin-top: 6px;
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
.content .content-header .content-from .content-from-highlight {
|
||||
font-weight: bold;
|
||||
}
|
||||
.content .content-header .content-num-messages {
|
||||
color: #92959E;
|
||||
}
|
||||
|
||||
.content .content-history {
|
||||
flex-grow: 1;
|
||||
padding: 20px 20px 20px;
|
||||
border-bottom: 2px solid white;
|
||||
overflow-y: scroll;
|
||||
height: 375px;
|
||||
}
|
||||
.content .content-history ul {
|
||||
list-style-type: none;
|
||||
padding-inline-start: 10px;
|
||||
}
|
||||
.content .content-history .message-data {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.content .content-history .message-data-time {
|
||||
color: #a8aab1;
|
||||
padding-left: 6px;
|
||||
}
|
||||
.content .content-history .message {
|
||||
color: white;
|
||||
padding: 8px 10px;
|
||||
line-height: 20px;
|
||||
font-size: 14px;
|
||||
border-radius: 7px;
|
||||
margin-bottom: 30px;
|
||||
width: 90%;
|
||||
position: relative;
|
||||
}
|
||||
.content .content-history .message:after {
|
||||
bottom: 100%;
|
||||
left: 7%;
|
||||
border: solid transparent;
|
||||
content: " ";
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
border-bottom-color: #86BB71;
|
||||
border-width: 10px;
|
||||
margin-left: -10px;
|
||||
}
|
||||
.content .content-history .my-message {
|
||||
background: #86BB71;
|
||||
}
|
||||
.content .content-history .other-message {
|
||||
background: #94C2ED;
|
||||
}
|
||||
.content .content-history .other-message:after {
|
||||
border-bottom-color: #94C2ED;
|
||||
left: 93%;
|
||||
}
|
||||
.content .content-message {
|
||||
flex-grow: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
.content .content-message textarea {
|
||||
width: 100%;
|
||||
border: none;
|
||||
padding: 10px 10px;
|
||||
font: 14px/22px "Lato", Arial, sans-serif;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 5px;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.content .content-message button {
|
||||
float: right;
|
||||
color: #94C2ED;
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
background: #F2F5F8;
|
||||
}
|
||||
.content .content-message button:hover {
|
||||
color: #75b1e8;
|
||||
}
|
||||
/* Tooltip container */
|
||||
.tooltip {
|
||||
color: #86BB71;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border-bottom: 1px dotted black; /* If you want dots under the hoverable text */
|
||||
}
|
||||
/* Tooltip text */
|
||||
.tooltip .tooltiptext {
|
||||
visibility: hidden;
|
||||
width: 120px;
|
||||
background-color: #444753;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
padding: 5px 0;
|
||||
border-radius: 6px;
|
||||
/* Position the tooltip text - see examples below! */
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Show the tooltip text when you mouse over the tooltip container */
|
||||
.tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.online, .offline, .me {
|
||||
margin-right: 3px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.online {
|
||||
color: #86BB71;
|
||||
}
|
||||
|
||||
.offline {
|
||||
color: #E38968;
|
||||
}
|
||||
|
||||
.me {
|
||||
color: #94C2ED;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.float-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.clearfix:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
BIN
data/style.css.gz
Normal file
BIN
data/style.css.gz
Normal file
Binary file not shown.
@@ -43,7 +43,8 @@ For an detailed walk-through aimed at beginners, we recommend [meshtastic.letsta
|
||||
|
||||
Note: Updates are happening almost daily, only major updates are listed below. For more details see our forum.
|
||||
|
||||
- 06/24/2020 - 0.7.x Now with over 1000 android users, over 600 people using the radios and translated into 13 languages. Fairly stable and we are working through bugs to get to 1.0.
|
||||
- 09/14/2020 - 1.0.0 Now with over 1700 android users, over 2000 nodes and translated into 15 languages. This project will always be a "beta" experiment, but now quite usable. We are currently selecting 1.1 features in our discussion forum.
|
||||
- 06/24/2020 - 0.7.x Now with over 1000 android users, over 600 people using the radios and translated into 22 languages. Fairly stable and we are working through bugs to get to 1.0.
|
||||
- 06/04/2020 - 0.6.7 Beta releases of both the application and the device code are released. Features are fairly solid now with a sizable number of users.
|
||||
- 04/28/2020 - 0.6.0 [Python API](https://pypi.org/project/meshtastic/) released. Makes it easy to use meshtastic devices as "zero config / just works" mesh transport adapters for other projects.
|
||||
- 04/20/2020 - 0.4.3 Pretty solid now both for the android app and the device code. Many people have donated translations and code. Probably going to call it a beta soon.
|
||||
@@ -66,6 +67,8 @@ The link above will return older more stable releases. We would prefer if you jo
|
||||
|
||||
If you'd like to help with development, the source code is [on github](https://github.com/meshtastic/Meshtastic-Android).
|
||||
|
||||
The app is also distributed for Amazon Fire devices via the Amazon appstore: [](https://www.amazon.com/Geeksville-Industries-Meshtastic/dp/B08CY9394Q)
|
||||
|
||||
## Supported hardware
|
||||
|
||||
We currently support two brands of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4001178678568.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). Also, the version of the T-Beam we link to is shipped with Meshtastic **preinstalled** by TTGO, so you don't have to install it yourself.
|
||||
@@ -74,11 +77,15 @@ Make sure to buy the frequency range which is legal for your country. For the US
|
||||
|
||||
Instructions for installing prebuilt firmware can be found [here](https://github.com/meshtastic/Meshtastic-esp32/blob/master/README.md).
|
||||
|
||||
For a nice printable cases:
|
||||
For a nice looking cases:
|
||||
|
||||
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).
|
||||
- 3D printable cases
|
||||
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 (SMA) 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. TTGO T_Beam V1 (IPEX) see this [design](https://www.thingiverse.com/thing:4587297) by [drewsed](https://www.thingiverse.com/drewsed)
|
||||
4. Heltec Lora32 see this [design](https://www.thingiverse.com/thing:3125854) by [ornotermes](https://www.thingiverse.com/ornotermes).
|
||||
- Laser-cut cases
|
||||
1. TTGO T_Beam V1 (SMA) see this [design](https://www.thingiverse.com/thing:4552771) by [jefish](https://www.thingiverse.com/jefish)
|
||||
|
||||
# IMPORTANT DISCLAIMERS AND FAQ
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
theme: jekyll-theme-cayman
|
||||
|
||||
title: Meshtastic
|
||||
description: An opensource hiking, pilot, skiing, Signal-App-extending GPS mesh communicator
|
||||
description: An opensource hiking, pilot, skiing, secure GPS mesh communicator
|
||||
google_analytics: G-DRZ5H5EXHV
|
||||
|
||||
include: [".well-known"]
|
||||
|
||||
BIN
docs/hardware/DS_SX1261-2_V1.2.pdf
Normal file
BIN
docs/hardware/DS_SX1261-2_V1.2.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/T-SX1262.pdf
Normal file
BIN
docs/hardware/T-SX1262.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/air530/Air530 GPS Manual Text English.pdf
Normal file
BIN
docs/hardware/air530/Air530 GPS Manual Text English.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/air530/Air530_GPS_User_Booklet.V1.7.pdf
Normal file
BIN
docs/hardware/air530/Air530_GPS_User_Booklet.V1.7.pdf
Normal file
Binary file not shown.
35
docs/hardware/corvus.md
Normal file
35
docs/hardware/corvus.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Notes on @BigCorvus boards
|
||||
|
||||
## Board version 1.1
|
||||
|
||||
variant name lora_relay_v1
|
||||
|
||||
### Remaining TODOs
|
||||
|
||||
- power hold for the ST7735
|
||||
- look at example sketch
|
||||
- turn on xmit boost
|
||||
|
||||
## Recommendations for future boards
|
||||
|
||||
@BigCorvus your board is **really** nice. Here's some ideas for the future:
|
||||
|
||||
- make the SWDIO header more standard (the small ARM 2x5 micro footprint?) or at least througholes so it is easy to solder a header
|
||||
|
||||
## How to program bootloader
|
||||
|
||||
Download from here: https://github.com/adafruit/Adafruit_nRF52_Bootloader/releases
|
||||
|
||||
```
|
||||
nrfjprog -f nrf52 --eraseall
|
||||
Erasing user available code and UICR flash areas.
|
||||
Applying system reset.
|
||||
|
||||
nrfjprog -f nrf52 --program feather_nrf52840_express_bootloader-0.3.2_s140_6.1.1.hex
|
||||
Parsing hex file.
|
||||
Reading flash area to program to guarantee it is erased.
|
||||
Checking that the area to write is not protected.
|
||||
Programming device.
|
||||
```
|
||||
|
||||
Then reboot the board, if all went well it now shows up as a mountable filesystem on your USB bus.
|
||||
BIN
docs/hardware/heltec-wifi-lora-32-v2-915.pdf
Normal file
BIN
docs/hardware/heltec-wifi-lora-32-v2-915.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/pinetab/PineTab LoRa schematic.pdf
Normal file
BIN
docs/hardware/pinetab/PineTab LoRa schematic.pdf
Normal file
Binary file not shown.
990
docs/hardware/pinetab/ch341h_datasheet.pdf
Normal file
990
docs/hardware/pinetab/ch341h_datasheet.pdf
Normal file
File diff suppressed because one or more lines are too long
BIN
docs/hardware/pinetab/nfnfaceblhkepdph.jpg
Normal file
BIN
docs/hardware/pinetab/nfnfaceblhkepdph.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 272 KiB |
BIN
docs/hardware/pinetab/nhndndegjjflkibg.jpg
Normal file
BIN
docs/hardware/pinetab/nhndndegjjflkibg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 417 KiB |
Binary file not shown.
38977
docs/hardware/u-blox8-M8_ReceiverDescrProtSpec_(UBX-13003221).pdf
Normal file
38977
docs/hardware/u-blox8-M8_ReceiverDescrProtSpec_(UBX-13003221).pdf
Normal file
File diff suppressed because one or more lines are too long
@@ -2,6 +2,12 @@
|
||||
|
||||
You probably don't care about this section - skip to the next one.
|
||||
|
||||
For high speed/lots of devices/short range tasks:
|
||||
|
||||
- When guessing numhops for sending: if I've heard from many local (0 hop neighbors) decrease hopcount by 2 rather than 1.
|
||||
This should nicely help 'router' nodes do the right thing when long range, or if there are many local nodes for short range.
|
||||
- fix timeouts/delays to be based on packet length at current radio settings
|
||||
|
||||
Nimble tasks:
|
||||
|
||||
- readerror.txt stress test bug
|
||||
|
||||
@@ -1,15 +1,36 @@
|
||||
# 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.
|
||||
This project uses the simple PlatformIO build system. PlatformIO is an extension to Microsoft VSCode.
|
||||
|
||||
1. Purchase a suitable radio (see above)
|
||||
## GUI
|
||||
1. Purchase a suitable [radio](https://github.com/meshtastic/Meshtastic-device/wiki/Hardware-Information).
|
||||
2. Install [Python](https://www.python.org/downloads/).
|
||||
3. Install [Git](https://git-scm.com/downloads).
|
||||
4. Reboot your computer.
|
||||
5. Install [PlatformIO](https://platformio.org/platformio-ide).
|
||||
6. Click the PlatformIO icon on the side bar. 
|
||||
7. Under `Quick Access, Miscellaneous, Clone Git Project` enter the URL of the Meshtastic repo found [here](https://github.com/meshtastic/Meshtastic-device). 
|
||||
8. Select a file location to save the repo.
|
||||
9. Once loaded, open the `platformio.ini` file.
|
||||
10. At the line `default_envs` you can change it to the board type you are building for ie. `tlora-v2, tlora-v1, tlora-v2-1-1.6, tbeam, heltec, tbeam0.7` (boards are listed further down in the file).
|
||||
11. The hardware can be configured for different countries by adding a definition to the `configuration.h` file. `#define HW_VERSION_US` or `HW_VERSION_EU433, HW_VERSION_EU865, HW_VERSION_CN, HW_VERSION_JP`. Other country settings can be found in `MeshRadio.h`. The default is `HW_VERSION_US`.
|
||||
12. Click the PlatformIO icon on the side bar. Under `Project Tasks` you can now build or upload.
|
||||
|
||||
Note - To get a clean build you may have to delete the auto-generated file `./.vscode/c_cpp_properties.json`, close and re-open Visual Studio and WAIT until the file is auto-generated before compiling again.
|
||||
|
||||
## Command Line
|
||||
1. Purchase a suitable [radio](https://github.com/meshtastic/Meshtastic-device/wiki/Hardware-Information).
|
||||
2. Install [PlatformIO](https://platformio.org/platformio-ide)
|
||||
3. Download this git repo and cd into it
|
||||
3. Download this git repo and cd into it:
|
||||
|
||||
```
|
||||
git clone https://github.com/meshtastic/Meshtastic-device.git
|
||||
cd Meshtastic-device
|
||||
```
|
||||
4. Run `git submodule update --init --recursive` to pull in dependencies this project needs.
|
||||
5. If you are outside the USA, run "export COUNTRY=EU865" (or whatever) to set the correct frequency range for your country. Options are provided for `EU433`, `EU865`, `CN`, `JP` and `US` (default). Pull-requests eagerly accepted for other countries.
|
||||
6. Plug the radio into your USB port
|
||||
7. Type `pio run --environment XXX -t upload` (This command will fetch dependencies, build the project and install it on the board via USB). For XXX, use the board type you have (either `tbeam`, `heltec`, `ttgo-lora32-v1`, `ttgo-lora32-v2`).
|
||||
7. Type `pio run --environment XXX -t upload` (This command will fetch dependencies, build the project and install it on the board via USB). For XXX, use the board type you have (either `tlora-v2, tlora-v1, tlora-v2-1-1.6, tbeam, heltec, tbeam0.7`).
|
||||
8. Platform IO also installs a very nice VisualStudio Code based IDE, see their [tutorial](https://docs.platformio.org/en/latest/tutorials/espressif32/arduino_debugging_unit_testing.html) if you'd like to use it.
|
||||
|
||||
## Decoding stack traces
|
||||
|
||||
@@ -42,7 +42,6 @@ Expected sequence for initial download:
|
||||
- Read a RadioConfig from "radio" - used to get the channel and radio settings
|
||||
- Read 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 a series of NodeInfo packets to build the phone's copy of the current NodeDB for the mesh
|
||||
- Read a endConfig packet that indicates that the entire state you need has been sent.
|
||||
- Read a series of MeshPackets until it returns empty to get any messages that arrived for this node while the phone was away
|
||||
|
||||
17
docs/software/gps-todo.txt
Normal file
17
docs/software/gps-todo.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
You probably don't care about this ugly file of personal notes ;-)
|
||||
|
||||
for taiwan region:
|
||||
bin/run.sh --set region 8
|
||||
|
||||
time only mode
|
||||
./bin/run.sh --set gps_operation 3
|
||||
|
||||
ublox parsing failure
|
||||
|
||||
record power measurements and update spreadsheet
|
||||
|
||||
have loop methods return allowable sleep time (from their perspective)
|
||||
increase main cpu sleep time
|
||||
|
||||
warn people about crummy gps antennas - add to faq
|
||||
|
||||
@@ -1,5 +1,75 @@
|
||||
# Mesh broadcast algorithm
|
||||
|
||||
## Current algorithm
|
||||
|
||||
The routing protocol for Meshtastic is really quite simple (and suboptimal). It is heavily influenced by the mesh routing algorithm used in [Radiohead](https://www.airspayce.com/mikem/arduino/RadioHead/) (which was used in very early versions of this project). It has four conceptual layers.
|
||||
|
||||
### A note about protocol buffers
|
||||
|
||||
Because we want our devices to work across various vendors and implementations, we use [Protocol Buffers](https://github.com/meshtastic/Meshtastic-protobufs) pervasively. For information on how the protocol buffers are used wrt API clients see [sw-design](sw-design.md), for purposes of this document you mostly only
|
||||
need to consider the MeshPacket and Subpacket message types.
|
||||
|
||||
### Layer 1: Non reliable zero hop messaging
|
||||
|
||||
This layer is conventional non-reliable lora packet transmission. The transmitted packet has the following representation on the ether:
|
||||
|
||||
- A 32 bit LORA preamble (to allow receiving radios to synchronize clocks and start framing). We use a longer than minimum (8 bit) preamble to maximize the amount of time the LORA receivers can stay asleep, which dramatically lowers power consumption.
|
||||
|
||||
After the preamble the 16 byte packet header is transmitted. This header is described directly by the PacketHeader class in the C++ source code. But indirectly it matches the first portion of the "MeshPacket" protobuf definition. But notably: this portion of the packet is sent directly as the following 16 bytes (rather than using the protobuf encoding). We do this to both save airtime and to allow receiving radio hardware the option of filtering packets before even waking the main CPU.
|
||||
|
||||
- to (4 bytes): the unique NodeId of the destination (or 0xffffffff for NodeNum_BROADCAST)
|
||||
- from (4 bytes): the unique NodeId of the sender)
|
||||
- id (4 bytes): the unique (wrt the sending node only) packet ID number for this packet. We use a large (32 bit) packet ID to ensure there is enough unique state to protect any encrypted payload from attack.
|
||||
- flags (4 bytes): Only a few bits are are currently used - 3 bits for for the "HopLimit" (see below) and 1 bit for "WantAck"
|
||||
|
||||
After the packet header the actual packet is placed onto the the wire. These bytes are merely the encrypted packed protobuf encoding of the SubPacket protobuf. A full description of our encryption is available in [crypto](crypto.md). It is worth noting that only this SubPacket is encrypted, headers are not. Which leaves open the option of eventually allowing nodes to route packets without knowing the keys used to encrypt.
|
||||
|
||||
NodeIds are constructed from the bottom four bytes of the macaddr of the bluetooth address. Because the OUI is assigned by the IEEE and we currently only support a few CPU manufacturers, the upper byte is defacto guaranteed unique for each vendor. The bottom 3 bytes are guaranteed unique by that vendor.
|
||||
|
||||
To prevent collisions all transmitters will listen before attempting to send. If they hear some other node transmitting, they will reattempt transmission in x milliseconds. This retransmission delay is random between FIXME and FIXME (these two numbers are currently hardwired, but really should be scaled based on expected packet transmission time at current channel settings).
|
||||
|
||||
### Layer 2: Reliable zero hop messaging
|
||||
|
||||
This layer adds reliable messaging between the node and its immediate neighbors (only).
|
||||
|
||||
The default messaging provided by layer-1 is extended by setting the "want-ack" flag in the MeshPacket protobuf. If want-ack is set the following documentation from mesh.proto applies:
|
||||
|
||||
"""This packet is being sent as a reliable message, we would prefer it to arrive
|
||||
at the destination. We would like to receive a ack packet in response.
|
||||
|
||||
Broadcasts messages treat this flag specially: Since acks for broadcasts would
|
||||
rapidly flood the channel, the normal ack behavior is suppressed. Instead,
|
||||
the original sender listens to see if at least one node is rebroadcasting this
|
||||
packet (because naive flooding algorithm). If it hears that the odds (given
|
||||
typical LoRa topologies) the odds are very high that every node should
|
||||
eventually receive the message. So FloodingRouter.cpp generates an implicit
|
||||
ack which is delivered to the original sender. If after some time we don't
|
||||
hear anyone rebroadcast our packet, we will timeout and retransmit, using the
|
||||
regular resend logic."""
|
||||
|
||||
If a transmitting node does not receive an ACK (or a NAK) packet within FIXME milliseconds, it will use layer-1 to attempt a retransmission of the sent packet. A reliable packet (at this 'zero hop' level) will be resent a maximum of three times. If no ack or nak has been received by then the local node will internally generate a nak (either for local consumption or use by higher layers of the protocol).
|
||||
|
||||
### Layer 3: (Naive) flooding for multi-hop messaging
|
||||
|
||||
Given our use-case for the initial release, most of our protocol is built around [flooding](<https://en.wikipedia.org/wiki/Flooding_(computer_networking)>). The implementation is currently 'naive' - i.e. it doesn't try to optimize flooding other than abandoning retransmission once we've seen a nearby receiver has acked the packet. Therefore, for each source packet up to N retransmissions might occur (if there are N nodes in the mesh).
|
||||
|
||||
Each node in the mesh, if it sees a packet on the ether with HopLimit set to a value other than zero, it will decrement that HopLimit and attempt retransmission on behalf of the original sending node.
|
||||
|
||||
### Layer 4: DSR for multi-hop unicast messaging
|
||||
|
||||
This layer is not yet fully implemented (and not yet used). But eventually (if we stay with our own transport rather than switching to QMesh or Reticulum)
|
||||
we will use conventional DSR for unicast messaging. Currently (even when not requiring 'broadcasts') we send any multi-hop unicasts as 'broadcasts' so that we can
|
||||
leverage our (functional) flooding implementation. This is suboptimal but it is a very rare use-case, because the odds are high that most nodes (given our small networks and 'hiking' use case) are within a very small number of hops. When any node witnesses an ack for a packet, it will realize that it can abandon its own
|
||||
broadcast attempt for that packet.
|
||||
|
||||
## Misc notes on remaining tasks
|
||||
|
||||
This section is currently poorly formatted, it is mostly a mere set of todo lists and notes for @geeksville during his initial development. After release 1.0 ideas for future optimization include:
|
||||
|
||||
- Make flood-routing less naive (because we have GPS and radio signal strength as heuristics to avoid redundant retransmissions)
|
||||
- If nodes have been user marked as 'routers', preferentially do flooding via those nodes
|
||||
- Fully implement DSR to improve unicast efficiency (or switch to QMesh/Reticulum as these projects mature)
|
||||
|
||||
great source of papers and class notes: http://www.cs.jhu.edu/~cs647/
|
||||
|
||||
flood routing improvements
|
||||
@@ -146,23 +216,3 @@ look into the literature for this idea specifically.
|
||||
build the most recent version of reality, and if some nodes are too far, then nodes closer in will eventually forward their changes to the distributed db.
|
||||
- construct non ambigious rules for who broadcasts to request db updates. ideally the algorithm should nicely realize node X can see most other nodes, so they should just listen to all those nodes and minimize the # of broadcasts. the distributed picture of nodes rssi could be useful here?
|
||||
- possibly view the BLE protocol to the radio the same way - just a process of reconverging the node/msgdb database.
|
||||
|
||||
# Old notes
|
||||
|
||||
FIXME, merge into the above:
|
||||
|
||||
good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept
|
||||
|
||||
interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf
|
||||
It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728
|
||||
https://en.wikipedia.org/wiki/Dynamic_Source_Routing
|
||||
|
||||
broadcast solution:
|
||||
Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent)
|
||||
Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set
|
||||
|
||||
compare to db sync algorithm?
|
||||
|
||||
what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies.
|
||||
|
||||
If Y were to die, at least the neighbor nodes of Y would have their last known position of Y.
|
||||
|
||||
@@ -5,8 +5,30 @@
|
||||
|
||||
## RAK815
|
||||
|
||||
TODO:
|
||||
### PPR1 TODO
|
||||
|
||||
* V_BK for the GPS should probably be supplied from something always on
|
||||
|
||||
* use S113 soft device 7.2.0
|
||||
* properly test charge controller config and read battery/charge status
|
||||
* fix bluetooth
|
||||
* fix LCD max contrast (currently too high, needs to be about 40?)
|
||||
* save brightness settings in flash
|
||||
* make ST7567Wire driver less ugly, move OLED stuff into a common class treee
|
||||
* add LCD power save mode for lcd per page 31 of datasheet
|
||||
* add LCD power off sequence per datasheet to lcd driver
|
||||
* leave LCD screen on most of the time (because it needs little power)
|
||||
|
||||
### general nrf52 TODO:
|
||||
|
||||
- turn off transitions on eink screens
|
||||
- change update interval on eink from 1/sec frames to one frame every 5 mins
|
||||
- enter SDS state at correct time (to protect battery or loss of phone contact)
|
||||
- show screen on eink when we enter SDS state (with app info and say sleeping)
|
||||
- require button press to pair
|
||||
|
||||
- shrink soft device RAM usage
|
||||
- get nrf52832 working again (currently OOM)
|
||||
- i2c gps comms not quite right
|
||||
- ble: AdafruitBluefruit::begin - adafruit_ble_task was assigned an invalid stack pointer. out of memory?
|
||||
- measure power draw
|
||||
@@ -194,7 +216,7 @@ Nice ideas worth considering someday...
|
||||
- DONE neg 7 error code from receive
|
||||
- DONE remove unused sx1262 lib from github
|
||||
- at boot we are starting our message IDs at 1, rather we should start them at a random number. also, seed random based on timer. this could be the cause of our first message not seen bug.
|
||||
- add a NEMA based GPS driver to test GPS
|
||||
- add a NMEA based GPS driver to test GPS
|
||||
- DONE use "variants" to get all gpio bindings
|
||||
- DONE plug in correct variants for the real board
|
||||
- turn on DFU assistance in the appload using the nordic DFU helper lib call
|
||||
|
||||
@@ -2,18 +2,25 @@
|
||||
|
||||
These are **preliminary** notes on support for Meshtastic in the Pinetab.
|
||||
|
||||
A RF95 is connected via a CS341 USB-SPI chip.
|
||||
A RF95 is connected via a CH341 USB-SPI chip.
|
||||
|
||||
Pin assignments:
|
||||
CS0 from RF95 goes to CS0 on CS341
|
||||
DIO0 from RF95 goes to INT on CS341
|
||||
RST from RF95 goes to RST on CS341
|
||||
CS0 from RF95 goes to CS0 on CH341
|
||||
DIO0 from RF95 goes to INT on CH341
|
||||
RST from RF95 goes to RST on CH341
|
||||
|
||||
This linux driver claims to provide USB-SPI support: https://github.com/gschorcht/spi-ch341-usb
|
||||
Notes here on using that driver: https://www.linuxquestions.org/questions/linux-hardware-18/ch341-usb-to-spi-adaptor-driver-doesn%27t-work-4175622736/
|
||||
|
||||
Or if **absolutely** necessary could bitbang: https://www.cnx-software.com/2018/02/16/wch-ch341-usb-to-serial-chip-gets-linux-driver-to-control-gpios-over-usb/
|
||||
|
||||
## Portduino tasks
|
||||
|
||||
* How to access spi devices via ioctl (spidev): https://www.raspberrypi.org/documentation/hardware/raspberrypi/spi/README.md#:~:text=Troubleshooting-,Overview,bus)%2C%20UARTs%2C%20etc.
|
||||
* access gpio via libgpiod?
|
||||
* Use dkms to distribute driver?
|
||||
* echo 100 > /sys/module/spi_ch341_usb/parameters/poll_period
|
||||
|
||||
## Task list
|
||||
|
||||
* Port meshtastic to build (under platformio) for a poxix target. spec: no screen, no gpios, sim network interface, posix threads, posix semaphores & queues, IO to the console only
|
||||
|
||||
@@ -32,14 +32,18 @@ From lower to higher power consumption.
|
||||
onEntry: setBluetoothOn(true)
|
||||
onExit:
|
||||
|
||||
- full on (ON) - Everything is on
|
||||
onEntry: setBluetoothOn(true), screen.setOn(true)
|
||||
onExit: screen.setOn(false)
|
||||
|
||||
- serial API usage (SERIAL) - Screen is on, device doesn't sleep, bluetooth off
|
||||
onEntry: setBluetooth off, screen on
|
||||
onExit:
|
||||
|
||||
- full on (ON) - Everything is on, can eventually timeout and lower to a lower power state
|
||||
onEntry: setBluetoothOn(true), screen.setOn(true)
|
||||
onExit: screen->setOn(false)
|
||||
|
||||
- has power (POWER) - Screen is on, device doesn't sleep, bluetooth on, will stay in this state as long as we have power
|
||||
onEntry: setBluetooth off, screen on
|
||||
onExit:
|
||||
|
||||
## Behavior
|
||||
|
||||
### events that increase CPU activity
|
||||
@@ -56,9 +60,11 @@ From lower to higher power consumption.
|
||||
- While in NB/DARK/ON: If we receive EVENT_NODEDB_UPDATED we transition to ON (so the new screen can be shown)
|
||||
- While in DARK: While the phone talks to us over BLE (EVENT_CONTACT_FROM_PHONE) reset any sleep timers and stay in DARK (needed for bluetooth sw update and nice user experience if the user is reading/replying to texts)
|
||||
- while in LS/NB/DARK: if SERIAL_CONNECTED, go to serial
|
||||
- while in any state: if we have AC power, go to POWER
|
||||
|
||||
### events that decrease cpu activity
|
||||
|
||||
- While in POWER: if lose AC go to ON
|
||||
- While in SERIAL: if SERIAL_DISCONNECTED, go to NB
|
||||
- While in ON: If PRESS event occurs, reset screen_on_secs timer and tell the screen to handle the pess
|
||||
- While in ON: If it has been more than screen_on_secs since a press, lower to DARK
|
||||
|
||||
@@ -5,5 +5,5 @@ This is a mini design doc for developing the meshtastic software.
|
||||
* 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...)
|
||||
* [Device API](device-api.md) and porting guide for new clients (iOS, python, etc...)
|
||||
* TODO: how to port the device code to a new device.
|
||||
|
||||
BIN
images/amazon-fire-button.png
Normal file
BIN
images/amazon-fire-button.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
69
nrf52/ttgo_eink_qpsi.ini
Normal file
69
nrf52/ttgo_eink_qpsi.ini
Normal file
@@ -0,0 +1,69 @@
|
||||
; nrfjprog.exe configuration file.
|
||||
|
||||
; Note: QSPI flash is mapped into memory at address 0x12000000
|
||||
|
||||
[DEFAULT_CONFIGURATION]
|
||||
; Define the capacity of the flash memory device in bytes. Set to 0 if no external memory device is present in your board.
|
||||
; MX25R1635F is 16Mbit/2Mbyte
|
||||
MemSize = 0x200000
|
||||
|
||||
; Define the desired ReadMode. Valid options are FASTREAD, READ2O, READ2IO, READ4O and READ4IO
|
||||
ReadMode = READ2IO
|
||||
|
||||
; Define the desired WriteMode. Valid options are PP, PP2O, PP4O and PP4IO
|
||||
WriteMode = PP
|
||||
|
||||
; Define the desired AddressMode. Valid options are BIT24 and BIT32
|
||||
AddressMode = BIT24
|
||||
|
||||
; Define the desired Frequency. Valid options are M2, M4, M8, M16 and M32
|
||||
Frequency = M16
|
||||
|
||||
; Define the desired SPI mode. Valid options are MODE0 and MODE3
|
||||
SpiMode = MODE0
|
||||
|
||||
; Define the desired SckDelay. Valid options are in the range 0 to 255
|
||||
SckDelay = 0x80
|
||||
|
||||
; Define the desired IO level for DIO2 and DIO3 during a custom instruction. Valid options are LEVEL_HIGH and LEVEL_LOW
|
||||
CustomInstructionIO2Level = LEVEL_LOW
|
||||
CustomInstructionIO3Level = LEVEL_HIGH
|
||||
|
||||
; Define the assigned pins for the QSPI peripheral. Valid options are those existing in your device
|
||||
CSNPin = 15
|
||||
CSNPort = 1
|
||||
SCKPin = 14
|
||||
SCKPort = 1
|
||||
DIO0Pin = 12
|
||||
DIO0Port = 1
|
||||
DIO1Pin = 13
|
||||
DIO1Port = 1
|
||||
|
||||
;These two pins are not connected, but we must name something
|
||||
DIO2Pin = 3
|
||||
DIO2Port = 1
|
||||
DIO3Pin = 5
|
||||
DIO3Port = 1
|
||||
|
||||
; Define the Index of the Write In Progress (WIP) bit in the status register. Valid options are in the range of 0 to 7.
|
||||
WIPIndex = 0
|
||||
|
||||
; Define page size for commands. Valid sizes are PAGE256 and PAGE512.
|
||||
PPSize = PAGE256
|
||||
|
||||
; Custom instructions to send to the external memory after initialization. Format is instruction code plus data to send in between optional brakets.
|
||||
; These instructions will be executed each time the qspi peripheral is initiated by nrfjprog.
|
||||
; To improve execution speed on consecutive interations with QSPI, you can run nrfjprog once with custom initialization, and then comment out the lines below.
|
||||
; Numbers can be given in decimal, hex (starting with either 0x or 0X) and binary (starting with either 0b or 0B) formats.
|
||||
; The custom instructions will be executed in the order found.
|
||||
|
||||
; This example includes two commands, first a WREN (WRite ENable) and then a WRSR (WRite Satus Register) enabling the Quad Operation and the High Performance
|
||||
; mode for the MX25R6435F memory present in the nRF52840 DK.
|
||||
;InitializationCustomInstruction = 0x06
|
||||
;InitializationCustomInstruction = 0x01, [0x40, 0, 0x2]
|
||||
|
||||
; For MX25R1635F on TTGO board, only two data lines are connected
|
||||
; This example includes two commands, first a WREN (WRite ENable) and then a WRSR (WRite Satus Register) disabling Quad Operation and the High Performance
|
||||
; mode. For normal operation you might want low power mode instead.
|
||||
InitializationCustomInstruction = 0x06
|
||||
InitializationCustomInstruction = 0x01, [0x00, 0, 0x2]
|
||||
151
platformio.ini
151
platformio.ini
@@ -9,7 +9,8 @@
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[platformio]
|
||||
default_envs = tbeam ; Note: the github actions CI test build can't yet build NRF52 targets
|
||||
default_envs = tbeam # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you'd like to change the default to something like lora-relay-v1 put that here
|
||||
;default_envs = heltec # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you'd like to change the default to something like lora-relay-v1 put that here
|
||||
|
||||
[common]
|
||||
; common is not currently used
|
||||
@@ -23,24 +24,21 @@ default_envs = tbeam ; Note: the github actions CI test build can't yet build NR
|
||||
|
||||
[env]
|
||||
|
||||
framework = arduino
|
||||
|
||||
; customize the partition table
|
||||
; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
|
||||
board_build.partitions = partition-table.csv
|
||||
|
||||
; note: we add src to our include search path so that lmic_project_config can override
|
||||
; FIXME: fix lib/BluetoothOTA dependency back on src/ so we can remove -Isrc
|
||||
build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -Ilib/nanopb/include -Os -Wl,-Map,.pio/build/output.map
|
||||
-DAXP_DEBUG_PORT=Serial
|
||||
build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -Ilib/nanopb/include -Wl,-Map,.pio/build/output.map
|
||||
-DHW_VERSION_${sysenv.COUNTRY}
|
||||
-DAPP_VERSION=${sysenv.APP_VERSION}
|
||||
-DHW_VERSION=${sysenv.HW_VERSION}
|
||||
-DUSE_THREAD_NAMES
|
||||
|
||||
; leave this commented out to avoid breaking Windows
|
||||
;upload_port = /dev/ttyUSB0
|
||||
;monitor_port = /dev/ttyUSB0
|
||||
|
||||
;upload_port = /dev/cu.SLAB_USBtoUART
|
||||
;monitor_port = /dev/cu.SLAB_USBtoUART
|
||||
|
||||
; the default is esptool
|
||||
; upload_protocol = esp-prog
|
||||
|
||||
@@ -61,30 +59,53 @@ debug_tool = jlink
|
||||
|
||||
lib_deps =
|
||||
https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
|
||||
SPI
|
||||
1260 ; OneButton library for non-blocking button debounce
|
||||
https://github.com/geeksville/OneButton.git ; OneButton library for non-blocking button debounce
|
||||
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
|
||||
https://github.com/meshtastic/RadioLib.git#d6b12f7eb0a06bd2414c79b437b25d377e3f603f
|
||||
https://github.com/meshtastic/arduino-fsm.git#2f106146071fc7bc620e1e8d4b88dc4e0266ce39
|
||||
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git#31015a55e630a2df77d9d714669c621a5bf355ad
|
||||
https://github.com/meshtastic/RadioLib.git#8657380241bce681c33aab46598bbf13b11f876c
|
||||
https://github.com/meshtastic/TinyGPSPlus.git
|
||||
https://github.com/meshtastic/AXP202X_Library.git#8404abb6d4b486748636bc6ad72d2a47baaf5460
|
||||
Wire ; explicitly needed here because the AXP202 library forgets to add it
|
||||
SPI
|
||||
https://github.com/geeksville/ArduinoThread.git#333ffd09b596977c217ba25da4258f588b462ac6
|
||||
|
||||
; Common settings for conventional (non Portduino) Ardino targets
|
||||
[arduino_base]
|
||||
|
||||
framework = arduino
|
||||
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
|
||||
build_flags = ${env.build_flags} -Os
|
||||
|
||||
src_filter = ${env.src_filter} -<portduino/>
|
||||
|
||||
; Common settings for ESP targes, mixin with extends = esp32_base
|
||||
[esp32_base]
|
||||
extends = arduino_base
|
||||
platform = espressif32
|
||||
src_filter =
|
||||
${env.src_filter} -<nrf52/>
|
||||
${arduino_base.src_filter} -<nrf52/>
|
||||
upload_speed = 921600
|
||||
debug_init_break = tbreak setup
|
||||
build_flags =
|
||||
${env.build_flags} -Wall -Wextra -Isrc/esp32 -mfix-esp32-psram-cache-issue -lnimble -std=c++11
|
||||
${arduino_base.build_flags} -Wall -Wextra -Isrc/esp32 -Isrc/esp32-mfix-esp32-psram-cache-issue -lnimble -std=c++11
|
||||
-DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
|
||||
-DAXP_DEBUG_PORT=Serial
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
https://github.com/meshtastic/esp32_https_server.git
|
||||
# Hmm - this doesn't work yet
|
||||
# board_build.ldscript = linker/esp32.extram.bss.ld
|
||||
lib_ignore = segger_rtt
|
||||
platform_packages =
|
||||
framework-arduinoespressif32 @ https://github.com/meshtastic/arduino-esp32.git#1adba3f11ca8406ac0a704d151697b572058b53d
|
||||
framework-arduinoespressif32@https://github.com/meshtastic/arduino-esp32.git#2814f110aa618429bdd9a0a2d6a93c55f29f87a6
|
||||
|
||||
; customize the partition table
|
||||
; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
|
||||
board_build.partitions = partition-table.csv
|
||||
|
||||
; 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
|
||||
@@ -93,13 +114,12 @@ platform_packages =
|
||||
|
||||
; -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
|
||||
|
||||
; The 1.0 release of the TBEAM board
|
||||
; The 1.0 release of the TBEAM board
|
||||
[env:tbeam]
|
||||
extends = esp32_base
|
||||
board = ttgo-t-beam
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
https://github.com/meshtastic/AXP202X_Library.git
|
||||
${esp32_base.lib_deps}
|
||||
build_flags =
|
||||
${esp32_base.build_flags} -D TBEAM_V10
|
||||
|
||||
@@ -142,25 +162,26 @@ build_flags =
|
||||
platform = https://github.com/HelTecAutomation/platform-asrmicro650x.git ; we use top-of-tree because stable version has too many bugs - asrmicro650x
|
||||
board = cubecell_board_plus
|
||||
; FIXME, bug in cubecell arduino - they are supposed to set ARDUINO
|
||||
build_flags = ${env.build_flags} -DARDUINO=100 -Isrc/cubecell
|
||||
build_flags = ${arduino_base.build_flags} -DARDUINO=100 -Isrc/cubecell
|
||||
src_filter =
|
||||
${env.src_filter} -<esp32/> -<nrf52/>
|
||||
${arduino_base.src_filter} -<esp32/> -<nrf52/>
|
||||
|
||||
; Common settings for NRF52 based targets
|
||||
[nrf52_base]
|
||||
; Instead of the standard nordicnrf52 platform, we use our fork which has our added variant files
|
||||
; platform = nordicnrf52
|
||||
platform = https://github.com/meshtastic/platform-nordicnrf52.git#62d185fe61b6c84c554046106529b4fd8f155e2c
|
||||
platform = https://github.com/meshtastic/platform-nordicnrf52.git#1a2639a6b0f79b5df66bea3e3089f0d5285fdc63
|
||||
extends = arduino_base
|
||||
debug_tool = jlink
|
||||
build_type = debug ; I'm debugging with ICE a lot now
|
||||
; note: liboberon provides the AES256 implementation for NRF52 (though not using the hardware acceleration of the NRF52840 - FIXME)
|
||||
build_flags =
|
||||
${env.build_flags} -Wno-unused-variable
|
||||
${arduino_base.build_flags} -Wno-unused-variable
|
||||
-Isrc/nrf52
|
||||
-Isdk-nrfxlib/crypto/nrf_oberon/include -Lsdk-nrfxlib/crypto/nrf_oberon/lib/cortex-m4/hard-float/ -lliboberon_3.0.3
|
||||
;-DCFG_DEBUG=3
|
||||
src_filter =
|
||||
${env.src_filter} -<esp32/> -<nimble/>
|
||||
${arduino_base.src_filter} -<esp32/> -<nimble/> -<meshwifi/>
|
||||
lib_ignore =
|
||||
BluetoothOTA
|
||||
monitor_port = /dev/ttyACM1
|
||||
@@ -186,6 +207,9 @@ board = nrf52840_dk
|
||||
[env:nrf52840dk-geeksville]
|
||||
extends = nrf52_base
|
||||
board = nrf52840_dk_modified
|
||||
# add our variants files to the include and src paths
|
||||
build_flags = ${nrf52_base.build_flags} -Ivariants/pca10056-rc-clock
|
||||
src_filter = ${nrf52_base.src_filter} +<../variants/pca10056-rc-clock>
|
||||
|
||||
; Note: By default no lora device is created for this build - it uses a simulated interface
|
||||
[env:feather_nrf52832]
|
||||
@@ -209,9 +233,82 @@ monitor_speed = 115200
|
||||
extends = nrf52_base
|
||||
board = ppr
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
${arduino_base.lib_deps}
|
||||
UC1701
|
||||
|
||||
; The PPR board
|
||||
[env:ppr1]
|
||||
extends = nrf52_base
|
||||
board = ppr1
|
||||
build_flags = ${nrf52_base.build_flags} -Ivariants/ppr1
|
||||
src_filter = ${nrf52_base.src_filter} +<../variants/ppr1>
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
|
||||
; Prototype eink/nrf52840/sx1262 device
|
||||
[env:eink]
|
||||
extends = nrf52_base
|
||||
board = eink
|
||||
# add our variants files to the include and src paths
|
||||
# define build flags for the TFT_eSPI library
|
||||
build_flags = ${nrf52_base.build_flags} -Ivariants/eink
|
||||
-DBUSY_PIN=3 -DRST_PIN=2 -DDC_PIN=28 -DCS_PIN=30
|
||||
src_filter = ${nrf52_base.src_filter} +<../variants/eink>
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
https://github.com/geeksville/EPD_Libraries.git
|
||||
TFT_eSPI
|
||||
|
||||
; The https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay board by @BigCorvus
|
||||
[env:lora-relay-v1]
|
||||
extends = nrf52_base
|
||||
board = lora-relay-v1
|
||||
# add our variants files to the include and src paths
|
||||
# define build flags for the TFT_eSPI library
|
||||
build_flags = ${nrf52_base.build_flags} -Ivariants/lora_relay_v1
|
||||
-DUSER_SETUP_LOADED
|
||||
-DTFT_WIDTH=80
|
||||
-DTFT_HEIGHT=160
|
||||
-DST7735_GREENTAB160x80
|
||||
-DST7735_DRIVER
|
||||
-DTFT_CS=ST7735_CS
|
||||
-DTFT_DC=ST7735_RS
|
||||
-DTFT_RST=ST7735_RESET
|
||||
-DSPI_FREQUENCY=27000000
|
||||
src_filter = ${nrf52_base.src_filter} +<../variants/lora_relay_v1>
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
SparkFun BQ27441 LiPo Fuel Gauge Arduino Library
|
||||
TFT_eSPI
|
||||
|
||||
; The https://github.com/BigCorvus/LoRa-BLE-Relay-v2 board by @BigCorvus
|
||||
[env:lora-relay-v2]
|
||||
extends = nrf52_base
|
||||
board = lora-relay-v2
|
||||
# add our variants files to the include and src paths
|
||||
# define build flags for the TFT_eSPI library
|
||||
build_flags = ${nrf52_base.build_flags} -Ivariants/lora_relay_v2
|
||||
-DUSER_SETUP_LOADED
|
||||
-DTFT_WIDTH=80
|
||||
-DTFT_HEIGHT=160
|
||||
-DST7735_GREENTAB160x80
|
||||
-DST7735_DRIVER
|
||||
-DTFT_CS=ST7735_CS
|
||||
-DTFT_DC=ST7735_RS
|
||||
-DTFT_RST=ST7735_RESET
|
||||
-DSPI_FREQUENCY=27000000
|
||||
-DTFT_WR=ST7735_SDA
|
||||
-DTFT_SCLK=ST7735_SCK
|
||||
src_filter = ${nrf52_base.src_filter} +<../variants/lora_relay_v2>
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
SparkFun BQ27441 LiPo Fuel Gauge Arduino Library
|
||||
TFT_eSPI
|
||||
|
||||
; The Portduino based sim environment on top of linux
|
||||
[env:linux]
|
||||
platform = https://github.com/geeksville/platform-portduino.git
|
||||
src_filter = ${env.src_filter} -<esp32/> -<nimble/> -<nrf52/> -<meshwifi/>
|
||||
build_flags = ${arduino_base.build_flags} -O0
|
||||
framework = arduino
|
||||
board = linux_x86_64
|
||||
|
||||
2
proto
2
proto
Submodule proto updated: 0523977d1f...a0b8d88896
3
release/.gitignore
vendored
3
release/.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
*.elf
|
||||
*.bin
|
||||
*.map
|
||||
*.zip
|
||||
*.zip
|
||||
*.uf2
|
||||
|
||||
192
src/GPSStatus.h
192
src/GPSStatus.h
@@ -1,126 +1,98 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include "Status.h"
|
||||
#include "configuration.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace meshtastic {
|
||||
namespace meshtastic
|
||||
{
|
||||
|
||||
/// Describes the state of the GPS system.
|
||||
class GPSStatus : public Status
|
||||
/// Describes the state of the GPS system.
|
||||
class GPSStatus : public Status
|
||||
{
|
||||
|
||||
private:
|
||||
CallbackObserver<GPSStatus, const GPSStatus *> statusObserver =
|
||||
CallbackObserver<GPSStatus, const GPSStatus *>(this, &GPSStatus::updateStatus);
|
||||
|
||||
bool hasLock = false; // default to false, until we complete our first read
|
||||
bool isConnected = false; // Do we have a GPS we are talking to
|
||||
int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double
|
||||
int32_t altitude = 0;
|
||||
uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs
|
||||
// scaling before use)
|
||||
uint32_t heading = 0;
|
||||
uint32_t numSatellites = 0;
|
||||
|
||||
public:
|
||||
GPSStatus() { statusType = STATUS_TYPE_GPS; }
|
||||
GPSStatus(bool hasLock, bool isConnected, int32_t latitude, int32_t longitude, int32_t altitude, uint32_t dop,
|
||||
uint32_t heading, uint32_t numSatellites)
|
||||
: Status()
|
||||
{
|
||||
this->hasLock = hasLock;
|
||||
this->isConnected = isConnected;
|
||||
this->latitude = latitude;
|
||||
this->longitude = longitude;
|
||||
this->altitude = altitude;
|
||||
this->dop = dop;
|
||||
this->heading = heading;
|
||||
this->numSatellites = numSatellites;
|
||||
}
|
||||
GPSStatus(const GPSStatus &);
|
||||
GPSStatus &operator=(const GPSStatus &);
|
||||
|
||||
private:
|
||||
CallbackObserver<GPSStatus, const GPSStatus *> statusObserver = CallbackObserver<GPSStatus, const GPSStatus *>(this, &GPSStatus::updateStatus);
|
||||
void observe(Observable<const GPSStatus *> *source) { statusObserver.observe(source); }
|
||||
|
||||
bool hasLock = false; // default to false, until we complete our first read
|
||||
bool isConnected = false; // Do we have a GPS we are talking to
|
||||
int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double
|
||||
int32_t altitude = 0;
|
||||
uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs scaling before use)
|
||||
uint32_t heading = 0;
|
||||
uint32_t numSatellites = 0;
|
||||
bool getHasLock() const { return hasLock; }
|
||||
|
||||
public:
|
||||
bool getIsConnected() const { return isConnected; }
|
||||
|
||||
GPSStatus() {
|
||||
statusType = STATUS_TYPE_GPS;
|
||||
}
|
||||
GPSStatus( bool hasLock, bool isConnected, int32_t latitude, int32_t longitude, int32_t altitude, uint32_t dop, uint32_t heading, uint32_t numSatellites ) : Status()
|
||||
int32_t getLatitude() const { return latitude; }
|
||||
|
||||
int32_t getLongitude() const { return longitude; }
|
||||
|
||||
int32_t getAltitude() const { return altitude; }
|
||||
|
||||
uint32_t getDOP() const { return dop; }
|
||||
|
||||
uint32_t getHeading() const { return heading; }
|
||||
|
||||
uint32_t getNumSatellites() const { return numSatellites; }
|
||||
|
||||
bool matches(const GPSStatus *newStatus) const
|
||||
{
|
||||
return (newStatus->hasLock != hasLock || newStatus->isConnected != isConnected || newStatus->latitude != latitude ||
|
||||
newStatus->longitude != longitude || newStatus->altitude != altitude || newStatus->dop != dop ||
|
||||
newStatus->heading != heading || newStatus->numSatellites != numSatellites);
|
||||
}
|
||||
int updateStatus(const GPSStatus *newStatus)
|
||||
{
|
||||
// Only update the status if values have actually changed
|
||||
bool isDirty;
|
||||
{
|
||||
this->hasLock = hasLock;
|
||||
this->isConnected = isConnected;
|
||||
this->latitude = latitude;
|
||||
this->longitude = longitude;
|
||||
this->altitude = altitude;
|
||||
this->dop = dop;
|
||||
this->heading = heading;
|
||||
this->numSatellites = numSatellites;
|
||||
isDirty = matches(newStatus);
|
||||
initialized = true;
|
||||
hasLock = newStatus->hasLock;
|
||||
isConnected = newStatus->isConnected;
|
||||
latitude = newStatus->latitude;
|
||||
longitude = newStatus->longitude;
|
||||
altitude = newStatus->altitude;
|
||||
dop = newStatus->dop;
|
||||
heading = newStatus->heading;
|
||||
numSatellites = newStatus->numSatellites;
|
||||
}
|
||||
GPSStatus(const GPSStatus &);
|
||||
GPSStatus &operator=(const GPSStatus &);
|
||||
|
||||
void observe(Observable<const GPSStatus *> *source)
|
||||
{
|
||||
statusObserver.observe(source);
|
||||
if (isDirty) {
|
||||
if (hasLock)
|
||||
DEBUG_MSG("New GPS pos lat=%f, lon=%f, alt=%d, pdop=%f, heading=%f, sats=%d\n", latitude * 1e-7, longitude * 1e-7,
|
||||
altitude, dop * 1e-2, heading * 1e-5, numSatellites);
|
||||
else
|
||||
DEBUG_MSG("No GPS lock\n");
|
||||
onNewStatus.notifyObservers(this);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
bool getHasLock() const
|
||||
{
|
||||
return hasLock;
|
||||
}
|
||||
|
||||
bool getIsConnected() const
|
||||
{
|
||||
return isConnected;
|
||||
}
|
||||
|
||||
int32_t getLatitude() const
|
||||
{
|
||||
return latitude;
|
||||
}
|
||||
|
||||
int32_t getLongitude() const
|
||||
{
|
||||
return longitude;
|
||||
}
|
||||
|
||||
int32_t getAltitude() const
|
||||
{
|
||||
return altitude;
|
||||
}
|
||||
|
||||
uint32_t getDOP() const
|
||||
{
|
||||
return dop;
|
||||
}
|
||||
|
||||
uint32_t getHeading() const
|
||||
{
|
||||
return heading;
|
||||
}
|
||||
|
||||
uint32_t getNumSatellites() const
|
||||
{
|
||||
return numSatellites;
|
||||
}
|
||||
|
||||
bool matches(const GPSStatus *newStatus) const
|
||||
{
|
||||
return (
|
||||
newStatus->hasLock != hasLock ||
|
||||
newStatus->isConnected != isConnected ||
|
||||
newStatus->latitude != latitude ||
|
||||
newStatus->longitude != longitude ||
|
||||
newStatus->altitude != altitude ||
|
||||
newStatus->dop != dop ||
|
||||
newStatus->heading != heading ||
|
||||
newStatus->numSatellites != numSatellites
|
||||
);
|
||||
}
|
||||
int updateStatus(const GPSStatus *newStatus) {
|
||||
// Only update the status if values have actually changed
|
||||
bool isDirty;
|
||||
{
|
||||
isDirty = matches(newStatus);
|
||||
initialized = true;
|
||||
hasLock = newStatus->hasLock;
|
||||
isConnected = newStatus->isConnected;
|
||||
latitude = newStatus->latitude;
|
||||
longitude = newStatus->longitude;
|
||||
altitude = newStatus->altitude;
|
||||
dop = newStatus->dop;
|
||||
heading = newStatus->heading;
|
||||
numSatellites = newStatus->numSatellites;
|
||||
}
|
||||
if(isDirty) {
|
||||
DEBUG_MSG("New GPS pos lat=%f, lon=%f, alt=%d, pdop=%f, heading=%f, sats=%d\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2, heading * 1e-5, numSatellites);
|
||||
onNewStatus.notifyObservers(this);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace meshtastic
|
||||
|
||||
extern meshtastic::GPSStatus *gpsStatus;
|
||||
287
src/Power.cpp
287
src/Power.cpp
@@ -1,27 +1,137 @@
|
||||
#include "power.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "main.h"
|
||||
#include "utils.h"
|
||||
#include "sleep.h"
|
||||
|
||||
#ifdef TBEAM_V10
|
||||
#include "utils.h"
|
||||
|
||||
// FIXME. nasty hack cleanup how we load axp192
|
||||
#undef AXP192_SLAVE_ADDRESS
|
||||
#include "axp20x.h"
|
||||
|
||||
#ifdef TBEAM_V10
|
||||
AXP20X_Class axp;
|
||||
#endif
|
||||
|
||||
bool pmu_irq = false;
|
||||
|
||||
Power *power;
|
||||
|
||||
bool Power::setup()
|
||||
using namespace meshtastic;
|
||||
|
||||
#if defined(NRF52_SERIES)
|
||||
/*
|
||||
* Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4,
|
||||
* 1/3, 1/2 or 1, meaning 3.6, 3.0, 2.4, 1.8, 1.2 or 0.6V for the ADC levels.
|
||||
*
|
||||
* External Reference is VDD/4, with an adjustable gain of 1, 2 or 4, meaning
|
||||
* VDD/4, VDD/2 or VDD for the ADC levels.
|
||||
*
|
||||
* Default settings are internal reference with 1/6 gain (GND..3.6V ADC range)
|
||||
*/
|
||||
#define AREF_VOLTAGE 3.6
|
||||
#else
|
||||
#define AREF_VOLTAGE 3.3
|
||||
#endif
|
||||
|
||||
/**
|
||||
* If this board has a battery level sensor, set this to a valid implementation
|
||||
*/
|
||||
static HasBatteryLevel *batteryLevel; // Default to NULL for no battery level sensor
|
||||
|
||||
/**
|
||||
* A simple battery level sensor that assumes the battery voltage is attached via a voltage-divider to an analog input
|
||||
*/
|
||||
class AnalogBatteryLevel : public HasBatteryLevel
|
||||
{
|
||||
/**
|
||||
* Battery state of charge, from 0 to 100 or -1 for unknown
|
||||
*
|
||||
* FIXME - use a lipo lookup table, the current % full is super wrong
|
||||
*/
|
||||
virtual int getBattPercentage()
|
||||
{
|
||||
float v = getBattVoltage() / 1000;
|
||||
|
||||
axp192Init();
|
||||
concurrency::PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device
|
||||
setPeriod(1);
|
||||
if (v < noBatVolt)
|
||||
return -1; // If voltage is super low assume no battery installed
|
||||
|
||||
return axp192_found;
|
||||
if (v > chargingVolt)
|
||||
return 0; // While charging we can't report % full on the battery
|
||||
|
||||
return 100 * (v - emptyVolt) / (fullVolt - emptyVolt);
|
||||
}
|
||||
|
||||
/**
|
||||
* The raw voltage of the batteryin millivolts or NAN if unknown
|
||||
*/
|
||||
virtual float getBattVoltage()
|
||||
{
|
||||
// Tested ttgo eink nrf52 board and the reported value is perfect
|
||||
// DEBUG_MSG("raw val %u", raw);
|
||||
return
|
||||
#ifdef BATTERY_PIN
|
||||
1000.0 * 2.0 * (AREF_VOLTAGE / 1024.0) * analogRead(BATTERY_PIN);
|
||||
#else
|
||||
NAN;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* return true if there is a battery installed in this unit
|
||||
*/
|
||||
virtual bool isBatteryConnect() { return getBattPercentage() != -1; }
|
||||
|
||||
/// If we see a battery voltage higher than physics allows - assume charger is pumping
|
||||
/// in power
|
||||
virtual bool isVBUSPlug() { return getBattVoltage() > chargingVolt; }
|
||||
|
||||
/// Assume charging if we have a battery and external power is connected.
|
||||
/// we can't be smart enough to say 'full'?
|
||||
virtual bool isChargeing() { return isBatteryConnect() && isVBUSPlug(); }
|
||||
|
||||
private:
|
||||
/// If we see a battery voltage higher than physics allows - assume charger is pumping
|
||||
/// in power
|
||||
const float fullVolt = 4.2, emptyVolt = 3.27, chargingVolt = 4.3, noBatVolt = 2.1;
|
||||
} analogLevel;
|
||||
|
||||
Power::Power() : OSThread("Power") {}
|
||||
|
||||
bool Power::analogInit()
|
||||
{
|
||||
#ifdef BATTERY_PIN
|
||||
DEBUG_MSG("Using analog input for battery level\n");
|
||||
|
||||
// disable any internal pullups
|
||||
pinMode(BATTERY_PIN, INPUT);
|
||||
|
||||
#ifndef NO_ESP32
|
||||
// ESP32 needs special analog stuff
|
||||
adcAttachPin(BATTERY_PIN);
|
||||
#endif
|
||||
#ifdef NRF52_SERIES
|
||||
analogReference(AR_INTERNAL); // 3.6V
|
||||
#endif
|
||||
|
||||
// adcStart(BATTERY_PIN);
|
||||
analogReadResolution(10); // Default of 12 is not very linear. Recommended to use 10 or 11 depending on needed resolution.
|
||||
batteryLevel = &analogLevel;
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Power::setup()
|
||||
{
|
||||
bool found = axp192Init();
|
||||
|
||||
if (!found) {
|
||||
found = analogInit();
|
||||
}
|
||||
enabled = found;
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/// Reads power status to powerStatus singleton.
|
||||
@@ -29,42 +139,86 @@ bool Power::setup()
|
||||
// TODO(girts): move this and other axp stuff to power.h/power.cpp.
|
||||
void Power::readPowerStatus()
|
||||
{
|
||||
bool hasBattery = axp.isBatteryConnect();
|
||||
int batteryVoltageMv = 0;
|
||||
uint8_t batteryChargePercent = 0;
|
||||
if (hasBattery) {
|
||||
batteryVoltageMv = axp.getBattVoltage();
|
||||
// If the AXP192 returns a valid battery percentage, use it
|
||||
if (axp.getBattPercentage() >= 0) {
|
||||
batteryChargePercent = axp.getBattPercentage();
|
||||
} else {
|
||||
// If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error
|
||||
// In that case, we compute an estimate of the charge percent based on maximum and minimum voltages defined in power.h
|
||||
batteryChargePercent = clamp((int)(((batteryVoltageMv - BAT_MILLIVOLTS_EMPTY) * 1e2) / (BAT_MILLIVOLTS_FULL - BAT_MILLIVOLTS_EMPTY)), 0, 100);
|
||||
if (batteryLevel) {
|
||||
bool hasBattery = batteryLevel->isBatteryConnect();
|
||||
int batteryVoltageMv = 0;
|
||||
int8_t batteryChargePercent = 0;
|
||||
if (hasBattery) {
|
||||
batteryVoltageMv = batteryLevel->getBattVoltage();
|
||||
// If the AXP192 returns a valid battery percentage, use it
|
||||
if (batteryLevel->getBattPercentage() >= 0) {
|
||||
batteryChargePercent = batteryLevel->getBattPercentage();
|
||||
} else {
|
||||
// If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error
|
||||
// In that case, we compute an estimate of the charge percent based on maximum and minimum voltages defined in
|
||||
// power.h
|
||||
batteryChargePercent =
|
||||
clamp((int)(((batteryVoltageMv - BAT_MILLIVOLTS_EMPTY) * 1e2) / (BAT_MILLIVOLTS_FULL - BAT_MILLIVOLTS_EMPTY)),
|
||||
0, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify any status instances that are observing us
|
||||
const PowerStatus powerStatus =
|
||||
PowerStatus(hasBattery ? OptTrue : OptFalse, batteryLevel->isVBUSPlug() ? OptTrue : OptFalse,
|
||||
batteryLevel->isChargeing() ? OptTrue : OptFalse, batteryVoltageMv, batteryChargePercent);
|
||||
DEBUG_MSG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d\n", powerStatus.getHasUSB(),
|
||||
powerStatus.getIsCharging(), powerStatus.getBatteryVoltageMv(), powerStatus.getBatteryChargePercent());
|
||||
newStatus.notifyObservers(&powerStatus);
|
||||
|
||||
// If we have a battery at all and it is less than 10% full, force deep sleep
|
||||
if (powerStatus.getHasBattery() && !powerStatus.getHasUSB() && batteryLevel->getBattVoltage() < MIN_BAT_MILLIVOLTS)
|
||||
powerFSM.trigger(EVENT_LOW_BATTERY);
|
||||
} else {
|
||||
// No power sensing on this board - tell everyone else we have no idea what is happening
|
||||
const PowerStatus powerStatus = PowerStatus(OptUnknown, OptUnknown, OptUnknown, -1, -1);
|
||||
newStatus.notifyObservers(&powerStatus);
|
||||
}
|
||||
|
||||
// Notify any status instances that are observing us
|
||||
const meshtastic::PowerStatus powerStatus = meshtastic::PowerStatus(hasBattery, axp.isVBUSPlug(), axp.isChargeing(), batteryVoltageMv, batteryChargePercent);
|
||||
newStatus.notifyObservers(&powerStatus);
|
||||
|
||||
// If we have a battery at all and it is less than 10% full, force deep sleep
|
||||
if (powerStatus.getHasBattery() && !powerStatus.getHasUSB() &&
|
||||
axp.getBattVoltage() < MIN_BAT_MILLIVOLTS)
|
||||
powerFSM.trigger(EVENT_LOW_BATTERY);
|
||||
}
|
||||
|
||||
void Power::doTask()
|
||||
int32_t Power::runOnce()
|
||||
{
|
||||
readPowerStatus();
|
||||
|
||||
// Only read once every 20 seconds once the power status for the app has been initialized
|
||||
if(statusHandler && statusHandler->isInitialized())
|
||||
setPeriod(1000 * 20);
|
||||
}
|
||||
#endif // TBEAM_V10
|
||||
|
||||
#ifdef AXP192_SLAVE_ADDRESS
|
||||
#ifdef TBEAM_V10
|
||||
// WE no longer use the IRQ line to wake the CPU (due to false wakes from sleep), but we do poll
|
||||
// the IRQ status by reading the registers over I2C
|
||||
axp.readIRQ();
|
||||
|
||||
if (axp.isVbusRemoveIRQ()) {
|
||||
DEBUG_MSG("USB unplugged\n");
|
||||
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
|
||||
}
|
||||
if (axp.isVbusPlugInIRQ()) {
|
||||
DEBUG_MSG("USB plugged In\n");
|
||||
powerFSM.trigger(EVENT_POWER_CONNECTED);
|
||||
}
|
||||
/*
|
||||
Other things we could check if we cared...
|
||||
|
||||
if (axp.isChargingIRQ()) {
|
||||
DEBUG_MSG("Battery start charging\n");
|
||||
}
|
||||
if (axp.isChargingDoneIRQ()) {
|
||||
DEBUG_MSG("Battery fully charged\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();
|
||||
#endif
|
||||
|
||||
// Only read once every 20 seconds once the power status for the app has been initialized
|
||||
return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the power manager chip
|
||||
*
|
||||
@@ -74,10 +228,13 @@ void Power::doTask()
|
||||
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 Power::axp192Init()
|
||||
bool Power::axp192Init()
|
||||
{
|
||||
#ifdef TBEAM_V10
|
||||
if (axp192_found) {
|
||||
if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS)) {
|
||||
batteryLevel = &axp;
|
||||
|
||||
DEBUG_MSG("AXP192 Begin PASS\n");
|
||||
|
||||
// axp.setChgLEDMode(LED_BLINK_4HZ);
|
||||
@@ -90,7 +247,7 @@ void Power::axp192Init()
|
||||
DEBUG_MSG("----------------------------------------\n");
|
||||
|
||||
axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio
|
||||
axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power
|
||||
// axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power - now turned on in setGpsPower
|
||||
axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON);
|
||||
axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON);
|
||||
axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON);
|
||||
@@ -122,9 +279,11 @@ void Power::axp192Init()
|
||||
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
||||
|
||||
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
|
||||
axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ |
|
||||
AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ,
|
||||
1);
|
||||
// we do not look for AXP202_CHARGING_FINISHED_IRQ & AXP202_CHARGING_IRQ because it occurs repeatedly while there is
|
||||
// no battery also it could cause inadvertent waking from light sleep just because the battery filled
|
||||
// we don't look for AXP202_BATT_REMOVED_IRQ because it occurs repeatedly while no battery installed
|
||||
// we don't look at AXP202_VBUS_REMOVED_IRQ because we don't have anything hooked to vbus
|
||||
axp.enableIRQ(AXP202_BATT_CONNECT_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ, 1);
|
||||
|
||||
axp.clearIRQ();
|
||||
#endif
|
||||
@@ -135,45 +294,9 @@ void Power::axp192Init()
|
||||
} else {
|
||||
DEBUG_MSG("AXP192 not found\n");
|
||||
}
|
||||
}
|
||||
|
||||
return axp192_found;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
|
||||
void Power::loop()
|
||||
{
|
||||
|
||||
#ifdef PMU_IRQ
|
||||
if (pmu_irq) {
|
||||
pmu_irq = false;
|
||||
axp.readIRQ();
|
||||
|
||||
DEBUG_MSG("pmu irq!\n");
|
||||
|
||||
if (axp.isChargingIRQ()) {
|
||||
DEBUG_MSG("Battery start charging\n");
|
||||
}
|
||||
if (axp.isChargingDoneIRQ()) {
|
||||
DEBUG_MSG("Battery fully charged\n");
|
||||
}
|
||||
if (axp.isVbusRemoveIRQ()) {
|
||||
DEBUG_MSG("USB unplugged\n");
|
||||
}
|
||||
if (axp.isVbusPlugInIRQ()) {
|
||||
DEBUG_MSG("USB plugged In\n");
|
||||
}
|
||||
if (axp.isBattPlugInIRQ()) {
|
||||
DEBUG_MSG("Battery inserted\n");
|
||||
}
|
||||
if (axp.isBattRemoveIRQ()) {
|
||||
DEBUG_MSG("Battery removed\n");
|
||||
}
|
||||
if (axp.isPEKShortPressIRQ()) {
|
||||
DEBUG_MSG("PEK short button press\n");
|
||||
}
|
||||
|
||||
readPowerStatus();
|
||||
axp.clearIRQ();
|
||||
}
|
||||
|
||||
#endif // T_BEAM_V10
|
||||
|
||||
}
|
||||
|
||||
143
src/PowerFSM.cpp
143
src/PowerFSM.cpp
@@ -4,25 +4,15 @@
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include "graphics/Screen.h"
|
||||
#include "main.h"
|
||||
#include "sleep.h"
|
||||
#include "target_specific.h"
|
||||
#include "timing.h"
|
||||
|
||||
static void sdsEnter()
|
||||
{
|
||||
/*
|
||||
|
||||
// Don't deepsleep if we have USB power or if the user as pressed a button recently
|
||||
// !isUSBPowered <- doesn't work yet because the axp192 isn't letting the battery fully charge when we are awake - FIXME
|
||||
if (timing::millis() - lastPressMs > radioConfig.preferences.mesh_sds_timeout_secs)
|
||||
{
|
||||
doDeepSleep(radioConfig.preferences.sds_secs);
|
||||
}
|
||||
*/
|
||||
|
||||
doDeepSleep(radioConfig.preferences.sds_secs * 1000LL);
|
||||
// FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw
|
||||
doDeepSleep(getPref_sds_secs() * 1000LL);
|
||||
}
|
||||
|
||||
#include "error.h"
|
||||
@@ -31,8 +21,8 @@ static uint32_t secsSlept;
|
||||
|
||||
static void lsEnter()
|
||||
{
|
||||
DEBUG_MSG("lsEnter begin, ls_secs=%u\n", radioConfig.preferences.ls_secs);
|
||||
screen.setOn(false);
|
||||
DEBUG_MSG("lsEnter begin, ls_secs=%u\n", getPref_ls_secs());
|
||||
screen->setOn(false);
|
||||
secsSlept = 0; // How long have we been sleeping this time
|
||||
|
||||
DEBUG_MSG("lsEnter end\n");
|
||||
@@ -40,13 +30,13 @@ static void lsEnter()
|
||||
|
||||
static void lsIdle()
|
||||
{
|
||||
// DEBUG_MSG("lsIdle begin ls_secs=%u\n", radioConfig.preferences.ls_secs);
|
||||
// DEBUG_MSG("lsIdle begin ls_secs=%u\n", getPref_ls_secs());
|
||||
|
||||
#ifndef NO_ESP32
|
||||
esp_sleep_source_t wakeCause = ESP_SLEEP_WAKEUP_UNDEFINED;
|
||||
|
||||
// Do we have more sleeping to do?
|
||||
if (secsSlept < radioConfig.preferences.ls_secs) {
|
||||
if (secsSlept < getPref_ls_secs()) {
|
||||
// Briefly come out of sleep long enough to blink the led once every few seconds
|
||||
uint32_t sleepTime = 30;
|
||||
|
||||
@@ -55,7 +45,8 @@ static void lsIdle()
|
||||
setLed(false); // Never leave led on while in light sleep
|
||||
wakeCause = doLightSleep(sleepTime * 1000LL);
|
||||
|
||||
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) {
|
||||
switch (wakeCause) {
|
||||
case ESP_SLEEP_WAKEUP_TIMER:
|
||||
// Normal case: timer expired, we should just go back to sleep ASAP
|
||||
|
||||
setLed(true); // briefly turn on led
|
||||
@@ -63,12 +54,15 @@ static void lsIdle()
|
||||
|
||||
secsSlept += sleepTime;
|
||||
// DEBUG_MSG("sleeping, flash led!\n");
|
||||
}
|
||||
if (wakeCause == ESP_SLEEP_WAKEUP_UART) {
|
||||
break;
|
||||
|
||||
case ESP_SLEEP_WAKEUP_UART:
|
||||
// Not currently used (because uart triggers in hw have problems)
|
||||
powerFSM.trigger(EVENT_SERIAL_CONNECTED);
|
||||
} else {
|
||||
// We woke for some other reason (button press, uart, device interrupt)
|
||||
break;
|
||||
|
||||
default:
|
||||
// We woke for some other reason (button press, device interrupt)
|
||||
// uint64_t status = esp_sleep_get_ext1_wakeup_status();
|
||||
DEBUG_MSG("wakeCause %d\n", wakeCause);
|
||||
|
||||
@@ -82,8 +76,10 @@ static void lsIdle()
|
||||
powerFSM.trigger(EVENT_PRESS);
|
||||
} else {
|
||||
// Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc)
|
||||
// we lie and say "wake timer" because the interrupt will be handled by the regular IRQ code
|
||||
powerFSM.trigger(EVENT_WAKE_TIMER);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Someone says we can't sleep now, so just save some power by sleeping the CPU for 100ms or so
|
||||
@@ -101,12 +97,12 @@ static void lsIdle()
|
||||
static void lsExit()
|
||||
{
|
||||
// setGPSPower(true); // restore GPS power
|
||||
gps->startLock();
|
||||
gps->forceWake(true);
|
||||
}
|
||||
|
||||
static void nbEnter()
|
||||
{
|
||||
screen.setOn(false);
|
||||
screen->setOn(false);
|
||||
setBluetoothEnable(false);
|
||||
|
||||
// FIXME - check if we already have packets for phone and immediately trigger EVENT_PACKETS_FOR_PHONE
|
||||
@@ -115,36 +111,50 @@ static void nbEnter()
|
||||
static void darkEnter()
|
||||
{
|
||||
setBluetoothEnable(true);
|
||||
screen.setOn(false);
|
||||
screen->setOn(false);
|
||||
}
|
||||
|
||||
static void serialEnter()
|
||||
{
|
||||
setBluetoothEnable(false);
|
||||
screen.setOn(true);
|
||||
screen->setOn(true);
|
||||
screen->print("Using API...\n");
|
||||
}
|
||||
|
||||
static void powerEnter()
|
||||
{
|
||||
screen->setOn(true);
|
||||
setBluetoothEnable(true);
|
||||
screen->print("Powered...\n");
|
||||
}
|
||||
|
||||
static void powerExit()
|
||||
{
|
||||
screen->setOn(true);
|
||||
setBluetoothEnable(true);
|
||||
screen->print("Unpowered...\n");
|
||||
}
|
||||
|
||||
static void onEnter()
|
||||
{
|
||||
screen.setOn(true);
|
||||
screen->setOn(true);
|
||||
setBluetoothEnable(true);
|
||||
|
||||
static uint32_t lastPingMs;
|
||||
|
||||
uint32_t now = timing::millis();
|
||||
uint32_t now = millis();
|
||||
|
||||
if (now - lastPingMs > 30 * 1000) { // if more than a minute since our last press, ask other nodes to update their state
|
||||
if (displayedNodeNum)
|
||||
service.sendNetworkPing(displayedNodeNum, true); // Refresh the currently displayed node
|
||||
lastPingMs = now;
|
||||
}
|
||||
}
|
||||
|
||||
static void wakeForPing() {}
|
||||
}
|
||||
|
||||
static void screenPress()
|
||||
{
|
||||
screen.onPress();
|
||||
screen->onPress();
|
||||
}
|
||||
|
||||
static void bootEnter() {}
|
||||
@@ -156,16 +166,25 @@ State stateDARK(darkEnter, NULL, NULL, "DARK");
|
||||
State stateSERIAL(serialEnter, NULL, NULL, "SERIAL");
|
||||
State stateBOOT(bootEnter, NULL, NULL, "BOOT");
|
||||
State stateON(onEnter, NULL, NULL, "ON");
|
||||
State statePOWER(powerEnter, NULL, powerExit, "POWER");
|
||||
Fsm powerFSM(&stateBOOT);
|
||||
|
||||
void PowerFSM_setup()
|
||||
{
|
||||
powerFSM.add_timed_transition(&stateBOOT, &stateON, 3 * 1000, NULL, "boot timeout");
|
||||
// If we are not a router and we already have AC power go to POWER state after init, otherwise go to ON
|
||||
// We assume routers might be powered all the time, but from a low current (solar) source
|
||||
bool isLowPower = radioConfig.preferences.is_low_power;
|
||||
bool hasPower = !isLowPower && powerStatus && powerStatus->getHasUSB();
|
||||
bool isRouter = radioConfig.preferences.is_router;
|
||||
DEBUG_MSG("PowerFSM init, USB power=%d\n", hasPower);
|
||||
powerFSM.add_timed_transition(&stateBOOT, hasPower ? &statePOWER : &stateON, 3 * 1000, NULL, "boot timeout");
|
||||
|
||||
powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WAKE_TIMER, wakeForPing, "Wake timer");
|
||||
// wake timer expired or a packet arrived
|
||||
// if we are a router node, we go to NB (no need for bluetooth) otherwise we go to DARK (so we can send message to phone)
|
||||
powerFSM.add_transition(&stateLS, isRouter ? &stateNB : &stateDARK, EVENT_WAKE_TIMER, NULL, "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 or dark 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");
|
||||
|
||||
@@ -173,7 +192,10 @@ void PowerFSM_setup()
|
||||
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(&statePOWER, &statePOWER, EVENT_PRESS, screenPress, "Press");
|
||||
powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, screenPress, "Press"); // reenter On to restart our timers
|
||||
powerFSM.add_transition(&stateSERIAL, &stateSERIAL, EVENT_PRESS, screenPress,
|
||||
"Press"); // Allow button to work while in serial API
|
||||
|
||||
// Handle critically low power battery by forcing deep sleep
|
||||
powerFSM.add_transition(&stateBOOT, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat");
|
||||
@@ -186,42 +208,61 @@ void PowerFSM_setup()
|
||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
|
||||
powerFSM.add_transition(&stateON, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
|
||||
|
||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||
powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||
// if we are a router we don't turn the screen on for these things
|
||||
if (!isRouter) {
|
||||
// show the latest node when we get a new node db update
|
||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||
powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||
|
||||
powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||
powerFSM.add_transition(&stateON, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); // restarts the sleep timer
|
||||
// Show the received text message
|
||||
powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||
powerFSM.add_transition(&stateON, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); // restarts the sleep timer
|
||||
}
|
||||
|
||||
powerFSM.add_transition(&stateLS, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||
powerFSM.add_transition(&stateNB, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||
powerFSM.add_transition(&stateDARK, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||
powerFSM.add_transition(&stateON, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||
|
||||
if (!isLowPower) {
|
||||
powerFSM.add_transition(&stateLS, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
||||
powerFSM.add_transition(&stateNB, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
||||
powerFSM.add_transition(&stateDARK, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
||||
powerFSM.add_transition(&stateON, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
||||
}
|
||||
|
||||
powerFSM.add_transition(&statePOWER, &stateON, EVENT_POWER_DISCONNECTED, NULL, "power disconnected");
|
||||
powerFSM.add_transition(&stateSERIAL, &stateON, EVENT_POWER_DISCONNECTED, NULL, "power disconnected");
|
||||
|
||||
powerFSM.add_transition(&stateSERIAL, &stateNB, EVENT_SERIAL_DISCONNECTED, NULL, "serial disconnect");
|
||||
|
||||
powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone");
|
||||
|
||||
powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone");
|
||||
|
||||
powerFSM.add_timed_transition(&stateON, &stateDARK, radioConfig.preferences.screen_on_secs * 1000, NULL, "Screen-on timeout");
|
||||
powerFSM.add_timed_transition(&stateON, &stateDARK, getPref_screen_on_secs() * 1000, NULL, "Screen-on timeout");
|
||||
|
||||
powerFSM.add_timed_transition(&stateDARK, &stateNB, radioConfig.preferences.phone_timeout_secs * 1000, NULL, "Phone timeout");
|
||||
// On most boards we use light-sleep to be our main state, but on NRF52 we just stay in DARK
|
||||
State *lowPowerState = &stateLS;
|
||||
|
||||
#ifndef NRF52_SERIES
|
||||
// We never enter light-sleep state on NRF52 (because the CPU uses so little power normally)
|
||||
powerFSM.add_timed_transition(&stateNB, &stateLS, radioConfig.preferences.min_wake_secs * 1000, NULL, "Min wake timeout");
|
||||
// We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally)
|
||||
|
||||
powerFSM.add_timed_transition(&stateDARK, &stateLS, radioConfig.preferences.wait_bluetooth_secs * 1000, NULL,
|
||||
"Bluetooth timeout");
|
||||
powerFSM.add_timed_transition(&stateDARK, &stateNB, getPref_phone_timeout_secs() * 1000, NULL, "Phone timeout");
|
||||
powerFSM.add_timed_transition(&stateNB, &stateLS, getPref_min_wake_secs() * 1000, NULL, "Min wake timeout");
|
||||
powerFSM.add_timed_transition(&stateDARK, &stateLS, getPref_wait_bluetooth_secs() * 1000, NULL, "Bluetooth timeout");
|
||||
#else
|
||||
lowPowerState = &stateDARK;
|
||||
#endif
|
||||
|
||||
powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.mesh_sds_timeout_secs * 1000, NULL,
|
||||
"mesh timeout");
|
||||
auto meshSds = getPref_mesh_sds_timeout_secs();
|
||||
if (meshSds != UINT32_MAX)
|
||||
powerFSM.add_timed_transition(lowPowerState, &stateSDS, meshSds * 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
|
||||
// powerFSM.add_timed_transition(lowPowerState, &stateSDS, getPref_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
|
||||
|
||||
@@ -13,10 +13,13 @@
|
||||
#define EVENT_BLUETOOTH_PAIR 7
|
||||
#define EVENT_NODEDB_UPDATED 8 // NodeDB has a big enough change that we think you should turn on the screen
|
||||
#define EVENT_CONTACT_FROM_PHONE 9 // the phone just talked to us over bluetooth
|
||||
#define EVENT_LOW_BATTERY 10 // Battery is critically low, go to sleep
|
||||
#define EVENT_LOW_BATTERY 10 // Battery is critically low, go to sleep
|
||||
#define EVENT_SERIAL_CONNECTED 11
|
||||
#define EVENT_SERIAL_DISCONNECTED 12
|
||||
#define EVENT_POWER_CONNECTED 13
|
||||
#define EVENT_POWER_DISCONNECTED 14
|
||||
|
||||
extern Fsm powerFSM;
|
||||
extern State statePOWER, stateSERIAL;
|
||||
|
||||
void PowerFSM_setup();
|
||||
|
||||
@@ -1,103 +1,94 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include "Status.h"
|
||||
#include "configuration.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace meshtastic {
|
||||
namespace meshtastic
|
||||
{
|
||||
|
||||
/// Describes the state of the GPS system.
|
||||
class PowerStatus : public Status
|
||||
/**
|
||||
* A boolean where we have a third state of Unknown
|
||||
*/
|
||||
enum OptionalBool { OptFalse = 0, OptTrue = 1, OptUnknown = 2 };
|
||||
|
||||
/// Describes the state of the GPS system.
|
||||
class PowerStatus : public Status
|
||||
{
|
||||
|
||||
private:
|
||||
CallbackObserver<PowerStatus, const PowerStatus *> statusObserver =
|
||||
CallbackObserver<PowerStatus, const PowerStatus *>(this, &PowerStatus::updateStatus);
|
||||
|
||||
/// Whether we have a battery connected
|
||||
OptionalBool hasBattery = OptUnknown;
|
||||
/// Battery voltage in mV, valid if haveBattery is true
|
||||
int batteryVoltageMv = 0;
|
||||
/// Battery charge percentage, either read directly or estimated
|
||||
int8_t batteryChargePercent = 0;
|
||||
/// Whether USB is connected
|
||||
OptionalBool hasUSB = OptUnknown;
|
||||
/// Whether we are charging the battery
|
||||
OptionalBool isCharging = OptUnknown;
|
||||
|
||||
public:
|
||||
PowerStatus() { statusType = STATUS_TYPE_POWER; }
|
||||
PowerStatus(OptionalBool hasBattery, OptionalBool hasUSB, OptionalBool isCharging, int batteryVoltageMv = -1,
|
||||
int8_t batteryChargePercent = 0)
|
||||
: Status()
|
||||
{
|
||||
this->hasBattery = hasBattery;
|
||||
this->hasUSB = hasUSB;
|
||||
this->isCharging = isCharging;
|
||||
this->batteryVoltageMv = batteryVoltageMv;
|
||||
this->batteryChargePercent = batteryChargePercent;
|
||||
}
|
||||
PowerStatus(const PowerStatus &);
|
||||
PowerStatus &operator=(const PowerStatus &);
|
||||
|
||||
private:
|
||||
CallbackObserver<PowerStatus, const PowerStatus *> statusObserver = CallbackObserver<PowerStatus, const PowerStatus *>(this, &PowerStatus::updateStatus);
|
||||
void observe(Observable<const PowerStatus *> *source) { statusObserver.observe(source); }
|
||||
|
||||
/// Whether we have a battery connected
|
||||
bool hasBattery;
|
||||
/// Battery voltage in mV, valid if haveBattery is true
|
||||
int batteryVoltageMv;
|
||||
/// Battery charge percentage, either read directly or estimated
|
||||
uint8_t batteryChargePercent;
|
||||
/// Whether USB is connected
|
||||
bool hasUSB;
|
||||
/// Whether we are charging the battery
|
||||
bool isCharging;
|
||||
bool getHasBattery() const { return hasBattery == OptTrue; }
|
||||
|
||||
public:
|
||||
bool getHasUSB() const { return hasUSB == OptTrue; }
|
||||
|
||||
PowerStatus() {
|
||||
statusType = STATUS_TYPE_POWER;
|
||||
}
|
||||
PowerStatus( bool hasBattery, bool hasUSB, bool isCharging, int batteryVoltageMv, uint8_t batteryChargePercent ) : Status()
|
||||
/// Can we even know if this board has USB power or not
|
||||
bool knowsUSB() const { return hasUSB != OptUnknown; }
|
||||
|
||||
bool getIsCharging() const { return isCharging == OptTrue; }
|
||||
|
||||
int getBatteryVoltageMv() const { return batteryVoltageMv; }
|
||||
|
||||
/**
|
||||
* Note: 0% battery means 'unknown/this board doesn't have a battery installed'
|
||||
*/
|
||||
uint8_t getBatteryChargePercent() const { return getHasBattery() ? batteryChargePercent : 0; }
|
||||
|
||||
bool matches(const PowerStatus *newStatus) const
|
||||
{
|
||||
return (newStatus->getHasBattery() != hasBattery || newStatus->getHasUSB() != hasUSB ||
|
||||
newStatus->getBatteryVoltageMv() != batteryVoltageMv);
|
||||
}
|
||||
int updateStatus(const PowerStatus *newStatus)
|
||||
{
|
||||
// Only update the status if values have actually changed
|
||||
bool isDirty;
|
||||
{
|
||||
this->hasBattery = hasBattery;
|
||||
this->hasUSB = hasUSB;
|
||||
this->isCharging = isCharging;
|
||||
this->batteryVoltageMv = batteryVoltageMv;
|
||||
this->batteryChargePercent = batteryChargePercent;
|
||||
isDirty = matches(newStatus);
|
||||
initialized = true;
|
||||
hasBattery = newStatus->hasBattery;
|
||||
batteryVoltageMv = newStatus->getBatteryVoltageMv();
|
||||
batteryChargePercent = newStatus->getBatteryChargePercent();
|
||||
hasUSB = newStatus->hasUSB;
|
||||
isCharging = newStatus->isCharging;
|
||||
}
|
||||
PowerStatus(const PowerStatus &);
|
||||
PowerStatus &operator=(const PowerStatus &);
|
||||
|
||||
void observe(Observable<const PowerStatus *> *source)
|
||||
{
|
||||
statusObserver.observe(source);
|
||||
if (isDirty) {
|
||||
// DEBUG_MSG("Battery %dmV %d%%\n", batteryVoltageMv, batteryChargePercent);
|
||||
onNewStatus.notifyObservers(this);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
bool getHasBattery() const
|
||||
{
|
||||
return hasBattery;
|
||||
}
|
||||
|
||||
bool getHasUSB() const
|
||||
{
|
||||
return hasUSB;
|
||||
}
|
||||
|
||||
bool getIsCharging() const
|
||||
{
|
||||
return isCharging;
|
||||
}
|
||||
|
||||
int getBatteryVoltageMv() const
|
||||
{
|
||||
return batteryVoltageMv;
|
||||
}
|
||||
|
||||
uint8_t getBatteryChargePercent() const
|
||||
{
|
||||
return batteryChargePercent;
|
||||
}
|
||||
|
||||
bool matches(const PowerStatus *newStatus) const
|
||||
{
|
||||
return (
|
||||
newStatus->getHasBattery() != hasBattery ||
|
||||
newStatus->getHasUSB() != hasUSB ||
|
||||
newStatus->getBatteryVoltageMv() != batteryVoltageMv
|
||||
);
|
||||
}
|
||||
int updateStatus(const PowerStatus *newStatus) {
|
||||
// Only update the status if values have actually changed
|
||||
bool isDirty;
|
||||
{
|
||||
isDirty = matches(newStatus);
|
||||
initialized = true;
|
||||
hasBattery = newStatus->getHasBattery();
|
||||
batteryVoltageMv = newStatus->getBatteryVoltageMv();
|
||||
batteryChargePercent = newStatus->getBatteryChargePercent();
|
||||
hasUSB = newStatus->getHasUSB();
|
||||
isCharging = newStatus->getIsCharging();
|
||||
}
|
||||
if(isDirty) {
|
||||
DEBUG_MSG("Battery %dmV %d%%\n", batteryVoltageMv, batteryChargePercent);
|
||||
onNewStatus.notifyObservers(this);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace meshtastic
|
||||
|
||||
extern meshtastic::PowerStatus *powerStatus;
|
||||
|
||||
11
src/SPILock.cpp
Normal file
11
src/SPILock.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#include "SPILock.h"
|
||||
#include <Arduino.h>
|
||||
#include <assert.h>
|
||||
|
||||
concurrency::Lock *spiLock;
|
||||
|
||||
void initSPI()
|
||||
{
|
||||
assert(!spiLock);
|
||||
spiLock = new concurrency::Lock();
|
||||
}
|
||||
12
src/SPILock.h
Normal file
12
src/SPILock.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "../concurrency/LockGuard.h"
|
||||
|
||||
/**
|
||||
* Used to provide mutual exclusion for access to the SPI bus. Usage:
|
||||
* concurrency::LockGuard g(spiLock);
|
||||
*/
|
||||
extern concurrency::Lock *spiLock;
|
||||
|
||||
/** Setup SPI access and create the spiLock lock. */
|
||||
void initSPI();
|
||||
@@ -40,6 +40,8 @@ void SerialConsole::onConnectionChanged(bool connected)
|
||||
if (connected) { // To prevent user confusion, turn off bluetooth while using the serial port api
|
||||
powerFSM.trigger(EVENT_SERIAL_CONNECTED);
|
||||
} else {
|
||||
// FIXME, we get no notice of serial going away, we should instead automatically generate this event if we haven't
|
||||
// received a packet in a while
|
||||
powerFSM.trigger(EVENT_SERIAL_DISCONNECTED);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace meshtastic
|
||||
CallbackObserver<Status, const Status *> statusObserver = CallbackObserver<Status, const Status *>(this, &Status::updateStatus);
|
||||
bool initialized = false;
|
||||
// Workaround for no typeid support
|
||||
int statusType;
|
||||
int statusType = 0;
|
||||
|
||||
public:
|
||||
// Allows us to generate observable events
|
||||
|
||||
39
src/concurrency/BinarySemaphoreFreeRTOS.cpp
Normal file
39
src/concurrency/BinarySemaphoreFreeRTOS.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "concurrency/BinarySemaphoreFreeRTOS.h"
|
||||
#include "configuration.h"
|
||||
|
||||
#ifdef HAS_FREE_RTOS
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
BinarySemaphoreFreeRTOS::BinarySemaphoreFreeRTOS()
|
||||
{
|
||||
semaphore = xSemaphoreCreateBinary();
|
||||
}
|
||||
|
||||
BinarySemaphoreFreeRTOS::~BinarySemaphoreFreeRTOS()
|
||||
{
|
||||
vSemaphoreDelete(semaphore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if we were interrupted
|
||||
*/
|
||||
bool BinarySemaphoreFreeRTOS::take(uint32_t msec)
|
||||
{
|
||||
return xSemaphoreTake(semaphore, pdMS_TO_TICKS(msec));
|
||||
}
|
||||
|
||||
void BinarySemaphoreFreeRTOS::give()
|
||||
{
|
||||
xSemaphoreGive(semaphore);
|
||||
}
|
||||
|
||||
IRAM_ATTR void BinarySemaphoreFreeRTOS::giveFromISR(BaseType_t *pxHigherPriorityTaskWoken)
|
||||
{
|
||||
xSemaphoreGiveFromISR(semaphore, pxHigherPriorityTaskWoken);
|
||||
}
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
#endif
|
||||
31
src/concurrency/BinarySemaphoreFreeRTOS.h
Normal file
31
src/concurrency/BinarySemaphoreFreeRTOS.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "configuration.h"
|
||||
#include "../freertosinc.h"
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
#ifdef HAS_FREE_RTOS
|
||||
|
||||
class BinarySemaphoreFreeRTOS
|
||||
{
|
||||
SemaphoreHandle_t semaphore;
|
||||
|
||||
public:
|
||||
BinarySemaphoreFreeRTOS();
|
||||
~BinarySemaphoreFreeRTOS();
|
||||
|
||||
/**
|
||||
* Returns false if we timed out
|
||||
*/
|
||||
bool take(uint32_t msec);
|
||||
|
||||
void give();
|
||||
|
||||
void giveFromISR(BaseType_t *pxHigherPriorityTaskWoken);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
36
src/concurrency/BinarySemaphorePosix.cpp
Normal file
36
src/concurrency/BinarySemaphorePosix.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#include "concurrency/BinarySemaphorePosix.h"
|
||||
#include "configuration.h"
|
||||
|
||||
#ifndef HAS_FREE_RTOS
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
BinarySemaphorePosix::BinarySemaphorePosix()
|
||||
{
|
||||
}
|
||||
|
||||
BinarySemaphorePosix::~BinarySemaphorePosix()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if we timed out
|
||||
*/
|
||||
bool BinarySemaphorePosix::take(uint32_t msec)
|
||||
{
|
||||
delay(msec); // FIXME
|
||||
return false;
|
||||
}
|
||||
|
||||
void BinarySemaphorePosix::give()
|
||||
{
|
||||
}
|
||||
|
||||
IRAM_ATTR void BinarySemaphorePosix::giveFromISR(BaseType_t *pxHigherPriorityTaskWoken)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
#endif
|
||||
31
src/concurrency/BinarySemaphorePosix.h
Normal file
31
src/concurrency/BinarySemaphorePosix.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "configuration.h"
|
||||
#include "../freertosinc.h"
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
#ifndef HAS_FREE_RTOS
|
||||
|
||||
class BinarySemaphorePosix
|
||||
{
|
||||
// SemaphoreHandle_t semaphore;
|
||||
|
||||
public:
|
||||
BinarySemaphorePosix();
|
||||
~BinarySemaphorePosix();
|
||||
|
||||
/**
|
||||
* Returns false if we timed out
|
||||
*/
|
||||
bool take(uint32_t msec);
|
||||
|
||||
void give();
|
||||
|
||||
void giveFromISR(BaseType_t *pxHigherPriorityTaskWoken);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
35
src/concurrency/InterruptableDelay.cpp
Normal file
35
src/concurrency/InterruptableDelay.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "concurrency/InterruptableDelay.h"
|
||||
#include "configuration.h"
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
InterruptableDelay::InterruptableDelay() {}
|
||||
|
||||
InterruptableDelay::~InterruptableDelay() {}
|
||||
|
||||
/**
|
||||
* Returns false if we were interrupted
|
||||
*/
|
||||
bool InterruptableDelay::delay(uint32_t msec)
|
||||
{
|
||||
// DEBUG_MSG("delay %u ", msec);
|
||||
|
||||
// sem take will return false if we timed out (i.e. were not interrupted)
|
||||
bool r = semaphore.take(msec);
|
||||
|
||||
// DEBUG_MSG("interrupt=%d\n", r);
|
||||
return !r;
|
||||
}
|
||||
|
||||
void InterruptableDelay::interrupt()
|
||||
{
|
||||
semaphore.give();
|
||||
}
|
||||
|
||||
IRAM_ATTR void InterruptableDelay::interruptFromISR(BaseType_t *pxHigherPriorityTaskWoken)
|
||||
{
|
||||
semaphore.giveFromISR(pxHigherPriorityTaskWoken);
|
||||
}
|
||||
|
||||
} // namespace concurrency
|
||||
42
src/concurrency/InterruptableDelay.h
Normal file
42
src/concurrency/InterruptableDelay.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "../freertosinc.h"
|
||||
|
||||
|
||||
#ifdef HAS_FREE_RTOS
|
||||
#include "concurrency/BinarySemaphoreFreeRTOS.h"
|
||||
#define BinarySemaphore BinarySemaphoreFreeRTOS
|
||||
#else
|
||||
#include "concurrency/BinarySemaphorePosix.h"
|
||||
#define BinarySemaphore BinarySemaphorePosix
|
||||
#endif
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
/**
|
||||
* An object that provides delay(msec) like functionality, but can be interrupted by calling interrupt().
|
||||
*
|
||||
* Useful for they top level loop() delay call to keep the CPU powered down until our next scheduled event or some external event.
|
||||
*
|
||||
* This is implmented for FreeRTOS but should be easy to port to other operating systems.
|
||||
*/
|
||||
class InterruptableDelay
|
||||
{
|
||||
BinarySemaphore semaphore;
|
||||
|
||||
public:
|
||||
InterruptableDelay();
|
||||
~InterruptableDelay();
|
||||
|
||||
/**
|
||||
* Returns false if we were interrupted
|
||||
*/
|
||||
bool delay(uint32_t msec);
|
||||
|
||||
void interrupt();
|
||||
|
||||
void interruptFromISR(BaseType_t *pxHigherPriorityTaskWoken);
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "Lock.h"
|
||||
#include <cassert>
|
||||
|
||||
namespace concurrency {
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
#ifdef HAS_FREE_RTOS
|
||||
Lock::Lock()
|
||||
{
|
||||
handle = xSemaphoreCreateBinary();
|
||||
@@ -19,5 +21,12 @@ void Lock::unlock()
|
||||
{
|
||||
assert(xSemaphoreGive(handle));
|
||||
}
|
||||
#else
|
||||
Lock::Lock() {}
|
||||
|
||||
void Lock::lock() {}
|
||||
|
||||
void Lock::unlock() {}
|
||||
#endif
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
#include "../freertosinc.h"
|
||||
|
||||
namespace concurrency {
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Simple wrapper around FreeRTOS API for implementing a mutex lock
|
||||
@@ -26,8 +27,9 @@ class Lock
|
||||
void unlock();
|
||||
|
||||
private:
|
||||
#ifdef HAS_FREE_RTOS
|
||||
SemaphoreHandle_t handle;
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
@@ -1,19 +1,85 @@
|
||||
#include "NotifiedWorkerThread.h"
|
||||
#include "configuration.h"
|
||||
#include <assert.h>
|
||||
|
||||
namespace concurrency {
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
static bool debugNotification;
|
||||
|
||||
/**
|
||||
* Notify this thread so it can run
|
||||
*/
|
||||
void NotifiedWorkerThread::notify(uint32_t v, eNotifyAction action)
|
||||
bool NotifiedWorkerThread::notify(uint32_t v, bool overwrite)
|
||||
{
|
||||
xTaskNotify(taskHandle, v, action);
|
||||
bool r = notifyCommon(v, overwrite);
|
||||
|
||||
if (r)
|
||||
mainDelay.interrupt();
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void NotifiedWorkerThread::block()
|
||||
/**
|
||||
* Notify this thread so it can run
|
||||
*/
|
||||
IRAM_ATTR bool NotifiedWorkerThread::notifyCommon(uint32_t v, bool overwrite)
|
||||
{
|
||||
xTaskNotifyWait(0, // don't clear notification on entry
|
||||
clearOnRead, ¬ification, portMAX_DELAY); // Wait forever
|
||||
if (overwrite || notification == 0) {
|
||||
enabled = true;
|
||||
setInterval(0); // Run ASAP
|
||||
|
||||
notification = v;
|
||||
if (debugNotification)
|
||||
DEBUG_MSG("setting notification %d\n", v);
|
||||
return true;
|
||||
} else {
|
||||
if (debugNotification)
|
||||
DEBUG_MSG("dropping notification %d\n", v);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace concurrency
|
||||
/**
|
||||
* Notify from an ISR
|
||||
*
|
||||
* This must be inline or IRAM_ATTR on ESP32
|
||||
*/
|
||||
IRAM_ATTR bool NotifiedWorkerThread::notifyFromISR(BaseType_t *highPriWoken, uint32_t v, bool overwrite)
|
||||
{
|
||||
bool r = notifyCommon(v, overwrite);
|
||||
if (r)
|
||||
mainDelay.interruptFromISR(highPriWoken);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a notification to fire in delay msecs
|
||||
*/
|
||||
bool NotifiedWorkerThread::notifyLater(uint32_t delay, uint32_t v, bool overwrite)
|
||||
{
|
||||
bool didIt = notify(v, overwrite);
|
||||
|
||||
if (didIt) { // If we didn't already have something queued, override the delay to be larger
|
||||
setIntervalFromNow(delay); // a new version of setInterval relative to the current time
|
||||
if (debugNotification)
|
||||
DEBUG_MSG("delaying notification %u\n", delay);
|
||||
}
|
||||
|
||||
return didIt;
|
||||
}
|
||||
|
||||
int32_t NotifiedWorkerThread::runOnce()
|
||||
{
|
||||
auto n = notification;
|
||||
enabled = false; // Only run once per notification
|
||||
notification = 0; // clear notification
|
||||
if (n) {
|
||||
onNotify(n);
|
||||
}
|
||||
|
||||
return RUN_SAME;
|
||||
}
|
||||
|
||||
} // namespace concurrency
|
||||
@@ -1,47 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "WorkerThread.h"
|
||||
#include "OSThread.h"
|
||||
|
||||
namespace concurrency {
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief A worker thread that waits on a freertos notification
|
||||
*/
|
||||
class NotifiedWorkerThread : public WorkerThread
|
||||
class NotifiedWorkerThread : public OSThread
|
||||
{
|
||||
/**
|
||||
* The notification that was most recently used to wake the thread. Read from runOnce()
|
||||
*/
|
||||
uint32_t notification = 0;
|
||||
|
||||
public:
|
||||
NotifiedWorkerThread(const char *name) : OSThread(name) {}
|
||||
|
||||
/**
|
||||
* Notify this thread so it can run
|
||||
*/
|
||||
void notify(uint32_t v = 0, eNotifyAction action = eNoAction);
|
||||
bool notify(uint32_t v, bool overwrite);
|
||||
|
||||
/**
|
||||
* Notify from an ISR
|
||||
*
|
||||
* This must be inline or IRAM_ATTR on ESP32
|
||||
*/
|
||||
inline void notifyFromISR(BaseType_t *highPriWoken, uint32_t v = 0, eNotifyAction action = eNoAction)
|
||||
{
|
||||
xTaskNotifyFromISR(taskHandle, v, action, highPriWoken);
|
||||
}
|
||||
bool notifyFromISR(BaseType_t *highPriWoken, uint32_t v, bool overwrite);
|
||||
|
||||
/**
|
||||
* Schedule a notification to fire in delay msecs
|
||||
*/
|
||||
bool notifyLater(uint32_t delay, uint32_t v, bool overwrite);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* The notification that was most recently used to wake the thread. Read from loop()
|
||||
*/
|
||||
uint32_t notification = 0;
|
||||
virtual void onNotify(uint32_t notification) = 0;
|
||||
|
||||
/**
|
||||
* What notification bits should be cleared just after we read and return them in notification?
|
||||
*
|
||||
* Defaults to clear all of them.
|
||||
*/
|
||||
uint32_t clearOnRead = UINT32_MAX;
|
||||
virtual int32_t runOnce();
|
||||
|
||||
private:
|
||||
/**
|
||||
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
|
||||
* Notify this thread so it can run
|
||||
*/
|
||||
virtual void block();
|
||||
bool notifyCommon(uint32_t v, bool overwrite);
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
79
src/concurrency/OSThread.cpp
Normal file
79
src/concurrency/OSThread.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#include "OSThread.h"
|
||||
#include "configuration.h"
|
||||
#include <assert.h>
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
/// Show debugging info for disabled threads
|
||||
bool OSThread::showDisabled;
|
||||
|
||||
/// Show debugging info for threads when we run them
|
||||
bool OSThread::showRun = false;
|
||||
|
||||
/// Show debugging info for threads we decide not to run;
|
||||
bool OSThread::showWaiting = false;
|
||||
|
||||
ThreadController mainController, timerController;
|
||||
InterruptableDelay mainDelay;
|
||||
|
||||
void OSThread::setup()
|
||||
{
|
||||
mainController.ThreadName = "mainController";
|
||||
timerController.ThreadName = "timerController";
|
||||
}
|
||||
|
||||
OSThread::OSThread(const char *_name, uint32_t period, ThreadController *_controller)
|
||||
: Thread(NULL, period), controller(_controller)
|
||||
{
|
||||
ThreadName = _name;
|
||||
|
||||
if (controller)
|
||||
controller->add(this);
|
||||
}
|
||||
|
||||
OSThread::~OSThread()
|
||||
{
|
||||
if (controller)
|
||||
controller->remove(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait a specified number msecs starting from the current time (rather than the last time we were run)
|
||||
*/
|
||||
void OSThread::setIntervalFromNow(unsigned long _interval)
|
||||
{
|
||||
// Save interval
|
||||
interval = _interval;
|
||||
|
||||
// Cache the next run based on the last_run
|
||||
_cached_next_run = millis() + interval;
|
||||
}
|
||||
|
||||
bool OSThread::shouldRun(unsigned long time)
|
||||
{
|
||||
bool r = Thread::shouldRun(time);
|
||||
|
||||
if (showRun && r)
|
||||
DEBUG_MSG("Thread %s: run\n", ThreadName.c_str());
|
||||
|
||||
if (showWaiting && enabled && !r)
|
||||
DEBUG_MSG("Thread %s: wait %lu\n", ThreadName.c_str(), interval);
|
||||
|
||||
if (showDisabled && !enabled)
|
||||
DEBUG_MSG("Thread %s: disabled\n", ThreadName.c_str());
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void OSThread::run()
|
||||
{
|
||||
auto newDelay = runOnce();
|
||||
|
||||
runned();
|
||||
|
||||
if (newDelay >= 0)
|
||||
setInterval(newDelay);
|
||||
}
|
||||
|
||||
} // namespace concurrency
|
||||
70
src/concurrency/OSThread.h
Normal file
70
src/concurrency/OSThread.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "Thread.h"
|
||||
#include "ThreadController.h"
|
||||
#include "concurrency/InterruptableDelay.h"
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
extern ThreadController mainController, timerController;
|
||||
extern InterruptableDelay mainDelay;
|
||||
|
||||
#define RUN_SAME -1
|
||||
|
||||
/**
|
||||
* @brief Base threading
|
||||
*
|
||||
* This is a pseudo threading layer that is super easy to port, well suited to our slow network and very ram & power efficient.
|
||||
*
|
||||
* TODO FIXME @geeksville
|
||||
*
|
||||
* move more things into OSThreads
|
||||
* remove lock/lockguard
|
||||
*
|
||||
* move typedQueue into concurrency
|
||||
* remove freertos from typedqueue
|
||||
*/
|
||||
class OSThread : public Thread
|
||||
{
|
||||
ThreadController *controller;
|
||||
|
||||
/// Show debugging info for disabled threads
|
||||
static bool showDisabled;
|
||||
|
||||
/// Show debugging info for threads when we run them
|
||||
static bool showRun;
|
||||
|
||||
/// Show debugging info for threads we decide not to run;
|
||||
static bool showWaiting;
|
||||
|
||||
public:
|
||||
OSThread(const char *name, uint32_t period = 0, ThreadController *controller = &mainController);
|
||||
|
||||
virtual ~OSThread();
|
||||
|
||||
virtual bool shouldRun(unsigned long time);
|
||||
|
||||
static void setup();
|
||||
|
||||
/**
|
||||
* Wait a specified number msecs starting from the current time (rather than the last time we were run)
|
||||
*/
|
||||
void setIntervalFromNow(unsigned long _interval);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* The method that will be called each time our thread gets a chance to run
|
||||
*
|
||||
* Returns desired period for next invocation (or RUN_SAME for no change)
|
||||
*/
|
||||
virtual int32_t runOnce() = 0;
|
||||
|
||||
// Do not override this
|
||||
virtual void run();
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
@@ -1,26 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "PeriodicTask.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
|
||||
namespace concurrency {
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Periodically invoke a callback. This just provides C-style callback conventions
|
||||
* @brief Periodically invoke a callback. This just provides C-style callback conventions
|
||||
* rather than a virtual function - FIXME, remove?
|
||||
*/
|
||||
class Periodic : public PeriodicTask
|
||||
class Periodic : public OSThread
|
||||
{
|
||||
uint32_t (*callback)();
|
||||
int32_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) {}
|
||||
Periodic(const char *name, int32_t (*_callback)()) : OSThread(name), callback(_callback) {}
|
||||
|
||||
protected:
|
||||
void doTask() {
|
||||
uint32_t p = callback();
|
||||
setPeriod(p);
|
||||
}
|
||||
int32_t runOnce() { return callback(); }
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
#include "PeriodicScheduler.h"
|
||||
#include "PeriodicTask.h"
|
||||
#include "LockGuard.h"
|
||||
#include "../timing.h"
|
||||
|
||||
namespace concurrency {
|
||||
|
||||
/// call this from loop
|
||||
void PeriodicScheduler::loop()
|
||||
{
|
||||
LockGuard lg(&lock);
|
||||
|
||||
uint32_t now = timing::millis();
|
||||
for (auto t : tasks) {
|
||||
if (t->period && (now - t->lastMsec) >= t->period) {
|
||||
|
||||
t->doTask();
|
||||
t->lastMsec = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PeriodicScheduler::schedule(PeriodicTask *t)
|
||||
{
|
||||
LockGuard lg(&lock);
|
||||
tasks.insert(t);
|
||||
}
|
||||
|
||||
void PeriodicScheduler::unschedule(PeriodicTask *t)
|
||||
{
|
||||
LockGuard lg(&lock);
|
||||
tasks.erase(t);
|
||||
}
|
||||
|
||||
} // namespace concurrency
|
||||
@@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Lock.h"
|
||||
#include <cstdint>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace concurrency {
|
||||
|
||||
class PeriodicTask;
|
||||
|
||||
/**
|
||||
* @brief Runs all PeriodicTasks in the system. Currently called from main loop()
|
||||
* but eventually should be its own thread blocked on a freertos timer.
|
||||
*/
|
||||
class PeriodicScheduler
|
||||
{
|
||||
friend class PeriodicTask;
|
||||
|
||||
/**
|
||||
* This really should be some form of heap, and when the period gets changed on a task it should get
|
||||
* rescheduled in that heap. Currently it is just a dumb array and everytime we run loop() we check
|
||||
* _every_ tasks. If it was a heap we'd only have to check the first task.
|
||||
*/
|
||||
std::unordered_set<PeriodicTask *> tasks;
|
||||
|
||||
// Protects the above variables.
|
||||
Lock lock;
|
||||
|
||||
public:
|
||||
/// Run any next tasks which are due for execution
|
||||
void loop();
|
||||
|
||||
private:
|
||||
void schedule(PeriodicTask *t);
|
||||
void unschedule(PeriodicTask *t);
|
||||
};
|
||||
|
||||
extern PeriodicScheduler periodicScheduler;
|
||||
|
||||
} // namespace concurrency
|
||||
@@ -1,16 +0,0 @@
|
||||
#include "PeriodicTask.h"
|
||||
#include "Periodic.h"
|
||||
#include "LockGuard.h"
|
||||
|
||||
namespace concurrency {
|
||||
|
||||
PeriodicScheduler periodicScheduler;
|
||||
|
||||
PeriodicTask::PeriodicTask(uint32_t initialPeriod) : period(initialPeriod) {}
|
||||
|
||||
void PeriodicTask::setup()
|
||||
{
|
||||
periodicScheduler.schedule(this);
|
||||
}
|
||||
|
||||
} // namespace concurrency
|
||||
@@ -1,56 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "PeriodicScheduler.h"
|
||||
#include "timing.h"
|
||||
|
||||
namespace concurrency {
|
||||
|
||||
/**
|
||||
* @brief A base class for tasks that want their doTask() method invoked periodically
|
||||
*
|
||||
* @todo 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.
|
||||
*/
|
||||
class PeriodicTask
|
||||
{
|
||||
friend class PeriodicScheduler;
|
||||
|
||||
uint32_t lastMsec = 0;
|
||||
uint32_t period = 1; // call soon after creation
|
||||
|
||||
public:
|
||||
virtual ~PeriodicTask() { periodicScheduler.unschedule(this); }
|
||||
|
||||
/**
|
||||
* Constructor (will schedule with the global PeriodicScheduler)
|
||||
*/
|
||||
PeriodicTask(uint32_t initialPeriod = 1);
|
||||
|
||||
/**
|
||||
* MUST be be called once at startup (but after threading is running - i.e. not from a constructor)
|
||||
*/
|
||||
void setup();
|
||||
|
||||
/**
|
||||
* Set a new period in msecs (can be called from doTask or elsewhere and the scheduler will cope)
|
||||
* While zero this task is disabled and will not run
|
||||
*/
|
||||
void setPeriod(uint32_t p)
|
||||
{
|
||||
lastMsec = timing::millis(); // reset starting from now
|
||||
period = p;
|
||||
}
|
||||
|
||||
uint32_t getPeriod() const { return period; }
|
||||
|
||||
/**
|
||||
* Syntatic sugar for suspending tasks
|
||||
*/
|
||||
void disable() { setPeriod(0); }
|
||||
|
||||
protected:
|
||||
virtual void doTask() = 0;
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
@@ -1,46 +0,0 @@
|
||||
#include "Thread.h"
|
||||
#include "timing.h"
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include "esp_task_wdt.h"
|
||||
#endif
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
void Thread::start(const char *name, size_t stackSize, uint32_t priority)
|
||||
{
|
||||
auto r = xTaskCreate(callRun, name, stackSize, this, priority, &taskHandle);
|
||||
assert(r == pdPASS);
|
||||
}
|
||||
|
||||
void Thread::callRun(void *_this)
|
||||
{
|
||||
((Thread *)_this)->doRun();
|
||||
}
|
||||
|
||||
void Thread::serviceWatchdog()
|
||||
{
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
esp_task_wdt_reset();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Thread::startWatchdog()
|
||||
{
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
auto r = esp_task_wdt_add(taskHandle);
|
||||
assert(r == ESP_OK);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Thread::stopWatchdog()
|
||||
{
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
auto r = esp_task_wdt_delete(taskHandle);
|
||||
assert(r == ESP_OK);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace concurrency
|
||||
@@ -1,47 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "freertosinc.h"
|
||||
|
||||
namespace concurrency {
|
||||
|
||||
/**
|
||||
* @brief Base threading
|
||||
*/
|
||||
class Thread
|
||||
{
|
||||
protected:
|
||||
TaskHandle_t taskHandle = NULL;
|
||||
|
||||
/**
|
||||
* set this to true to ask thread to cleanly exit asap
|
||||
*/
|
||||
volatile bool wantExit = false;
|
||||
|
||||
public:
|
||||
void start(const char *name, size_t stackSize = 1024, uint32_t priority = tskIDLE_PRIORITY);
|
||||
|
||||
virtual ~Thread() { vTaskDelete(taskHandle); }
|
||||
|
||||
uint32_t getStackHighwaterMark() { return uxTaskGetStackHighWaterMark(taskHandle); }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* The method that will be called when start is called.
|
||||
*/
|
||||
virtual void doRun() = 0;
|
||||
|
||||
/**
|
||||
* All thread run methods must periodically call serviceWatchdog, or the system will declare them hung and panic.
|
||||
*
|
||||
* this only applies after startWatchdog() has been called. If you need to sleep for a long time call stopWatchdog()
|
||||
*/
|
||||
void serviceWatchdog();
|
||||
void startWatchdog();
|
||||
void stopWatchdog();
|
||||
|
||||
private:
|
||||
static void callRun(void *_this);
|
||||
};
|
||||
|
||||
|
||||
} // namespace concurrency
|
||||
@@ -1,32 +0,0 @@
|
||||
#include "WorkerThread.h"
|
||||
#include "timing.h"
|
||||
|
||||
namespace concurrency {
|
||||
|
||||
void WorkerThread::doRun()
|
||||
{
|
||||
startWatchdog();
|
||||
|
||||
while (!wantExit) {
|
||||
stopWatchdog();
|
||||
block();
|
||||
startWatchdog();
|
||||
|
||||
// no need - startWatchdog is guaranteed to give us one full watchdog interval
|
||||
// serviceWatchdog(); // Let our loop worker have one full watchdog interval (at least) to run
|
||||
|
||||
#ifdef DEBUG_STACK
|
||||
static uint32_t lastPrint = 0;
|
||||
if (timing::millis() - lastPrint > 10 * 1000L) {
|
||||
lastPrint = timing::millis();
|
||||
meshtastic::printThreadInfo("net");
|
||||
}
|
||||
#endif
|
||||
|
||||
loop();
|
||||
}
|
||||
|
||||
stopWatchdog();
|
||||
}
|
||||
|
||||
} // namespace concurrency
|
||||
@@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Thread.h"
|
||||
|
||||
namespace concurrency {
|
||||
|
||||
/**
|
||||
* @brief This wraps threading (FreeRTOS for now) with a blocking API intended for efficiently converting
|
||||
* old-school arduino loop() code. Use as a mixin base class for the classes you want to convert.
|
||||
*
|
||||
* @link https://www.freertos.org/RTOS_Task_Notification_As_Mailbox.html
|
||||
*/
|
||||
class WorkerThread : public Thread
|
||||
{
|
||||
protected:
|
||||
/**
|
||||
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
|
||||
*/
|
||||
virtual void block() = 0;
|
||||
|
||||
virtual void loop() = 0;
|
||||
|
||||
/**
|
||||
* The method that will be called when start is called.
|
||||
*/
|
||||
virtual void doRun();
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
@@ -55,24 +55,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/// Convert a preprocessor name into a quoted string and if that string is empty use "unset"
|
||||
#define optstr(s) (xstr(s)[0] ? xstr(s) : "unset")
|
||||
|
||||
#ifdef NRF52_SERIES // All of the NRF52 targets are configured using variant.h, so this section shouldn't need to be
|
||||
// board specific
|
||||
#ifdef PORTDUINO
|
||||
|
||||
#define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth)
|
||||
|
||||
#elif defined(NRF52_SERIES) // All of the NRF52 targets are configured using variant.h, so this section shouldn't need to be
|
||||
// board specific
|
||||
|
||||
//
|
||||
// Standard definitions for NRF52 targets
|
||||
//
|
||||
|
||||
// Nop definition for these attributes - not used on NRF52
|
||||
#define EXT_RAM_ATTR
|
||||
#define IRAM_ATTR
|
||||
|
||||
#define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth)
|
||||
|
||||
// We bind to the GPS using variant.h instead for this platform (Serial1)
|
||||
|
||||
// FIXME, not yet ready for NRF52
|
||||
#define RTC_DATA_ATTR
|
||||
|
||||
#define LED_PIN PIN_LED1 // LED1 on nrf52840-DK
|
||||
|
||||
// If the variant filed defines as standard button
|
||||
@@ -80,6 +77,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define BUTTON_PIN PIN_BUTTON1
|
||||
#endif
|
||||
|
||||
#ifdef PIN_BUTTON2
|
||||
#define BUTTON_PIN_ALT PIN_BUTTON2
|
||||
#endif
|
||||
|
||||
// FIXME, use variant.h defs for all of this!!! (even on the ESP32 targets)
|
||||
#elif defined(CubeCell_BoardPlus)
|
||||
|
||||
@@ -89,9 +90,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth)
|
||||
|
||||
// FIXME, not yet ready for NRF52
|
||||
#define RTC_DATA_ATTR
|
||||
|
||||
#define LED_PIN -1 // FIXME totally bogus
|
||||
#define BUTTON_PIN -1
|
||||
|
||||
@@ -101,6 +99,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// Standard definitions for ESP32 targets
|
||||
//
|
||||
|
||||
#define HAS_WIFI
|
||||
|
||||
#define GPS_SERIAL_NUM 1
|
||||
#define GPS_RX_PIN 34
|
||||
#ifdef USE_JTAG
|
||||
@@ -123,11 +123,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#endif
|
||||
|
||||
//
|
||||
// Standard definitions for !ESP32 targets
|
||||
//
|
||||
|
||||
#ifdef NO_ESP32
|
||||
// Nop definition for these attributes - not used on NRF52
|
||||
#define EXT_RAM_ATTR
|
||||
#define IRAM_ATTR
|
||||
#define RTC_DATA_ATTR
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// OLED
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#define SSD1306_ADDRESS 0x3C
|
||||
#define ST7567_ADDRESS 0x3F
|
||||
|
||||
// The SH1106 controller is almost, but not quite, the same as SSD1306
|
||||
// Define this if you know you have that controller or your "SSD1306" misbehaves.
|
||||
@@ -135,10 +147,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// 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
|
||||
#define SCREEN_FLIP_VERTICALLY
|
||||
|
||||
// DEBUG LED
|
||||
#define LED_INVERTED 0 // define as 1 if LED is active low (on)
|
||||
// Define if screen should be mirrored left to right
|
||||
// #define SCREEN_MIRROR
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// GPS
|
||||
@@ -158,16 +170,33 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define BUTTON_PIN 38 // The middle button GPIO on the T-Beam
|
||||
#define BUTTON_PIN_ALT 13 // Alternate GPIO for an external button if needed
|
||||
|
||||
#ifndef USE_JTAG
|
||||
#define RF95_RESET 14
|
||||
#define LED_INVERTED 1
|
||||
#define LED_PIN 4 // Newer tbeams (1.1) have an extra led on GPIO4
|
||||
|
||||
// TTGO uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if
|
||||
// not found then probe for SX1262
|
||||
#define USE_RF95
|
||||
#define USE_SX1262
|
||||
|
||||
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
||||
#define LORA_RESET 23
|
||||
#define LORA_DIO1 33 // SX1262 IRQ
|
||||
#define LORA_DIO2 32 // SX1262 BUSY
|
||||
#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled
|
||||
|
||||
#ifdef USE_SX1262
|
||||
#define SX1262_CS RF95_NSS // FIXME - we really should define LORA_CS instead
|
||||
#define SX1262_DIO1 LORA_DIO1
|
||||
#define SX1262_BUSY LORA_DIO2
|
||||
#define SX1262_RESET LORA_RESET
|
||||
#define SX1262_E22 // Not really an E22 but TTGO seems to be trying to clone that
|
||||
// Internally the TTGO module hooks the SX1262-DIO2 in to control the TX/RX switch (which is the default for the sx1262interface
|
||||
// code)
|
||||
#endif
|
||||
#define RF95_IRQ 26
|
||||
#define RF95_DIO1 33 // Note: not really used on this board
|
||||
#define RF95_DIO2 32 // Note: not really used on this board
|
||||
|
||||
// Leave undefined to disable our PMU IRQ handler
|
||||
#define PMU_IRQ 35
|
||||
|
||||
// Leave undefined to disable our PMU IRQ handler. DO NOT ENABLE THIS because the pmuirq can cause sperious interrupts
|
||||
// and waking from light sleep
|
||||
// #define PMU_IRQ 35
|
||||
#define AXP192_SLAVE_ADDRESS 0x34
|
||||
|
||||
#elif defined(TBEAM_V07)
|
||||
@@ -180,13 +209,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define I2C_SCL 22
|
||||
|
||||
#define BUTTON_PIN 39
|
||||
#define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
|
||||
|
||||
#ifndef USE_JTAG
|
||||
#define RF95_RESET 23
|
||||
#endif
|
||||
#define RF95_IRQ 26
|
||||
#define RF95_DIO1 33 // Note: not really used on this board
|
||||
#define RF95_DIO2 32 // Note: not really used on this board
|
||||
#define USE_RF95
|
||||
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
||||
#define LORA_RESET 23
|
||||
#define LORA_DIO1 33 // Not really used
|
||||
#define LORA_DIO2 32 // Not really used
|
||||
|
||||
// This board has different GPS pins than all other boards
|
||||
#undef GPS_RX_PIN
|
||||
@@ -216,12 +245,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#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
|
||||
|
||||
#define USE_RF95
|
||||
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
||||
#ifndef USE_JTAG
|
||||
#define RF95_RESET 14 // If defined, this pin will be used to reset the LORA radio
|
||||
#define LORA_RESET 14
|
||||
#endif
|
||||
#define RF95_IRQ 26
|
||||
#define RF95_DIO1 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
|
||||
#define RF95_DIO2 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
|
||||
#define LORA_DIO1 35 // Not really used
|
||||
#define LORA_DIO2 34 // Not really used
|
||||
|
||||
#elif defined(TLORA_V1)
|
||||
// This string must exactly match the case used in release file names or the android updater won't work
|
||||
#define HW_VENDOR "tlora-v1"
|
||||
@@ -238,11 +269,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// #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 BUTTON_NEED_PULLUP
|
||||
|
||||
#define RF95_RESET 14 // If defined, this pin will be used to reset the LORA radio
|
||||
#define RF95_IRQ 26 // IRQ line for the LORA radio
|
||||
#define RF95_DIO1 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
|
||||
#define RF95_DIO2 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
|
||||
#define USE_RF95
|
||||
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
||||
#define LORA_RESET 14
|
||||
#define LORA_DIO1 35 // Not really used
|
||||
#define LORA_DIO2 34 // Not really used
|
||||
|
||||
#elif defined(TLORA_V2)
|
||||
// This string must exactly match the case used in release file names or the android updater won't work
|
||||
@@ -263,11 +296,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#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 BUTTON_NEED_PULLUP
|
||||
|
||||
#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
|
||||
#define USE_RF95
|
||||
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
||||
#define LORA_RESET 14
|
||||
#define LORA_DIO1 35 // Not really used
|
||||
#define LORA_DIO2 34 // Not really used
|
||||
|
||||
#elif defined(TLORA_V2_1_16)
|
||||
// This string must exactly match the case used in release file names or the android updater won't work
|
||||
@@ -278,6 +313,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define GPS_RX_PIN 36
|
||||
#define GPS_TX_PIN 39
|
||||
|
||||
#define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
|
||||
|
||||
#define I2C_SDA 21 // I2C pins for this board
|
||||
#define I2C_SCL 22
|
||||
|
||||
@@ -288,11 +325,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define BUTTON_PIN \
|
||||
12 // 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 BUTTON_NEED_PULLUP
|
||||
|
||||
#define USE_RF95
|
||||
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
||||
#define LORA_RESET 14
|
||||
#define LORA_DIO1 35 // Not really used
|
||||
#define LORA_DIO2 34 // Not really used
|
||||
|
||||
#define RF95_RESET 14 // If defined, this pin will be used to reset the LORA radio
|
||||
#define RF95_IRQ 26 // IRQ line for the LORA radio
|
||||
#define RF95_DIO1 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
|
||||
#define RF95_DIO2 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_NRF52840_PCA10056
|
||||
@@ -312,6 +352,36 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#define HW_VENDOR "nrf52unknown" // FIXME - unknown nrf52 board
|
||||
|
||||
#elif PORTDUINO
|
||||
|
||||
#define HW_VENDOR "portduino"
|
||||
|
||||
#define USE_SIM_RADIO
|
||||
|
||||
#define USE_RF95
|
||||
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
||||
#define LORA_RESET 23
|
||||
#define LORA_DIO1 33 // Not really used
|
||||
#define LORA_DIO2 32 // Not really used
|
||||
|
||||
// Fake SPI device selections
|
||||
#define RF95_SCK 5
|
||||
#define RF95_MISO 19
|
||||
#define RF95_MOSI 27
|
||||
#define RF95_NSS 18
|
||||
|
||||
#endif
|
||||
|
||||
// DEBUG LED
|
||||
#ifndef LED_INVERTED
|
||||
#define LED_INVERTED 0 // define as 1 if LED is active low (on)
|
||||
#endif
|
||||
|
||||
#ifdef USE_RF95
|
||||
#define RF95_RESET LORA_RESET
|
||||
#define RF95_IRQ LORA_DIO0 // on SX1262 version this is a no connect DIO0
|
||||
#define RF95_DIO1 LORA_DIO1 // Note: not really used for RF95
|
||||
#define RF95_DIO2 LORA_DIO2 // Note: not really used for RF95
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -337,8 +407,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// Debug printing to segger console
|
||||
#define SEGGER_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__)
|
||||
|
||||
// nrf52 gets its settings via variant files
|
||||
#ifndef PIN_SERIAL_RX
|
||||
// If we are not on a NRF52840 (which has built in USB-ACM serial support) and we don't have serial pins hooked up, then we MUST
|
||||
// use SEGGER for debug output
|
||||
#if !defined(PIN_SERIAL_RX) && !defined(NRF52840_XXAA)
|
||||
// No serial ports on this board - ONLY use segger in memory console
|
||||
#define USE_SEGGER
|
||||
#endif
|
||||
@@ -363,3 +434,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#define GPS_POWER_CTRL_CH 3
|
||||
#define LORA_POWER_CTRL_CH 2
|
||||
|
||||
// Default Bluetooth PIN
|
||||
#define defaultBLEPin 123456
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "../concurrency/LockGuard.h"
|
||||
#include "../timing.h"
|
||||
#include "BluetoothSoftwareUpdate.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "RadioLibInterface.h"
|
||||
@@ -31,6 +30,8 @@ int update_size_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_
|
||||
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR && updateExpectedSize != 0) {
|
||||
updateActualSize = 0;
|
||||
crc.reset();
|
||||
if (Update.isRunning())
|
||||
Update.abort();
|
||||
bool canBegin = Update.begin(updateExpectedSize);
|
||||
DEBUG_MSG("Setting update size %u, result %d\n", updateExpectedSize, canBegin);
|
||||
if (!canBegin) {
|
||||
@@ -71,7 +72,7 @@ int update_data_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_
|
||||
crc.update(data, len);
|
||||
Update.write(data, len);
|
||||
updateActualSize += len;
|
||||
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG); // Not exactly correct, but we want to force the device to not sleep now
|
||||
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -100,7 +101,7 @@ int update_crc32_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble
|
||||
} else {
|
||||
if (Update.end()) {
|
||||
DEBUG_MSG("OTA done, rebooting in 5 seconds!\n");
|
||||
rebootAtMsec = timing::millis() + 5000;
|
||||
rebootAtMsec = millis() + 5000;
|
||||
} else {
|
||||
DEBUG_MSG("Error Occurred. Error #: %d\n", Update.getError());
|
||||
}
|
||||
@@ -126,7 +127,7 @@ int update_result_callback(uint16_t conn_handle, uint16_t attr_handle, struct bl
|
||||
|
||||
void bluetoothRebootCheck()
|
||||
{
|
||||
if (rebootAtMsec && timing::millis() > rebootAtMsec) {
|
||||
if (rebootAtMsec && millis() > rebootAtMsec) {
|
||||
DEBUG_MSG("Rebooting for update\n");
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
@@ -33,25 +33,36 @@ void WiFiServerAPI::loop()
|
||||
if (client.connected()) {
|
||||
StreamAPI::loop();
|
||||
} else {
|
||||
DEBUG_MSG("Client dropped connection, closing UDP server\n");
|
||||
DEBUG_MSG("Client dropped connection, closing TCP server\n");
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
#define MESHTASTIC_PORTNUM 4403
|
||||
|
||||
WiFiServerPort::WiFiServerPort() : WiFiServer(MESHTASTIC_PORTNUM) {}
|
||||
WiFiServerPort::WiFiServerPort() : WiFiServer(MESHTASTIC_PORTNUM), concurrency::OSThread("ApiServer") {}
|
||||
|
||||
void WiFiServerPort::init()
|
||||
{
|
||||
DEBUG_MSG("Listening on TCP port %d\n", MESHTASTIC_PORTNUM);
|
||||
DEBUG_MSG("API server sistening on TCP port %d\n", MESHTASTIC_PORTNUM);
|
||||
begin();
|
||||
}
|
||||
|
||||
void WiFiServerPort::loop()
|
||||
int32_t WiFiServerPort::runOnce()
|
||||
{
|
||||
auto client = available();
|
||||
if (client) {
|
||||
new WiFiServerAPI(client);
|
||||
// Close any previous connection (see FIXME in header file)
|
||||
if (openAPI)
|
||||
delete openAPI;
|
||||
|
||||
openAPI = new WiFiServerAPI(client);
|
||||
}
|
||||
|
||||
if (openAPI) {
|
||||
// Allow idle processing so the API can read from its incoming stream
|
||||
openAPI->loop();
|
||||
return 0; // run fast while our API server is running
|
||||
} else
|
||||
return 100; // only check occasionally for incoming connections
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "StreamAPI.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include <WiFi.h>
|
||||
|
||||
/**
|
||||
@@ -27,12 +28,19 @@ class WiFiServerAPI : public StreamAPI
|
||||
/**
|
||||
* Listens for incoming connections and does accepts and creates instances of WiFiServerAPI as needed
|
||||
*/
|
||||
class WiFiServerPort : public WiFiServer
|
||||
class WiFiServerPort : public WiFiServer, private concurrency::OSThread
|
||||
{
|
||||
/** The currently open port
|
||||
*
|
||||
* FIXME: We currently only allow one open TCP connection at a time, because we depend on the loop() call in this class to
|
||||
* delegate to the worker. Once coroutines are implemented we can relax this restriction.
|
||||
*/
|
||||
WiFiServerAPI *openAPI = NULL;
|
||||
|
||||
public:
|
||||
WiFiServerPort();
|
||||
|
||||
void init();
|
||||
|
||||
void loop();
|
||||
int32_t runOnce();
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "utils.h"
|
||||
#include <nvs.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
void getMacAddr(uint8_t *dmac)
|
||||
{
|
||||
@@ -86,4 +87,58 @@ void esp32Loop()
|
||||
|
||||
// for debug printing
|
||||
// radio.radioIf.canSleep();
|
||||
}
|
||||
|
||||
void cpuDeepSleep(uint64_t msecToWake)
|
||||
{
|
||||
/*
|
||||
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.
|
||||
|
||||
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
|
||||
13,
|
||||
/* 14, */ /* 15, */
|
||||
#endif
|
||||
/* 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]);
|
||||
|
||||
// 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);
|
||||
|
||||
#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);
|
||||
|
||||
#ifdef BUTTON_NEED_PULLUP
|
||||
gpio_pullup_en((gpio_num_t)BUTTON_PIN);
|
||||
#endif
|
||||
|
||||
// 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)
|
||||
}
|
||||
@@ -3,22 +3,32 @@
|
||||
// The FreeRTOS includes are in a different directory on ESP32 and I can't figure out how to make that work with platformio gcc
|
||||
// options so this is my quick hack to make things work
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#define HAS_FREE_RTOS
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
#else
|
||||
// not yet supported on cubecell
|
||||
#ifndef CubeCell_BoardPlus
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_NRF52_ADAFRUIT)
|
||||
#define HAS_FREE_RTOS
|
||||
|
||||
#include <FreeRTOS.h>
|
||||
#include <queue.h>
|
||||
#include <semphr.h>
|
||||
#include <task.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAS_FREE_RTOS
|
||||
|
||||
// Include real FreeRTOS defs above
|
||||
|
||||
#else
|
||||
|
||||
// Include placeholder fake FreeRTOS defs
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
typedef uint32_t TickType_t;
|
||||
@@ -26,5 +36,12 @@ typedef uint32_t BaseType_t;
|
||||
|
||||
#define portMAX_DELAY UINT32_MAX
|
||||
|
||||
#define tskIDLE_PRIORITY 0
|
||||
#define configMAX_PRIORITIES 10 // Highest priority level
|
||||
|
||||
// Don't do anything on non free rtos platforms when done with the ISR
|
||||
#define portYIELD_FROM_ISR(x)
|
||||
|
||||
enum eNotifyAction { eNoAction, eSetValueWithoutOverwrite, eSetValueWithOverwrite };
|
||||
|
||||
#endif
|
||||
#endif
|
||||
83
src/gps/Air530GPS.cpp
Normal file
83
src/gps/Air530GPS.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include "Air530GPS.h"
|
||||
#include <assert.h>
|
||||
|
||||
/*
|
||||
Helpful translations from the Air530 GPS datasheet
|
||||
|
||||
Sat acquision mode
|
||||
捕获电流值@3.3v 42.6 mA
|
||||
|
||||
sat tracking mode
|
||||
跟踪电流值@3.3v 36.7 mA
|
||||
|
||||
Low power mode
|
||||
低功耗模式@3.3V 0.85 mA
|
||||
(发送指令:$PGKC051,0)
|
||||
|
||||
Super low power mode
|
||||
超低功耗模式@3.3V 31 uA
|
||||
(发送指令:$PGKC105,4)
|
||||
|
||||
To exit sleep use WAKE pin
|
||||
|
||||
Commands to enter sleep
|
||||
6、Command: 105
|
||||
进入周期性低功耗模式
|
||||
Arguments:
|
||||
|
||||
Arg1: “0”,正常运行模式 (normal mode)
|
||||
“1”,周期超低功耗跟踪模式,需要拉高 WAKE 来唤醒 (periodic low power tracking mode - keeps sat positions, use wake to wake up)
|
||||
“2”,周期低功耗模式 (periodic low power mode)
|
||||
“4”,直接进入超低功耗跟踪模式,需要拉高 WAKE 来唤醒 (super low power consumption mode immediately, need WAKE to resume)
|
||||
“8”,自动低功耗模式,可以通过串口唤醒 (automatic low power mode, wake by sending characters to serial port)
|
||||
“9”, 自动超低功耗跟踪模式,需要拉高 WAKE 来唤醒 (automatic low power tracking when possible, need wake pin to resume)
|
||||
|
||||
(Arg 2 & 3 only valid if Arg1 is "1" or "2")
|
||||
Arg2:运行时间(毫秒),在 Arg1 为 1、2 的周期模式下,此参数起作用
|
||||
ON time in msecs
|
||||
|
||||
Arg3:睡眠时间(毫秒),在 Arg1 为 1、2 的周期模式下,此参数起作用
|
||||
Sleep time in msecs
|
||||
|
||||
Example:
|
||||
$PGKC105,8*3F<CR><LF>
|
||||
This will set automatic low power mode with waking when we send chars to the serial port. Possibly do this as soon as we get a
|
||||
new location. When we wake again in a minute we send a character to wake up.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
void Air530GPS::sendCommand(const char *cmd) {
|
||||
uint8_t sum = 0;
|
||||
|
||||
// Skip the $
|
||||
assert(cmd[0] == '$');
|
||||
const char *p = cmd + 1;
|
||||
while(*p)
|
||||
sum ^= *p++;
|
||||
|
||||
assert(_serial_gps);
|
||||
|
||||
_serial_gps->write(cmd);
|
||||
_serial_gps->printf("*%02x\r\n", sum);
|
||||
|
||||
// DEBUG_MSG("xsum %02x\n", sum);
|
||||
}
|
||||
|
||||
void Air530GPS::sleep() {
|
||||
NMEAGPS::sleep();
|
||||
#ifdef PIN_GPS_WAKE
|
||||
sendCommand("$PGKC105,4");
|
||||
#endif
|
||||
}
|
||||
|
||||
/// wake the GPS into normal operation mode
|
||||
void Air530GPS::wake()
|
||||
{
|
||||
#if 1
|
||||
NMEAGPS::wake();
|
||||
#else
|
||||
// For power testing - keep GPS sleeping forever
|
||||
sleep();
|
||||
#endif
|
||||
}
|
||||
22
src/gps/Air530GPS.h
Normal file
22
src/gps/Air530GPS.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "NMEAGPS.h"
|
||||
|
||||
/**
|
||||
* A gps class thatreads from a NMEA GPS stream (and FIXME - eventually keeps the gps powered down except when reading)
|
||||
*
|
||||
* When new data is available it will notify observers.
|
||||
*/
|
||||
class Air530GPS : public NMEAGPS
|
||||
{
|
||||
protected:
|
||||
/// If possible force the GPS into sleep/low power mode
|
||||
virtual void sleep();
|
||||
|
||||
/// wake the GPS into normal operation mode
|
||||
virtual void wake();
|
||||
|
||||
private:
|
||||
/// Send a NMEA cmd with checksum
|
||||
void sendCommand(const char *str);
|
||||
};
|
||||
313
src/gps/GPS.cpp
313
src/gps/GPS.cpp
@@ -1,15 +1,16 @@
|
||||
|
||||
#include "GPS.h"
|
||||
#include "NodeDB.h"
|
||||
#include "RTC.h"
|
||||
#include "configuration.h"
|
||||
#include "timing.h"
|
||||
#include "sleep.h"
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
|
||||
// If we have a serial GPS port it will not be null
|
||||
#ifdef GPS_RX_PIN
|
||||
HardwareSerial _serial_gps_real(GPS_SERIAL_NUM);
|
||||
HardwareSerial *GPS::_serial_gps = &_serial_gps_real;
|
||||
#elif defined(NRF52840_XXAA)
|
||||
#elif defined(NRF52840_XXAA) || defined(NRF52833_XXAA)
|
||||
// Assume NRF52840
|
||||
HardwareSerial *GPS::_serial_gps = &Serial1;
|
||||
#else
|
||||
@@ -22,67 +23,279 @@ uint8_t GPS::i2cAddress = GPS_I2C_ADDRESS;
|
||||
uint8_t GPS::i2cAddress = 0;
|
||||
#endif
|
||||
|
||||
bool timeSetFromGPS; // We try to set our time from GPS each time we wake from sleep
|
||||
|
||||
GPS *gps;
|
||||
|
||||
// stuff that really should be in in the instance instead...
|
||||
static uint32_t
|
||||
timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time
|
||||
static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock
|
||||
/// Multiple GPS instances might use the same serial port (in sequence), but we can
|
||||
/// only init that port once.
|
||||
static bool didSerialInit;
|
||||
|
||||
void readFromRTC()
|
||||
bool GPS::setupGPS()
|
||||
{
|
||||
struct timeval tv; /* btw settimeofday() is helpfull here too*/
|
||||
|
||||
if (!gettimeofday(&tv, NULL)) {
|
||||
uint32_t now = timing::millis();
|
||||
|
||||
DEBUG_MSG("Read RTC time as %ld (cur millis %u) valid=%d\n", tv.tv_sec, now, timeSetFromGPS);
|
||||
timeStartMsec = now;
|
||||
zeroOffsetSecs = tv.tv_sec;
|
||||
}
|
||||
}
|
||||
|
||||
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||
void perhapsSetRTC(const struct timeval *tv)
|
||||
{
|
||||
if (!timeSetFromGPS) {
|
||||
timeSetFromGPS = true;
|
||||
DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec);
|
||||
#ifndef NO_ESP32
|
||||
settimeofday(tv, NULL);
|
||||
if (_serial_gps && !didSerialInit) {
|
||||
didSerialInit = true;
|
||||
|
||||
#ifdef GPS_RX_PIN
|
||||
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
|
||||
#else
|
||||
DEBUG_MSG("ERROR TIME SETTING NOT IMPLEMENTED!\n");
|
||||
_serial_gps->begin(GPS_BAUDRATE);
|
||||
#endif
|
||||
readFromRTC();
|
||||
#ifndef NO_ESP32
|
||||
_serial_gps->setRxBufferSize(2048); // the default is 256
|
||||
#endif
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GPS::setup()
|
||||
{
|
||||
// Master power for the GPS
|
||||
#ifdef PIN_GPS_EN
|
||||
digitalWrite(PIN_GPS_EN, PIN_GPS_EN);
|
||||
pinMode(PIN_GPS_EN, OUTPUT);
|
||||
#endif
|
||||
|
||||
#ifdef PIN_GPS_RESET
|
||||
digitalWrite(PIN_GPS_RESET, 1); // assert for 10ms
|
||||
pinMode(PIN_GPS_RESET, OUTPUT);
|
||||
delay(10);
|
||||
digitalWrite(PIN_GPS_RESET, 0);
|
||||
#endif
|
||||
|
||||
setAwake(true); // Wake GPS power before doing any init
|
||||
bool ok = setupGPS();
|
||||
|
||||
if (ok) {
|
||||
notifySleepObserver.observe(¬ifySleep);
|
||||
notifyDeepSleepObserver.observe(¬ifyDeepSleep);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Allow defining the polarity of the WAKE output. default is active high
|
||||
#ifndef GPS_WAKE_ACTIVE
|
||||
#define GPS_WAKE_ACTIVE 1
|
||||
#endif
|
||||
|
||||
void GPS::wake()
|
||||
{
|
||||
#ifdef PIN_GPS_WAKE
|
||||
digitalWrite(PIN_GPS_WAKE, GPS_WAKE_ACTIVE);
|
||||
pinMode(PIN_GPS_WAKE, OUTPUT);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void GPS::sleep() {
|
||||
#ifdef PIN_GPS_WAKE
|
||||
digitalWrite(PIN_GPS_WAKE, GPS_WAKE_ACTIVE ? 0 : 1);
|
||||
pinMode(PIN_GPS_WAKE, OUTPUT);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Record that we have a GPS
|
||||
void GPS::setConnected()
|
||||
{
|
||||
if (!hasGPS) {
|
||||
hasGPS = true;
|
||||
shouldPublish = true;
|
||||
}
|
||||
}
|
||||
|
||||
void perhapsSetRTC(struct tm &t)
|
||||
void GPS::setNumSatellites(uint8_t 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).
|
||||
*/
|
||||
time_t res = mktime(&t);
|
||||
struct timeval tv;
|
||||
tv.tv_sec = res;
|
||||
tv.tv_usec = 0; // time.centisecond() * (10 / 1000);
|
||||
|
||||
// DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec);
|
||||
if (t.tm_year < 0 || t.tm_year >= 300)
|
||||
DEBUG_MSG("Ignoring invalid GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec);
|
||||
else
|
||||
perhapsSetRTC(&tv);
|
||||
if (n != numSatellites) {
|
||||
numSatellites = n;
|
||||
shouldPublish = true;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t getTime()
|
||||
/**
|
||||
* Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode
|
||||
*
|
||||
* calls sleep/wake
|
||||
*/
|
||||
void GPS::setAwake(bool on)
|
||||
{
|
||||
return ((timing::millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
|
||||
if (!wakeAllowed && on) {
|
||||
DEBUG_MSG("Inhibiting because !wakeAllowed\n");
|
||||
on = false;
|
||||
}
|
||||
|
||||
if (isAwake != on) {
|
||||
DEBUG_MSG("WANT GPS=%d\n", on);
|
||||
if (on) {
|
||||
lastWakeStartMsec = millis();
|
||||
wake();
|
||||
} else {
|
||||
lastSleepStartMsec = millis();
|
||||
sleep();
|
||||
}
|
||||
|
||||
isAwake = on;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t getValidTime()
|
||||
GpsOperation GPS::getGpsOp() const
|
||||
{
|
||||
return timeSetFromGPS ? getTime() : 0;
|
||||
auto op = radioConfig.preferences.gps_operation;
|
||||
|
||||
if (op == GpsOperation_GpsOpUnset)
|
||||
op = (radioConfig.preferences.location_share == LocationSharing_LocDisabled) ? GpsOperation_GpsOpTimeOnly
|
||||
: GpsOperation_GpsOpMobile;
|
||||
|
||||
return op;
|
||||
}
|
||||
|
||||
/** Get how long we should stay looking for each aquisition in msecs
|
||||
*/
|
||||
uint32_t GPS::getWakeTime() const
|
||||
{
|
||||
uint32_t t = radioConfig.preferences.gps_attempt_time;
|
||||
|
||||
if (t == UINT32_MAX)
|
||||
return t; // already maxint
|
||||
|
||||
if (t == 0)
|
||||
t = 15 * 60; // Allow up to 5 mins for each attempt (probably will be much less if we can find sats)
|
||||
|
||||
t *= 1000; // msecs
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
/** Get how long we should sleep between aqusition attempts in msecs
|
||||
*/
|
||||
uint32_t GPS::getSleepTime() const
|
||||
{
|
||||
uint32_t t = radioConfig.preferences.gps_update_interval;
|
||||
|
||||
auto op = getGpsOp();
|
||||
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
|
||||
if ((gotTime && op == GpsOperation_GpsOpTimeOnly) || (op == GpsOperation_GpsOpDisabled))
|
||||
t = UINT32_MAX; // Sleep forever now
|
||||
|
||||
if (t == UINT32_MAX)
|
||||
return t; // already maxint
|
||||
|
||||
if (t == 0)
|
||||
t = 2 * 60; // 2 mins
|
||||
|
||||
t *= 1000;
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
void GPS::publishUpdate()
|
||||
{
|
||||
if (shouldPublish) {
|
||||
shouldPublish = false;
|
||||
|
||||
DEBUG_MSG("publishing GPS lock=%d\n", hasLock());
|
||||
|
||||
// Notify any status instances that are observing us
|
||||
const meshtastic::GPSStatus status =
|
||||
meshtastic::GPSStatus(hasLock(), isConnected(), latitude, longitude, altitude, dop, heading, numSatellites);
|
||||
newStatus.notifyObservers(&status);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t GPS::runOnce()
|
||||
{
|
||||
if (whileIdle()) {
|
||||
// if we have received valid NMEA claim we are connected
|
||||
setConnected();
|
||||
}
|
||||
|
||||
// If we are overdue for an update, turn on the GPS and at least publish the current status
|
||||
uint32_t now = millis();
|
||||
|
||||
auto sleepTime = getSleepTime();
|
||||
if (!isAwake && sleepTime != UINT32_MAX && (now - lastSleepStartMsec) > sleepTime) {
|
||||
// We now want to be awake - so wake up the GPS
|
||||
setAwake(true);
|
||||
}
|
||||
|
||||
// While we are awake
|
||||
if (isAwake) {
|
||||
// DEBUG_MSG("looking for location\n");
|
||||
if ((now - lastWhileActiveMsec) > 5000) {
|
||||
lastWhileActiveMsec = now;
|
||||
whileActive();
|
||||
}
|
||||
|
||||
// If we've already set time from the GPS, no need to ask the GPS
|
||||
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
|
||||
if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
|
||||
gotTime = true;
|
||||
shouldPublish = true;
|
||||
}
|
||||
|
||||
bool gotLoc = lookForLocation();
|
||||
if (gotLoc && !hasValidLocation) { // declare that we have location ASAP
|
||||
hasValidLocation = true;
|
||||
shouldPublish = true;
|
||||
}
|
||||
|
||||
// We've been awake too long - force sleep
|
||||
auto wakeTime = getWakeTime();
|
||||
bool tooLong = wakeTime != UINT32_MAX && (now - lastWakeStartMsec) > wakeTime;
|
||||
|
||||
// Once we get a location we no longer desperately want an update
|
||||
// or if we got a time and we are in GpsOpTimeOnly mode
|
||||
// DEBUG_MSG("gotLoc %d, tooLong %d, gotTime %d\n", gotLoc, tooLong, gotTime);
|
||||
if ((gotLoc && gotTime) || tooLong || (gotTime && getGpsOp() == GpsOperation_GpsOpTimeOnly)) {
|
||||
|
||||
if (tooLong) {
|
||||
// we didn't get a location during this ack window, therefore declare loss of lock
|
||||
hasValidLocation = false;
|
||||
}
|
||||
|
||||
setAwake(false);
|
||||
shouldPublish = true; // publish our update for this just finished acquisition window
|
||||
}
|
||||
}
|
||||
|
||||
// If state has changed do a publish
|
||||
publishUpdate();
|
||||
|
||||
// 9600bps is approx 1 byte per msec, so considering our buffer size we never need to wake more often than 200ms
|
||||
// if not awake we can run super infrquently (once every 5 secs?) to see if we need to wake.
|
||||
return isAwake ? 100 : 5000;
|
||||
}
|
||||
|
||||
void GPS::forceWake(bool on)
|
||||
{
|
||||
if (on) {
|
||||
DEBUG_MSG("Allowing GPS lock\n");
|
||||
// lastSleepStartMsec = 0; // Force an update ASAP
|
||||
wakeAllowed = true;
|
||||
} else {
|
||||
wakeAllowed = false;
|
||||
|
||||
// Note: if the gps was already awake, we DO NOT shut it down, because we want to allow it to complete its lock
|
||||
// attempt even if we are in light sleep. Once the attempt succeeds (or times out) we'll then shut it down.
|
||||
// setAwake(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||
int GPS::prepareSleep(void *unused)
|
||||
{
|
||||
DEBUG_MSG("GPS prepare sleep!\n");
|
||||
forceWake(false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||
int GPS::prepareDeepSleep(void *unused)
|
||||
{
|
||||
DEBUG_MSG("GPS deep sleep!\n");
|
||||
|
||||
// For deep sleep we also want abandon any lock attempts (because we want minimum power)
|
||||
setAwake(false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
128
src/gps/GPS.h
128
src/gps/GPS.h
@@ -1,35 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "../concurrency/PeriodicTask.h"
|
||||
#include "GPSStatus.h"
|
||||
#include "Observer.h"
|
||||
#include "sys/time.h"
|
||||
|
||||
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||
void perhapsSetRTC(const struct timeval *tv);
|
||||
void perhapsSetRTC(struct tm &t);
|
||||
#include "concurrency/OSThread.h"
|
||||
|
||||
// Generate a string representation of DOP
|
||||
const char *getDOPString(uint32_t dop);
|
||||
|
||||
/// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero
|
||||
uint32_t getTime();
|
||||
|
||||
/// Return time since 1970 in secs. If we don't have a GPS lock return zero
|
||||
uint32_t getValidTime();
|
||||
|
||||
void readFromRTC();
|
||||
|
||||
/**
|
||||
* A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading)
|
||||
*
|
||||
* When new data is available it will notify observers.
|
||||
*/
|
||||
class GPS : public Observable<void *>
|
||||
class GPS : private concurrency::OSThread
|
||||
{
|
||||
protected:
|
||||
private:
|
||||
uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastWhileActiveMsec = 0;
|
||||
|
||||
bool hasValidLocation = false; // default to false, until we complete our first read
|
||||
|
||||
bool isAwake = false; // true if we want a location right now
|
||||
|
||||
bool wakeAllowed = true; // false if gps must be forced to sleep regardless of what time it is
|
||||
|
||||
bool shouldPublish = false; // If we've changed GPS state, this will force a publish the next loop()
|
||||
|
||||
bool hasGPS = false; // Do we have a GPS we are talking to
|
||||
|
||||
uint8_t numSatellites = 0;
|
||||
|
||||
CallbackObserver<GPS, void *> notifySleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareSleep);
|
||||
CallbackObserver<GPS, void *> notifyDeepSleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareDeepSleep);
|
||||
|
||||
public:
|
||||
/** If !NULL we will use this serial port to construct our GPS */
|
||||
static HardwareSerial *_serial_gps;
|
||||
@@ -42,29 +44,105 @@ class GPS : public Observable<void *>
|
||||
uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs
|
||||
// scaling before use)
|
||||
uint32_t heading = 0; // Heading of motion, in degrees * 10^-5
|
||||
uint32_t numSatellites = 0;
|
||||
|
||||
bool isConnected = false; // Do we have a GPS we are talking to
|
||||
GPS() : concurrency::OSThread("GPS") {}
|
||||
|
||||
virtual ~GPS() {}
|
||||
virtual ~GPS() {} // FIXME, we really should unregister our sleep observer
|
||||
|
||||
/** We will notify this observable anytime GPS state has changed meaningfully */
|
||||
Observable<const meshtastic::GPSStatus *> newStatus;
|
||||
|
||||
/**
|
||||
* Returns true if we succeeded
|
||||
*/
|
||||
virtual bool setup() { return true; }
|
||||
|
||||
/// A loop callback for subclasses that need it. FIXME, instead just block on serial reads
|
||||
virtual void loop() {}
|
||||
virtual bool setup();
|
||||
|
||||
/// Returns ture if we have acquired GPS lock.
|
||||
bool hasLock() const { return hasValidLocation; }
|
||||
|
||||
/// Return true if we are connected to a GPS
|
||||
bool isConnected() const { return hasGPS; }
|
||||
|
||||
/**
|
||||
* Restart our lock attempt - try to get and broadcast a GPS reading ASAP
|
||||
* called after the CPU wakes from light-sleep state */
|
||||
virtual void startLock() {}
|
||||
* called after the CPU wakes from light-sleep state
|
||||
*
|
||||
* Or set to false, to disallow any sort of waking
|
||||
* */
|
||||
void forceWake(bool on);
|
||||
|
||||
protected:
|
||||
/// Do gps chipset specific init, return true for success
|
||||
virtual bool setupGPS();
|
||||
|
||||
/// If possible force the GPS into sleep/low power mode
|
||||
virtual void sleep();
|
||||
|
||||
/// wake the GPS into normal operation mode
|
||||
virtual void wake();
|
||||
|
||||
/** Subclasses should look for serial rx characters here and feed it to their GPS parser
|
||||
*
|
||||
* Return true if we received a valid message from the GPS
|
||||
*/
|
||||
virtual bool whileIdle() = 0;
|
||||
|
||||
/** Idle processing while GPS is looking for lock, called once per secondish */
|
||||
virtual void whileActive() {}
|
||||
|
||||
/**
|
||||
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||
* Override this method to check for new locations
|
||||
*
|
||||
* @return true if we've acquired a time
|
||||
*/
|
||||
virtual bool lookForTime() = 0;
|
||||
|
||||
/**
|
||||
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||
* Override this method to check for new locations
|
||||
*
|
||||
* @return true if we've acquired a new location
|
||||
*/
|
||||
virtual bool lookForLocation() = 0;
|
||||
|
||||
/// Record that we have a GPS
|
||||
void setConnected();
|
||||
|
||||
void setNumSatellites(uint8_t n);
|
||||
|
||||
private:
|
||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||
/// always returns 0 to indicate okay to sleep
|
||||
int prepareSleep(void *unused);
|
||||
|
||||
/// Prepare the GPS for the cpu entering deep sleep, expect to be gone for at least 100s of msecs
|
||||
/// always returns 0 to indicate okay to sleep
|
||||
int prepareDeepSleep(void *unused);
|
||||
|
||||
/**
|
||||
* Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode
|
||||
*
|
||||
* calls sleep/wake
|
||||
*/
|
||||
void setAwake(bool on);
|
||||
|
||||
/** Get how long we should stay looking for each aquisition
|
||||
*/
|
||||
uint32_t getWakeTime() const;
|
||||
|
||||
/** Get how long we should sleep between aqusition attempts
|
||||
*/
|
||||
uint32_t getSleepTime() const;
|
||||
|
||||
GpsOperation getGpsOp() const;
|
||||
|
||||
/**
|
||||
* Tell users we have new GPS readings
|
||||
*/
|
||||
void publishUpdate();
|
||||
|
||||
virtual int32_t runOnce();
|
||||
};
|
||||
|
||||
extern GPS *gps;
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
#include "NEMAGPS.h"
|
||||
#include "configuration.h"
|
||||
#include "timing.h"
|
||||
|
||||
static int32_t toDegInt(RawDegrees d)
|
||||
{
|
||||
int32_t degMult = 10000000; // 1e7
|
||||
int32_t r = d.deg * degMult + d.billionths / 100;
|
||||
if (d.negative)
|
||||
r *= -1;
|
||||
return r;
|
||||
}
|
||||
|
||||
void NEMAGPS::loop()
|
||||
{
|
||||
while (_serial_gps->available() > 0) {
|
||||
int c = _serial_gps->read();
|
||||
// Serial.write(c);
|
||||
reader.encode(c);
|
||||
}
|
||||
|
||||
uint32_t now = timing::millis();
|
||||
if ((now - lastUpdateMsec) > 20 * 1000) { // Ugly hack for now - limit update checks to once every 20 secs (but still consume
|
||||
// serial chars at whatever rate)
|
||||
lastUpdateMsec = now;
|
||||
|
||||
auto ti = reader.time;
|
||||
auto d = reader.date;
|
||||
if (ti.isUpdated() && ti.isValid() && d.isValid()) {
|
||||
/* Convert to unix time
|
||||
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
|
||||
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
||||
*/
|
||||
struct tm t;
|
||||
t.tm_sec = ti.second();
|
||||
t.tm_min = ti.minute();
|
||||
t.tm_hour = ti.hour();
|
||||
t.tm_mday = d.day();
|
||||
t.tm_mon = d.month() - 1;
|
||||
t.tm_year = d.year() - 1900;
|
||||
t.tm_isdst = false;
|
||||
perhapsSetRTC(t);
|
||||
|
||||
isConnected = true; // we seem to have a real GPS (but not necessarily a lock)
|
||||
}
|
||||
|
||||
if (reader.location.isUpdated()) {
|
||||
if (reader.altitude.isValid())
|
||||
altitude = reader.altitude.meters();
|
||||
|
||||
if (reader.location.isValid()) {
|
||||
auto loc = reader.location.value();
|
||||
latitude = toDegInt(loc.lat);
|
||||
longitude = toDegInt(loc.lng);
|
||||
}
|
||||
// Diminution of precision (an accuracy metric) is reported in 10^2 units, so we need to scale down when we use it
|
||||
if (reader.hdop.isValid()) {
|
||||
dop = reader.hdop.value();
|
||||
}
|
||||
if (reader.course.isValid()) {
|
||||
heading = reader.course.value() * 1e3; //Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5
|
||||
}
|
||||
if (reader.satellites.isValid()) {
|
||||
numSatellites = reader.satellites.value();
|
||||
}
|
||||
|
||||
// expect gps pos lat=37.520825, lon=-122.309162, alt=158
|
||||
DEBUG_MSG("new NEMA GPS pos lat=%f, lon=%f, alt=%d, hdop=%f, heading=%f\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2, heading * 1e-5);
|
||||
|
||||
hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0
|
||||
if (hasValidLocation)
|
||||
notifyObservers(NULL);
|
||||
}
|
||||
|
||||
// Notify any status instances that are observing us
|
||||
const meshtastic::GPSStatus status = meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop, heading, numSatellites);
|
||||
newStatus.notifyObservers(&status);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "GPS.h"
|
||||
#include "Observer.h"
|
||||
#include "../concurrency/PeriodicTask.h"
|
||||
#include "TinyGPS++.h"
|
||||
|
||||
/**
|
||||
* A gps class thatreads from a NEMA GPS stream (and FIXME - eventually keeps the gps powered down except when reading)
|
||||
*
|
||||
* When new data is available it will notify observers.
|
||||
*/
|
||||
class NEMAGPS : public GPS
|
||||
{
|
||||
TinyGPSPlus reader;
|
||||
|
||||
uint32_t lastUpdateMsec = 0;
|
||||
|
||||
public:
|
||||
virtual void loop();
|
||||
};
|
||||
112
src/gps/NMEAGPS.cpp
Normal file
112
src/gps/NMEAGPS.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#include "NMEAGPS.h"
|
||||
#include "RTC.h"
|
||||
#include "configuration.h"
|
||||
|
||||
static int32_t toDegInt(RawDegrees d)
|
||||
{
|
||||
int32_t degMult = 10000000; // 1e7
|
||||
int32_t r = d.deg * degMult + d.billionths / 100;
|
||||
if (d.negative)
|
||||
r *= -1;
|
||||
return r;
|
||||
}
|
||||
|
||||
bool NMEAGPS::setupGPS()
|
||||
{
|
||||
GPS::setupGPS();
|
||||
|
||||
#ifdef PIN_GPS_PPS
|
||||
// pulse per second
|
||||
// FIXME - move into shared GPS code
|
||||
pinMode(PIN_GPS_PPS, INPUT);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||
* Override this method to check for new locations
|
||||
*
|
||||
* @return true if we've acquired a new location
|
||||
*/
|
||||
bool NMEAGPS::lookForTime()
|
||||
{
|
||||
auto ti = reader.time;
|
||||
auto d = reader.date;
|
||||
if (ti.isValid() && d.isValid()) { // Note: we don't check for updated, because we'll only be called if needed
|
||||
/* Convert to unix time
|
||||
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
|
||||
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
||||
*/
|
||||
struct tm t;
|
||||
t.tm_sec = ti.second();
|
||||
t.tm_min = ti.minute();
|
||||
t.tm_hour = ti.hour();
|
||||
t.tm_mday = d.day();
|
||||
t.tm_mon = d.month() - 1;
|
||||
t.tm_year = d.year() - 1900;
|
||||
t.tm_isdst = false;
|
||||
perhapsSetRTC(RTCQualityGPS, t);
|
||||
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||
* Override this method to check for new locations
|
||||
*
|
||||
* @return true if we've acquired a new location
|
||||
*/
|
||||
bool NMEAGPS::lookForLocation()
|
||||
{
|
||||
bool foundLocation = false;
|
||||
|
||||
// uint8_t fixtype = reader.fixQuality();
|
||||
// hasValidLocation = ((fixtype >= 1) && (fixtype <= 5));
|
||||
|
||||
if (reader.satellites.isUpdated()) {
|
||||
setNumSatellites(reader.satellites.value());
|
||||
}
|
||||
|
||||
// Diminution of precision (an accuracy metric) is reported in 10^2 units, so we need to scale down when we use it
|
||||
if (reader.hdop.isUpdated()) {
|
||||
dop = reader.hdop.value();
|
||||
}
|
||||
if (reader.course.isUpdated()) {
|
||||
heading = reader.course.value() * 1e3; // Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5
|
||||
}
|
||||
|
||||
if (reader.altitude.isUpdated())
|
||||
altitude = reader.altitude.meters();
|
||||
|
||||
if (reader.location.isUpdated()) {
|
||||
|
||||
auto loc = reader.location.value();
|
||||
latitude = toDegInt(loc.lat);
|
||||
longitude = toDegInt(loc.lng);
|
||||
foundLocation = true;
|
||||
|
||||
// expect gps pos lat=37.520825, lon=-122.309162, alt=158
|
||||
DEBUG_MSG("new NMEA GPS pos lat=%f, lon=%f, alt=%d, hdop=%g, heading=%f\n", latitude * 1e-7, longitude * 1e-7, altitude,
|
||||
dop * 1e-2, heading * 1e-5);
|
||||
}
|
||||
|
||||
return foundLocation;
|
||||
}
|
||||
|
||||
bool NMEAGPS::whileIdle()
|
||||
{
|
||||
bool isValid = false;
|
||||
|
||||
// First consume any chars that have piled up at the receiver
|
||||
while (_serial_gps->available() > 0) {
|
||||
int c = _serial_gps->read();
|
||||
// DEBUG_MSG("%c", c);
|
||||
isValid |= reader.encode(c);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
41
src/gps/NMEAGPS.h
Normal file
41
src/gps/NMEAGPS.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "GPS.h"
|
||||
#include "Observer.h"
|
||||
#include "TinyGPS++.h"
|
||||
|
||||
/**
|
||||
* A gps class thatreads from a NMEA GPS stream (and FIXME - eventually keeps the gps powered down except when reading)
|
||||
*
|
||||
* When new data is available it will notify observers.
|
||||
*/
|
||||
class NMEAGPS : public GPS
|
||||
{
|
||||
TinyGPSPlus reader;
|
||||
|
||||
public:
|
||||
virtual bool setupGPS();
|
||||
|
||||
protected:
|
||||
/** Subclasses should look for serial rx characters here and feed it to their GPS parser
|
||||
*
|
||||
* Return true if we received a valid message from the GPS
|
||||
*/
|
||||
virtual bool whileIdle();
|
||||
|
||||
/**
|
||||
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||
* Override this method to check for new locations
|
||||
*
|
||||
* @return true if we've acquired a time
|
||||
*/
|
||||
virtual bool lookForTime();
|
||||
|
||||
/**
|
||||
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||
* Override this method to check for new locations
|
||||
*
|
||||
* @return true if we've acquired a new location
|
||||
*/
|
||||
virtual bool lookForLocation();
|
||||
};
|
||||
77
src/gps/RTC.cpp
Normal file
77
src/gps/RTC.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "RTC.h"
|
||||
#include "configuration.h"
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
static RTCQuality currentQuality = RTCQualityNone;
|
||||
|
||||
RTCQuality getRTCQuality()
|
||||
{
|
||||
return currentQuality;
|
||||
}
|
||||
|
||||
// stuff that really should be in in the instance instead...
|
||||
static uint32_t
|
||||
timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time
|
||||
static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock
|
||||
|
||||
void readFromRTC()
|
||||
{
|
||||
struct timeval tv; /* btw settimeofday() is helpfull here too*/
|
||||
|
||||
if (!gettimeofday(&tv, NULL)) {
|
||||
uint32_t now = millis();
|
||||
|
||||
DEBUG_MSG("Read RTC time as %ld (cur millis %u) quality=%d\n", tv.tv_sec, now, currentQuality);
|
||||
timeStartMsec = now;
|
||||
zeroOffsetSecs = tv.tv_sec;
|
||||
}
|
||||
}
|
||||
|
||||
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
|
||||
{
|
||||
if (q > currentQuality) {
|
||||
currentQuality = q;
|
||||
DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec);
|
||||
#ifndef NO_ESP32
|
||||
settimeofday(tv, NULL);
|
||||
#else
|
||||
DEBUG_MSG("ERROR TIME SETTING NOT IMPLEMENTED!\n");
|
||||
#endif
|
||||
readFromRTC();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool perhapsSetRTC(RTCQuality q, struct tm &t)
|
||||
{
|
||||
/* Convert to unix time
|
||||
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
|
||||
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
||||
*/
|
||||
time_t res = mktime(&t);
|
||||
struct timeval tv;
|
||||
tv.tv_sec = res;
|
||||
tv.tv_usec = 0; // time.centisecond() * (10 / 1000);
|
||||
|
||||
// DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec);
|
||||
if (t.tm_year < 0 || t.tm_year >= 300) {
|
||||
// DEBUG_MSG("Ignoring invalid GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec);
|
||||
return false;
|
||||
} else {
|
||||
return perhapsSetRTC(q, &tv);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t getTime()
|
||||
{
|
||||
return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
|
||||
}
|
||||
|
||||
uint32_t getValidTime(RTCQuality minQuality)
|
||||
{
|
||||
return (currentQuality >= minQuality) ? getTime() : 0;
|
||||
}
|
||||
30
src/gps/RTC.h
Normal file
30
src/gps/RTC.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "configuration.h"
|
||||
#include "sys/time.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
enum RTCQuality {
|
||||
/// We haven't had our RTC set yet
|
||||
RTCQualityNone = 0,
|
||||
|
||||
/// Some other node gave us a time we can use
|
||||
RTCQualityFromNet = 1,
|
||||
|
||||
/// Our time is based on our own GPS
|
||||
RTCQualityGPS = 2
|
||||
};
|
||||
|
||||
RTCQuality getRTCQuality();
|
||||
|
||||
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv);
|
||||
bool perhapsSetRTC(RTCQuality q, struct tm &t);
|
||||
|
||||
/// Return time since 1970 in secs. While quality is RTCQualityNone we will be returning time based at zero
|
||||
uint32_t getTime();
|
||||
|
||||
/// Return time since 1970 in secs. If quality is RTCQualityNone return zero
|
||||
uint32_t getValidTime(RTCQuality minQuality);
|
||||
|
||||
void readFromRTC();
|
||||
@@ -1,137 +1,143 @@
|
||||
#include "UBloxGPS.h"
|
||||
#include "RTC.h"
|
||||
#include "error.h"
|
||||
#include "sleep.h"
|
||||
#include <assert.h>
|
||||
|
||||
UBloxGPS::UBloxGPS() : concurrency::PeriodicTask()
|
||||
{
|
||||
notifySleepObserver.observe(¬ifySleep);
|
||||
}
|
||||
UBloxGPS::UBloxGPS() {}
|
||||
|
||||
bool UBloxGPS::tryConnect()
|
||||
{
|
||||
isConnected = false;
|
||||
bool c = false;
|
||||
|
||||
if (_serial_gps)
|
||||
isConnected = ublox.begin(*_serial_gps);
|
||||
c = ublox.begin(*_serial_gps);
|
||||
|
||||
if (!isConnected && i2cAddress) {
|
||||
if (!c && i2cAddress) {
|
||||
extern bool neo6M; // Super skanky - if we are talking to the device i2c we assume it is a neo7 on a RAK815, which
|
||||
// supports the newer API
|
||||
neo6M = true;
|
||||
|
||||
isConnected = ublox.begin(Wire, i2cAddress);
|
||||
c = ublox.begin(Wire, i2cAddress);
|
||||
}
|
||||
|
||||
return isConnected;
|
||||
if (c)
|
||||
setConnected();
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
bool UBloxGPS::setup()
|
||||
bool UBloxGPS::setupGPS()
|
||||
{
|
||||
if (_serial_gps) {
|
||||
#ifdef GPS_RX_PIN
|
||||
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
|
||||
#else
|
||||
_serial_gps->begin(GPS_BAUDRATE);
|
||||
#endif
|
||||
// _serial_gps.setRxBufferSize(1024); // the default is 256
|
||||
}
|
||||
|
||||
#ifdef GPS_POWER_EN
|
||||
pinMode(GPS_POWER_EN, OUTPUT);
|
||||
digitalWrite(GPS_POWER_EN, 1);
|
||||
delay(200); // Give time for the GPS to startup after we gave power
|
||||
#endif
|
||||
GPS::setupGPS();
|
||||
|
||||
// uncomment to see debug info
|
||||
// ublox.enableDebugging(Serial);
|
||||
|
||||
// try a second time, the ublox lib serial parsing is buggy?
|
||||
if (!tryConnect())
|
||||
tryConnect();
|
||||
// see https://github.com/meshtastic/Meshtastic-device/issues/376
|
||||
for (int i = 0; (i < 3) && !tryConnect(); i++)
|
||||
delay(500);
|
||||
|
||||
if (isConnected) {
|
||||
if (isConnected()) {
|
||||
DEBUG_MSG("Connected to UBLOX GPS successfully\n");
|
||||
|
||||
bool factoryReset = false;
|
||||
bool ok;
|
||||
if (factoryReset) {
|
||||
// It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have
|
||||
// GPS_TX connected)
|
||||
ublox.factoryReset();
|
||||
delay(3000);
|
||||
tryConnect();
|
||||
DEBUG_MSG("Factory reset success=%d\n", isConnected);
|
||||
ok = ublox.saveConfiguration(3000);
|
||||
assert(ok);
|
||||
return false;
|
||||
} else {
|
||||
if (_serial_gps) {
|
||||
ok = ublox.setUART1Output(COM_TYPE_UBX, 500); // Use native API
|
||||
assert(ok);
|
||||
}
|
||||
if (i2cAddress) {
|
||||
ok = ublox.setI2COutput(COM_TYPE_UBX, 500);
|
||||
assert(ok);
|
||||
}
|
||||
|
||||
ok = ublox.setNavigationFrequency(1, 500); // Produce 4x/sec to keep the amount of time we stall in getPVT low
|
||||
assert(ok);
|
||||
// ok = ublox.setAutoPVT(false); // Not implemented on NEO-6M
|
||||
// assert(ok);
|
||||
// ok = ublox.setDynamicModel(DYN_MODEL_BIKE); // probably PEDESTRIAN but just in case assume bike speeds
|
||||
// assert(ok);
|
||||
ok = ublox.powerSaveMode(true, 2000); // use power save mode, the default timeout (1100ms seems a bit too tight)
|
||||
assert(ok);
|
||||
}
|
||||
ok = ublox.saveConfiguration(3000);
|
||||
if (!ok)
|
||||
if (!setUBXMode())
|
||||
recordCriticalError(UBloxInitFailed); // Don't halt the boot if saving the config fails, but do report the bug
|
||||
|
||||
concurrency::PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||
int UBloxGPS::prepareSleep(void *unused)
|
||||
bool UBloxGPS::setUBXMode()
|
||||
{
|
||||
if (isConnected)
|
||||
ublox.powerOff();
|
||||
if (_serial_gps) {
|
||||
if (!ublox.setUART1Output(COM_TYPE_UBX, 1000)) // Use native API
|
||||
return false;
|
||||
}
|
||||
if (i2cAddress) {
|
||||
if (!ublox.setI2COutput(COM_TYPE_UBX, 1000))
|
||||
return false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
if (!ublox.setNavigationFrequency(1, 1000)) // Produce 4x/sec to keep the amount of time we stall in getPVT low
|
||||
return false;
|
||||
|
||||
// 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);
|
||||
|
||||
// per https://github.com/meshtastic/Meshtastic-device/issues/376 powerSaveMode might not work with the marginal
|
||||
// TTGO antennas
|
||||
// if (!ublox.powerSaveMode(true, 2000)) // use power save mode, the default timeout (1100ms seems a bit too tight)
|
||||
// return false;
|
||||
|
||||
if (!ublox.saveConfiguration(3000))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UBloxGPS::doTask()
|
||||
/**
|
||||
* Reset our GPS back to factory settings
|
||||
*
|
||||
* @return true for success
|
||||
*/
|
||||
bool UBloxGPS::factoryReset()
|
||||
{
|
||||
uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix
|
||||
bool ok = false;
|
||||
|
||||
assert(isConnected);
|
||||
// It is useful to force back into factory defaults (9600baud, NMEA to test the behavior of boards that don't have
|
||||
// GPS_TX connected)
|
||||
ublox.factoryReset();
|
||||
delay(5000);
|
||||
tryConnect(); // sets isConnected
|
||||
|
||||
// Consume all characters that have arrived
|
||||
// try a second time, the ublox lib serial parsing is buggy?
|
||||
for (int i = 0; (i < 3) && !tryConnect(); i++)
|
||||
delay(500);
|
||||
|
||||
// if using i2c or serial look too see if any chars are ready
|
||||
ublox.checkUblox(); // See if new data is available. Process bytes as they come in.
|
||||
DEBUG_MSG("GPS Factory reset success=%d\n", isConnected());
|
||||
if (isConnected())
|
||||
ok = setUBXMode();
|
||||
|
||||
// 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
|
||||
uint16_t maxWait = i2cAddress ? 300 : 0; // If using i2c we must poll with wait
|
||||
fixtype = ublox.getFixType(maxWait);
|
||||
DEBUG_MSG("GPS fix type %d\n", fixtype);
|
||||
return ok;
|
||||
}
|
||||
|
||||
// DEBUG_MSG("sec %d\n", ublox.getSecond());
|
||||
// DEBUG_MSG("lat %d\n", ublox.getLatitude());
|
||||
/** Idle processing while GPS is looking for lock */
|
||||
void UBloxGPS::whileActive()
|
||||
{
|
||||
ublox.getT(maxWait()); // ask for new time data - hopefully ready when we come back
|
||||
|
||||
// any fix that has time
|
||||
// Ask for a new position fix - hopefully it will have results ready by next time
|
||||
// the order here is important, because we only check for has latitude when reading
|
||||
ublox.getSIV(maxWait());
|
||||
ublox.getPDOP(maxWait());
|
||||
ublox.getP(maxWait());
|
||||
|
||||
if (ublox.getT(maxWait)) {
|
||||
// Update fixtype
|
||||
if (ublox.moduleQueried.fixType) {
|
||||
fixType = ublox.getFixType(0);
|
||||
// DEBUG_MSG("GPS fix type %d, numSats %d\n", fixType, numSatellites);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||
* Override this method to check for new locations
|
||||
*
|
||||
* @return true if we've acquired a new location
|
||||
*/
|
||||
bool UBloxGPS::lookForTime()
|
||||
{
|
||||
if (ublox.moduleQueried.gpsSecond) {
|
||||
/* Convert to unix time
|
||||
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
|
||||
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
||||
*/
|
||||
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January
|
||||
1, 1970 (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
||||
*/
|
||||
struct tm t;
|
||||
t.tm_sec = ublox.getSecond(0);
|
||||
t.tm_min = ublox.getMinute(0);
|
||||
@@ -140,45 +146,72 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
|
||||
t.tm_mon = ublox.getMonth(0) - 1;
|
||||
t.tm_year = ublox.getYear(0) - 1900;
|
||||
t.tm_isdst = false;
|
||||
perhapsSetRTC(t);
|
||||
perhapsSetRTC(RTCQualityGPS, t);
|
||||
return true;
|
||||
}
|
||||
|
||||
latitude = ublox.getLatitude(0);
|
||||
longitude = ublox.getLongitude(0);
|
||||
altitude = ublox.getAltitude(0) / 1000; // in mm convert to meters
|
||||
dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it
|
||||
heading = ublox.getHeading(0);
|
||||
numSatellites = ublox.getSIV(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
// bogus lat lon is reported as 0 or 0 (can be bogus just for one)
|
||||
// Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg!
|
||||
hasValidLocation =
|
||||
(latitude != 0) && (longitude != 0) && (latitude <= 900000000 && latitude >= -900000000) && (numSatellites > 0);
|
||||
/**
|
||||
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||
* Override this method to check for new locations
|
||||
*
|
||||
* @return true if we've acquired a new location
|
||||
*/
|
||||
bool UBloxGPS::lookForLocation()
|
||||
{
|
||||
bool foundLocation = false;
|
||||
|
||||
if (ublox.moduleQueried.SIV)
|
||||
setNumSatellites(ublox.getSIV(0));
|
||||
|
||||
if (ublox.moduleQueried.pDOP)
|
||||
dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it
|
||||
|
||||
// we only notify if position has changed due to a new fix
|
||||
if ((fixtype >= 3 && fixtype <= 4) && ublox.getP(maxWait)) // rd fixes only
|
||||
{
|
||||
if (hasValidLocation) {
|
||||
wantNewLocation = false;
|
||||
notifyObservers(NULL);
|
||||
// ublox.powerOff();
|
||||
if ((fixType >= 3 && fixType <= 4)) {
|
||||
if (ublox.moduleQueried.latitude) // rd fixes only
|
||||
{
|
||||
latitude = ublox.getLatitude(0);
|
||||
longitude = ublox.getLongitude(0);
|
||||
altitude = ublox.getAltitudeMSL(0) / 1000; // in mm convert to meters
|
||||
|
||||
// Note: heading is only currently implmented in the ublox for the 8m chipset - therefore
|
||||
// don't read it here - it will generate an ignored getPVT command on the 6ms
|
||||
// heading = ublox.getHeading(0);
|
||||
|
||||
// bogus lat lon is reported as 0 or 0 (can be bogus just for one)
|
||||
// Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg!
|
||||
foundLocation = (latitude != 0) && (longitude != 0) && (latitude <= 900000000 && latitude >= -900000000);
|
||||
}
|
||||
} else // we didn't get a location update, go back to sleep and hope the characters show up
|
||||
wantNewLocation = true;
|
||||
}
|
||||
|
||||
// Notify any status instances that are observing us
|
||||
const meshtastic::GPSStatus status =
|
||||
meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop, heading, numSatellites);
|
||||
newStatus.notifyObservers(&status);
|
||||
|
||||
// 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);
|
||||
return foundLocation;
|
||||
}
|
||||
|
||||
void UBloxGPS::startLock()
|
||||
bool UBloxGPS::whileIdle()
|
||||
{
|
||||
DEBUG_MSG("Looking for GPS lock\n");
|
||||
wantNewLocation = true;
|
||||
setPeriod(1);
|
||||
// if using i2c or serial look too see if any chars are ready
|
||||
return ublox.checkUblox(); // See if new data is available. Process bytes as they come in.
|
||||
}
|
||||
|
||||
/// If possible force the GPS into sleep/low power mode
|
||||
/// Note: ublox doesn't need a wake method, because as soon as we send chars to the GPS it will wake up
|
||||
void UBloxGPS::sleep()
|
||||
{
|
||||
// Tell GPS to power down until we send it characters on serial port (we leave vcc connected)
|
||||
ublox.powerOff();
|
||||
// setGPSPower(false);
|
||||
}
|
||||
|
||||
void UBloxGPS::wake()
|
||||
{
|
||||
fixType = 0; // assume we hace no fix yet
|
||||
|
||||
setGPSPower(true);
|
||||
|
||||
// Note: no delay needed because now we leave gps power on always and instead use ublox.powerOff()
|
||||
// Give time for the GPS to boot
|
||||
// delay(200);
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "GPS.h"
|
||||
#include "Observer.h"
|
||||
#include "../concurrency/PeriodicTask.h"
|
||||
#include "SparkFun_Ublox_Arduino_Library.h"
|
||||
|
||||
/**
|
||||
@@ -10,35 +9,63 @@
|
||||
*
|
||||
* When new data is available it will notify observers.
|
||||
*/
|
||||
class UBloxGPS : public GPS, public concurrency::PeriodicTask
|
||||
class UBloxGPS : public GPS
|
||||
{
|
||||
SFE_UBLOX_GPS ublox;
|
||||
|
||||
bool wantNewLocation = true;
|
||||
|
||||
CallbackObserver<UBloxGPS, void *> notifySleepObserver = CallbackObserver<UBloxGPS, void *>(this, &UBloxGPS::prepareSleep);
|
||||
uint8_t fixType = 0;
|
||||
|
||||
public:
|
||||
UBloxGPS();
|
||||
|
||||
/**
|
||||
* Reset our GPS back to factory settings
|
||||
*
|
||||
* @return true for success
|
||||
*/
|
||||
bool factoryReset();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Returns true if we succeeded
|
||||
*/
|
||||
virtual bool setup();
|
||||
virtual bool setupGPS();
|
||||
|
||||
virtual void doTask();
|
||||
/** Subclasses should look for serial rx characters here and feed it to their GPS parser
|
||||
*
|
||||
* Return true if we received a valid message from the GPS
|
||||
*/
|
||||
virtual bool whileIdle();
|
||||
|
||||
/** Idle processing while GPS is looking for lock */
|
||||
virtual void whileActive();
|
||||
|
||||
/**
|
||||
* Restart our lock attempt - try to get and broadcast a GPS reading ASAP
|
||||
* called after the CPU wakes from light-sleep state */
|
||||
virtual void startLock();
|
||||
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||
* Override this method to check for new locations
|
||||
*
|
||||
* @return true if we've acquired a time
|
||||
*/
|
||||
virtual bool lookForTime();
|
||||
|
||||
/**
|
||||
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||
* Override this method to check for new locations
|
||||
*
|
||||
* @return true if we've acquired a new location
|
||||
*/
|
||||
virtual bool lookForLocation();
|
||||
|
||||
/// If possible force the GPS into sleep/low power mode
|
||||
virtual void sleep();
|
||||
virtual void wake();
|
||||
|
||||
private:
|
||||
|
||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||
/// always returns 0 to indicate okay to sleep
|
||||
int prepareSleep(void *unused);
|
||||
|
||||
/// Attempt to connect to our GPS, returns false if no gps is present
|
||||
bool tryConnect();
|
||||
|
||||
/// Switch to our desired operating mode and save the settings to flash
|
||||
/// returns true for success
|
||||
bool setUBXMode();
|
||||
|
||||
uint16_t maxWait() const { return i2cAddress ? 300 : 0; /*If using i2c we must poll with wait */ }
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user