mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-14 06:42:34 +00:00
Compare commits
651 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aaa6af3f38 | ||
|
|
b11b322581 | ||
|
|
60b38215ce | ||
|
|
53e68515dd | ||
|
|
7676ae9184 | ||
|
|
b385feefa5 | ||
|
|
fc50b4abc0 | ||
|
|
0104a2eb3a | ||
|
|
23fdc9d1a9 | ||
|
|
ea4a678308 | ||
|
|
6566c4f15e | ||
|
|
6f39f1740f | ||
|
|
7b61bdbfd2 | ||
|
|
8e65db19b0 | ||
|
|
698fcdad36 | ||
|
|
c25d6e974f | ||
|
|
35aae48932 | ||
|
|
f8afa2703f | ||
|
|
cd903dceb9 | ||
|
|
5843c6f25e | ||
|
|
3005373760 | ||
|
|
7678540a93 | ||
|
|
463666aec5 | ||
|
|
5847f94ece | ||
|
|
09855b76ef | ||
|
|
f4d476d160 | ||
|
|
d88d4be63e | ||
|
|
154114e900 | ||
|
|
03f019dea2 | ||
|
|
a47524b762 | ||
|
|
fd2f5a5ba4 | ||
|
|
e66b2234f6 | ||
|
|
f35b15b09c | ||
|
|
99dac51b9d | ||
|
|
656faf4ce9 | ||
|
|
d9ab6986ad | ||
|
|
514d45c219 | ||
|
|
94b7fc76c1 | ||
|
|
e9923ac257 | ||
|
|
60ad1793e4 | ||
|
|
96a15bec3b | ||
|
|
fba676fc07 | ||
|
|
9ad14ad98b | ||
|
|
477c62082d | ||
|
|
82169d4115 | ||
|
|
362d5452d5 | ||
|
|
92e5a15399 | ||
|
|
114e0c4ada | ||
|
|
075ff26499 | ||
|
|
d65f2ede81 | ||
|
|
4a9248a451 | ||
|
|
1c6092c430 | ||
|
|
0e9ed1e82f | ||
|
|
02ce0558b9 | ||
|
|
63110f0134 | ||
|
|
d8db4449be | ||
|
|
fda98bbf58 | ||
|
|
aadba1f694 | ||
|
|
2c8d152885 | ||
|
|
37c598833c | ||
|
|
e80de4eba0 | ||
|
|
e382a181f7 | ||
|
|
85f69cb908 | ||
|
|
112a94e572 | ||
|
|
13307c502f | ||
|
|
8a1754efe8 | ||
|
|
8caa075bc6 | ||
|
|
d5deb49d20 | ||
|
|
575a15e135 | ||
|
|
dc7469c64b | ||
|
|
f54b18f733 | ||
|
|
db66e4dc00 | ||
|
|
47e614c7d6 | ||
|
|
03cb3c2145 | ||
|
|
a8d4b5479d | ||
|
|
de37e1bbab | ||
|
|
f0b8f10665 | ||
|
|
88b91de197 | ||
|
|
f22bb34c07 | ||
|
|
2d1cb7a43a | ||
|
|
dc169675e2 | ||
|
|
99f8253637 | ||
|
|
f3244caac1 | ||
|
|
6edaadf5d8 | ||
|
|
1f668046a0 | ||
|
|
cb2aa3b29f | ||
|
|
8ccd59a7d8 | ||
|
|
ddfdae64bf | ||
|
|
21a90a42e5 | ||
|
|
712d6e5c1e | ||
|
|
f02a2c0853 | ||
|
|
00d55c9daa | ||
|
|
a05e45f84b | ||
|
|
e650033f2c | ||
|
|
846fc14b4a | ||
|
|
a8a5e036f5 | ||
|
|
009f05b61d | ||
|
|
d8a6f82459 | ||
|
|
185fe8520a | ||
|
|
ce9bac34d6 | ||
|
|
e80673ed15 | ||
|
|
bdbaf9c655 | ||
|
|
7473a6c27a | ||
|
|
71a4cfefd5 | ||
|
|
4919129bbc | ||
|
|
a02175cec0 | ||
|
|
63affdd2e7 | ||
|
|
351687e5cd | ||
|
|
739f497bbd | ||
|
|
2d2ed591e9 | ||
|
|
871a85d688 | ||
|
|
a5f05019db | ||
|
|
e124d2094f | ||
|
|
5d874cd43b | ||
|
|
9f6cdadd3e | ||
|
|
6a3853ef35 | ||
|
|
9ea65c6793 | ||
|
|
8d14e97dfa | ||
|
|
1c63a70673 | ||
|
|
8022f3b920 | ||
|
|
420b7d48d9 | ||
|
|
5915669f6f | ||
|
|
52b01db306 | ||
|
|
a0d6ecb331 | ||
|
|
0271b02d50 | ||
|
|
9f61c78c0e | ||
|
|
4db176867b | ||
|
|
4b5cfaf9ba | ||
|
|
c014c1bafe | ||
|
|
96594516af | ||
|
|
bed20bf1fa | ||
|
|
4e5a445d8b | ||
|
|
99437d931e | ||
|
|
5166717298 | ||
|
|
49b5738f4f | ||
|
|
c753ea7cd1 | ||
|
|
5b1488ddf0 | ||
|
|
a34cfb0ee0 | ||
|
|
d8ff605d32 | ||
|
|
8031c47602 | ||
|
|
e522e47544 | ||
|
|
ed0f79b683 | ||
|
|
de1337d351 | ||
|
|
1b34a0c6d8 | ||
|
|
313380381b | ||
|
|
f56ff2ca20 | ||
|
|
da2ef0ac61 | ||
|
|
6a3033fa85 | ||
|
|
f3a1c5e679 | ||
|
|
a96c8fd4db | ||
|
|
da3ed9926b | ||
|
|
fd386d9d7f | ||
|
|
5d1614989e | ||
|
|
bae73a9526 | ||
|
|
f4b1678535 | ||
|
|
943517a1e4 | ||
|
|
03999e9d56 | ||
|
|
d5f177b1ee | ||
|
|
fdaed7e323 | ||
|
|
d39e775c95 | ||
|
|
829e0b6e26 | ||
|
|
1656c8d0cb | ||
|
|
2770cc7de3 | ||
|
|
48de631e04 | ||
|
|
66b11bcbd7 | ||
|
|
cda7487cbe | ||
|
|
e8f6504ec4 | ||
|
|
8f1b26bdda | ||
|
|
5bd3e4bcd0 | ||
|
|
fb3b62f8f0 | ||
|
|
e89fe2f7d9 | ||
|
|
16812c3ee4 | ||
|
|
c9cb293bf2 | ||
|
|
8e2e154cdd | ||
|
|
b7505ab575 | ||
|
|
cc47e29fac | ||
|
|
608f8349d9 | ||
|
|
3ecd93dcf6 | ||
|
|
dd0209b192 | ||
|
|
ae92567383 | ||
|
|
9f2646ba03 | ||
|
|
925e46da8c | ||
|
|
a6475ce848 | ||
|
|
9149912a2d | ||
|
|
2dadb4d7a2 | ||
|
|
1897d4703c | ||
|
|
a25cead7d7 | ||
|
|
3d919b21f6 | ||
|
|
d2de04d5b2 | ||
|
|
a753c942b9 | ||
|
|
9dd88281af | ||
|
|
e75561016b | ||
|
|
e2cbccb133 | ||
|
|
0271df0657 | ||
|
|
e05e324a7c | ||
|
|
71041e8674 | ||
|
|
c65b518432 | ||
|
|
6ba960ce47 | ||
|
|
8bf4919576 | ||
|
|
cca4867987 | ||
|
|
7aa47cf93b | ||
|
|
3089de79ee | ||
|
|
6b020149f3 | ||
|
|
34fae52e98 | ||
|
|
976bdad067 | ||
|
|
26d3ef529e | ||
|
|
53c3d9baa2 | ||
|
|
2a6858fa34 | ||
|
|
5440cbec6a | ||
|
|
19f5a5ef79 | ||
|
|
ef831a0b4d | ||
|
|
efc239533c | ||
|
|
ef1463a6a9 | ||
|
|
95e952b896 | ||
|
|
db72faca5e | ||
|
|
6961853ed7 | ||
|
|
5ec5248fe4 | ||
|
|
14fdd33972 | ||
|
|
140e29840a | ||
|
|
d1d318fad5 | ||
|
|
dfb6c0bb20 | ||
|
|
7339abbab5 | ||
|
|
27db0e27e8 | ||
|
|
a0b43b9a95 | ||
|
|
b6a202d68e | ||
|
|
125035d546 | ||
|
|
9f05ad2927 | ||
|
|
86ae69d360 | ||
|
|
c12fb69ca2 | ||
|
|
8b911aba7f | ||
|
|
2fa595523f | ||
|
|
190a3c2d6b | ||
|
|
1bf9d052fc | ||
|
|
e7b99b0d78 | ||
|
|
96313ee1c4 | ||
|
|
3e356e5866 | ||
|
|
1cc24de787 | ||
|
|
e6875d559c | ||
|
|
b73dd5b23b | ||
|
|
28d21ecdcc | ||
|
|
42298abfad | ||
|
|
c4a1fe0f36 | ||
|
|
0396f8f968 | ||
|
|
f1b840dede | ||
|
|
4da5d79e88 | ||
|
|
fc0c9bcfe3 | ||
|
|
32d0368f59 | ||
|
|
8bfe9fa8fc | ||
|
|
f10ad07f97 | ||
|
|
95df7dd8dc | ||
|
|
dcd1f7478a | ||
|
|
c2be6c4068 | ||
|
|
101eef5495 | ||
|
|
933d5424da | ||
|
|
ecf528f9b6 | ||
|
|
9b309fe0a0 | ||
|
|
29fd8dc7a5 | ||
|
|
624b95782d | ||
|
|
4fa25042c8 | ||
|
|
9f9cb030ad | ||
|
|
1d9290afc0 | ||
|
|
ad2f639195 | ||
|
|
07b4eea037 | ||
|
|
79c61cf0e0 | ||
|
|
80268ea56a | ||
|
|
bb9f595b8b | ||
|
|
2ad314f150 | ||
|
|
916e62398d | ||
|
|
4735b3ff5b | ||
|
|
cfd6483ea5 | ||
|
|
4f7a25f562 | ||
|
|
4176d79ee9 | ||
|
|
fad496378c | ||
|
|
e084699704 | ||
|
|
50213d8323 | ||
|
|
763276a2c8 | ||
|
|
71fcdba017 | ||
|
|
08227e79d0 | ||
|
|
5a4fab2506 | ||
|
|
31eb2f5337 | ||
|
|
82c1752d85 | ||
|
|
49a13bbfd3 | ||
|
|
0096f54ae9 | ||
|
|
5af122b39d | ||
|
|
4e106f4098 | ||
|
|
1f1d683f4f | ||
|
|
a8f64c3cc8 | ||
|
|
968a2d7fbc | ||
|
|
1fab9c5aac | ||
|
|
22bca31ce3 | ||
|
|
48c045a253 | ||
|
|
e9ca7792eb | ||
|
|
b1a55b4576 | ||
|
|
dd7452ad96 | ||
|
|
d7d8188093 | ||
|
|
62a893c760 | ||
|
|
a2ba9d3c44 | ||
|
|
22720e9f63 | ||
|
|
11b79a942d | ||
|
|
3c3e722181 | ||
|
|
fce31560c6 | ||
|
|
074ac33b8a | ||
|
|
2982e197e0 | ||
|
|
f69ddf168b | ||
|
|
8d985cfd37 | ||
|
|
4693302d82 | ||
|
|
814c126e67 | ||
|
|
a7d153abcb | ||
|
|
1b265eb48d | ||
|
|
94e80d3b44 | ||
|
|
f1ec95f49b | ||
|
|
7ee2643e9a | ||
|
|
2ab34357d5 | ||
|
|
b53392ab73 | ||
|
|
803d2dfefb | ||
|
|
6a54bbcf2a | ||
|
|
02dfe7564f | ||
|
|
b704911603 | ||
|
|
0193c281ef | ||
|
|
59086fd477 | ||
|
|
88a704c4d3 | ||
|
|
9f49f90acd | ||
|
|
93f69d5a94 | ||
|
|
cceecf5f8e | ||
|
|
e5d2d24e2c | ||
|
|
eb40013ddc | ||
|
|
dda946d933 | ||
|
|
178e800969 | ||
|
|
dec4870649 | ||
|
|
97f5a7c5fe | ||
|
|
7ff42b89a6 | ||
|
|
44de3bd179 | ||
|
|
15cb599cd1 | ||
|
|
038b7c9c91 | ||
|
|
724e39bc92 | ||
|
|
def86131f0 | ||
|
|
8f1c1a9049 | ||
|
|
3f3a1a11df | ||
|
|
64f6c0f5c0 | ||
|
|
7cd60d859e | ||
|
|
db11d9280c | ||
|
|
ca03110932 | ||
|
|
b8b503cb0a | ||
|
|
bb885a5110 | ||
|
|
bebaa838c4 | ||
|
|
7fa9d09d9f | ||
|
|
7bc299573f | ||
|
|
e0a1855429 | ||
|
|
5e75beff3f | ||
|
|
4f7e85c1a4 | ||
|
|
5ad30caf67 | ||
|
|
8f26ae240a | ||
|
|
d445cbe083 | ||
|
|
12599849db | ||
|
|
3c9c01189d | ||
|
|
4f3a9d8646 | ||
|
|
0c7c3f17e5 | ||
|
|
5b0451f25c | ||
|
|
8f3b33c84c | ||
|
|
2fdb75efdf | ||
|
|
fbd12e1929 | ||
|
|
ffe95f62ab | ||
|
|
3e4ccef992 | ||
|
|
309e7be00c | ||
|
|
f0f6c4950b | ||
|
|
16998ebd8d | ||
|
|
b77c068881 | ||
|
|
e94227cddd | ||
|
|
fe3cbeed3a | ||
|
|
2061706c11 | ||
|
|
a0b6d57591 | ||
|
|
3673f95fe5 | ||
|
|
c67b53b969 | ||
|
|
562b227c73 | ||
|
|
169d85d0fa | ||
|
|
bd77d47215 | ||
|
|
e40524baf0 | ||
|
|
31f735ae1f | ||
|
|
9232dfcccf | ||
|
|
fab616a15e | ||
|
|
2419ebb04e | ||
|
|
88c576798b | ||
|
|
db766f18ed | ||
|
|
f9a805e3d4 | ||
|
|
e5f9a752d8 | ||
|
|
20b41836e2 | ||
|
|
c55df4d1cc | ||
|
|
908b1020c0 | ||
|
|
78470ed3f5 | ||
|
|
4ce7df295e | ||
|
|
598abb0d23 | ||
|
|
184eac6281 | ||
|
|
176532f55f | ||
|
|
8eb3045451 | ||
|
|
2fe145aed9 | ||
|
|
ab61e64056 | ||
|
|
5b17417e0c | ||
|
|
04a83fd6b7 | ||
|
|
7730bd762a | ||
|
|
62286fff52 | ||
|
|
25cca0628d | ||
|
|
65406eaa08 | ||
|
|
ea24394110 | ||
|
|
6afeb3e456 | ||
|
|
f108c576a7 | ||
|
|
6eb74415ab | ||
|
|
0d14b69a24 | ||
|
|
d4eb47e837 | ||
|
|
86716c4397 | ||
|
|
7bbcf101c1 | ||
|
|
8e495e417c | ||
|
|
a6e0d6d751 | ||
|
|
cff255a397 | ||
|
|
3336e821ad | ||
|
|
d75d603218 | ||
|
|
bf5be49186 | ||
|
|
39ff26aa45 | ||
|
|
02b5efed96 | ||
|
|
0a07c5692c | ||
|
|
2464784f00 | ||
|
|
a8f5ff1fbd | ||
|
|
0a6af936ed | ||
|
|
0b62083e35 | ||
|
|
5ca149fac9 | ||
|
|
80c69c28cd | ||
|
|
5904d66111 | ||
|
|
fd17193d5e | ||
|
|
5c379c4a98 | ||
|
|
9c5d626e7d | ||
|
|
4757b6807e | ||
|
|
ac7f3cd603 | ||
|
|
04ad8bb533 | ||
|
|
5924690ce3 | ||
|
|
3ac3baf1c1 | ||
|
|
aa88e2bb1f | ||
|
|
640cb3bf7f | ||
|
|
6ad451eb5f | ||
|
|
93a06906cb | ||
|
|
3754950a0e | ||
|
|
06a17885eb | ||
|
|
33624b7753 | ||
|
|
2d6b9320a5 | ||
|
|
6b35454570 | ||
|
|
753a57230d | ||
|
|
9c3e63e6b5 | ||
|
|
4d4d7b38cc | ||
|
|
23f148f8ce | ||
|
|
d150549754 | ||
|
|
441c4c7b3c | ||
|
|
c22df18e28 | ||
|
|
e32b41938a | ||
|
|
ef395a1596 | ||
|
|
af35f3006c | ||
|
|
13ac686c96 | ||
|
|
05a0266fc4 | ||
|
|
3acbf31198 | ||
|
|
2e82bf3ca1 | ||
|
|
63e1a3f47e | ||
|
|
07a8972aea | ||
|
|
d1cb45aa5d | ||
|
|
1b3610d0fa | ||
|
|
2fcdc2c09f | ||
|
|
da74803ffb | ||
|
|
890511615f | ||
|
|
17de6f9532 | ||
|
|
2a15195c32 | ||
|
|
1b050a6c3d | ||
|
|
1da62e5ba1 | ||
|
|
feb6f2e59e | ||
|
|
7dabad1b70 | ||
|
|
a73f466473 | ||
|
|
1fd92cf8eb | ||
|
|
dd671ceca6 | ||
|
|
8a76931b37 | ||
|
|
b071eecce1 | ||
|
|
81734f75c8 | ||
|
|
11d57e721a | ||
|
|
9b0e329bb9 | ||
|
|
450fb7bc35 | ||
|
|
c1f8c8cca4 | ||
|
|
ea250d9cd3 | ||
|
|
be468a2183 | ||
|
|
a47d6c4d68 | ||
|
|
5386a5b224 | ||
|
|
a350b3795b | ||
|
|
cf2aa37635 | ||
|
|
d1387be015 | ||
|
|
f8857ad45b | ||
|
|
d831beab3d | ||
|
|
5c4ae6c042 | ||
|
|
a0c97825e8 | ||
|
|
cc3bac7ea0 | ||
|
|
ce21859ada | ||
|
|
54cd082bfe | ||
|
|
4c35d1f207 | ||
|
|
4d54df4c9b | ||
|
|
aa9aca2b88 | ||
|
|
a579bbcb50 | ||
|
|
12e67d3b30 | ||
|
|
50d724780a | ||
|
|
45babab8c4 | ||
|
|
3443e60718 | ||
|
|
65128a04c9 | ||
|
|
7210c1ae5e | ||
|
|
9075501917 | ||
|
|
a3b70e7538 | ||
|
|
b4b8abe6ec | ||
|
|
d647be73df | ||
|
|
42d7966858 | ||
|
|
f4d368e1f4 | ||
|
|
3a756b0e08 | ||
|
|
34ead2d68e | ||
|
|
3f1161b68b | ||
|
|
f108e24bc1 | ||
|
|
1f83f7d9df | ||
|
|
756c7ca7b3 | ||
|
|
79f7bf77c9 | ||
|
|
dee3e530de | ||
|
|
3e44c2c3e1 | ||
|
|
3886665041 | ||
|
|
c9b269c3c0 | ||
|
|
eb51c92d08 | ||
|
|
992b525588 | ||
|
|
25288d8ed6 | ||
|
|
32ac5ac9ae | ||
|
|
ef5cdefca6 | ||
|
|
f6f9dfa463 | ||
|
|
2161ce21df | ||
|
|
534691f0c2 | ||
|
|
6bc8e1b10a | ||
|
|
c8b95f7691 | ||
|
|
daf8594b99 | ||
|
|
5b54fd6359 | ||
|
|
53765298e1 | ||
|
|
0d94458c4e | ||
|
|
5e55695862 | ||
|
|
c9e2e6c386 | ||
|
|
dbbb62f63e | ||
|
|
79ce7d929c | ||
|
|
33437b5246 | ||
|
|
f4bacb9d87 | ||
|
|
0ac218b06d | ||
|
|
2ce1b4bb2c | ||
|
|
f002bee4f4 | ||
|
|
9c69326ed9 | ||
|
|
7a617d5378 | ||
|
|
90edae1ce0 | ||
|
|
2134b4db9b | ||
|
|
7b1ffb5c09 | ||
|
|
535f07d927 | ||
|
|
bfad136137 | ||
|
|
36bee8fa53 | ||
|
|
99cb0b3855 | ||
|
|
41c95eaff7 | ||
|
|
ca8a25f585 | ||
|
|
51a8700391 | ||
|
|
f099a31a29 | ||
|
|
52dbc4e15d | ||
|
|
8cabb3ea3d | ||
|
|
7a4a1af332 | ||
|
|
90ecdf229e | ||
|
|
24ac907780 | ||
|
|
5037fb830e | ||
|
|
79f1346359 | ||
|
|
35dada683a | ||
|
|
30a431788d | ||
|
|
d0b8adab75 | ||
|
|
74f7b7b622 | ||
|
|
9ec8562ce7 | ||
|
|
0cbcb7a9bd | ||
|
|
2874b22d6c | ||
|
|
3c9be48445 | ||
|
|
b5201f928b | ||
|
|
2591859df5 | ||
|
|
c411db111b | ||
|
|
0d62a2be85 | ||
|
|
9c971d3686 | ||
|
|
3c1357b732 | ||
|
|
acd5e5d29d | ||
|
|
64109b25f2 | ||
|
|
e93bc76ac9 | ||
|
|
1107c6d23d | ||
|
|
9b2155402d | ||
|
|
35cf8a4859 | ||
|
|
76f21dfd6e | ||
|
|
b101dc2c88 | ||
|
|
9bbd658b9d | ||
|
|
4999da0824 | ||
|
|
a783341df1 | ||
|
|
04258755e7 | ||
|
|
ea711ece1f | ||
|
|
e93187b630 | ||
|
|
eca3cda8b9 | ||
|
|
2874adfd69 | ||
|
|
91227b25df | ||
|
|
4dfac03402 | ||
|
|
9bc895eb57 | ||
|
|
474026d7fe | ||
|
|
9995d2a7ab | ||
|
|
3028ac3f24 | ||
|
|
8605950eef | ||
|
|
0f856dea7b | ||
|
|
2a2bc5b1d6 | ||
|
|
f87cb00940 | ||
|
|
a4ebf83b69 | ||
|
|
5dbacbddba | ||
|
|
011864a2d3 | ||
|
|
8646d8aeba | ||
|
|
87782cdf8f | ||
|
|
10289ed11a | ||
|
|
5def6a66e6 | ||
|
|
2d3bb4bb75 | ||
|
|
ffe8bf2be6 | ||
|
|
ab73033e51 | ||
|
|
c66e24aa4d | ||
|
|
651b13b3e5 | ||
|
|
951f5d11d5 | ||
|
|
2ce95ed2ce | ||
|
|
d1b7b6c5c5 | ||
|
|
425c570757 | ||
|
|
46dab2d496 | ||
|
|
f5271f5c79 | ||
|
|
375804c9e4 | ||
|
|
fe5703c684 | ||
|
|
f1c90a232f | ||
|
|
a91fba763a | ||
|
|
28588f6730 | ||
|
|
d074218049 | ||
|
|
1f07d6735b | ||
|
|
bb0b2e72c8 | ||
|
|
30c717ac2f | ||
|
|
e01680cd4b | ||
|
|
4bb308c80c | ||
|
|
12a68026e7 | ||
|
|
06449e39c1 | ||
|
|
cc6a8c8430 | ||
|
|
9aedec2c5c | ||
|
|
2221ddfca2 | ||
|
|
7dc6264fa7 | ||
|
|
f091a9505a | ||
|
|
57d5a54099 | ||
|
|
045a2031a3 | ||
|
|
0b8d7768fd | ||
|
|
c41f572cf5 | ||
|
|
bf26994d4e | ||
|
|
30ff67d46a | ||
|
|
65e6d029e0 | ||
|
|
31e9d34d1e | ||
|
|
0a796c4937 | ||
|
|
635e189007 | ||
|
|
6eba792537 |
6
.clang-format
Normal file
6
.clang-format
Normal file
@@ -0,0 +1,6 @@
|
||||
Language: Cpp
|
||||
IndentWidth: 4
|
||||
ColumnLimit: 130
|
||||
PointerAlignment: Right
|
||||
BreakBeforeBraces: Linux
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
21
.github/workflows/main.yml
vendored
Normal file
21
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Continuous Integration
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Main
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@master
|
||||
with:
|
||||
python-version: 3.x
|
||||
- name: Install Platform IO
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio
|
||||
- name: Build
|
||||
run: platformio run
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,5 @@ main/credentials.h
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[submodule "proto"]
|
||||
path = proto
|
||||
url = https://github.com/meshtastic/Meshtastic-protobufs.git
|
||||
[submodule "sdk-nrfxlib"]
|
||||
path = sdk-nrfxlib
|
||||
url = https://github.com/nrfconnect/sdk-nrfxlib.git
|
||||
32
.vscode/launch.json
vendored
32
.vscode/launch.json
vendored
@@ -1,32 +0,0 @@
|
||||
// AUTOMATICALLY GENERATED FILE. PLEASE DO NOT MODIFY IT MANUALLY
|
||||
|
||||
// PIO Unified Debugger
|
||||
//
|
||||
// Documentation: https://docs.platformio.org/page/plus/debugging.html
|
||||
// Configuration: https://docs.platformio.org/page/projectconf/section_env_debug.html
|
||||
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "platformio-debug",
|
||||
"request": "launch",
|
||||
"name": "PIO Debug",
|
||||
"executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/esp32/firmware.elf",
|
||||
"toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin",
|
||||
"preLaunchTask": {
|
||||
"type": "PlatformIO",
|
||||
"task": "Pre-Debug"
|
||||
},
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
},
|
||||
{
|
||||
"type": "platformio-debug",
|
||||
"request": "launch",
|
||||
"name": "PIO Debug (skip Pre-Debug)",
|
||||
"executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/esp32/firmware.elf",
|
||||
"toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin",
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
}
|
||||
]
|
||||
}
|
||||
17
.vscode/settings.json
vendored
17
.vscode/settings.json
vendored
@@ -42,10 +42,23 @@
|
||||
"typeinfo": "cpp",
|
||||
"string": "cpp",
|
||||
"*.xbm": "cpp",
|
||||
"list": "cpp"
|
||||
"list": "cpp",
|
||||
"atomic": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"optional": "cpp",
|
||||
"string_view": "cpp",
|
||||
"cassert": "cpp"
|
||||
},
|
||||
"cSpell.words": [
|
||||
"Blox",
|
||||
"HFSR",
|
||||
"Meshtastic",
|
||||
"descs"
|
||||
"NEMAGPS",
|
||||
"Ublox",
|
||||
"bkpt",
|
||||
"cfsr",
|
||||
"descs",
|
||||
"ocrypto",
|
||||
"protobufs"
|
||||
]
|
||||
}
|
||||
186
README.md
186
README.md
@@ -1,37 +1,76 @@
|
||||
# Meshtastic-esp32
|
||||
This is the device side code for the [meshtastic.org](https://www.meshtastic.org) project.
|
||||
# Meshtastic-device
|
||||
|
||||
Meshtastic is a project that lets you use
|
||||
inexpensive GPS mesh radios as an extensible, super long battery life mesh GPS communicator. These radios are great for hiking, skiing, paragliding -
|
||||
essentially any hobby where you don't have reliable internet access. Each member of your private mesh can always see the location and distance of all other
|
||||
This is the device side code for the [meshtastic.org](https://www.meshtastic.org) project.
|
||||
|
||||

|
||||
|
||||
Meshtastic™ is a project that lets you use
|
||||
inexpensive GPS mesh radios as an extensible, super long battery life mesh GPS communicator. These radios are great for hiking, skiing, paragliding -
|
||||
essentially any hobby where you don't have reliable internet access. Each member of your private mesh can always see the location and distance of all other
|
||||
members and any text messages sent to your group chat.
|
||||
|
||||
The radios automatically create a mesh to forward packets as needed, so everyone in the group can receive messages from even the furthest member. The radios
|
||||
The radios automatically create a mesh to forward packets as needed, so everyone in the group can receive messages from even the furthest member. The radios
|
||||
will optionally work with your phone, but no phone is required.
|
||||
|
||||
Typical time between recharging the radios should be about eight days.
|
||||
|
||||
This project is currently early-alpha, but if you have questions please join our chat [](https://gitter.im/Meshtastic/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge).
|
||||
This project is is currently in beta-testing - if you have questions please [join our discussion forum](https://meshtastic.discourse.group/).
|
||||
|
||||
This software is 100% open source and developed by a group of hobbyist experimenters. No warranty is provided, if you'd like to improve it - we'd love your help. Please post in the chat.
|
||||
This software is 100% open source and developed by a group of hobbyist experimenters. No warranty is provided, if you'd like to improve it - we'd love your help. Please post in the chat.
|
||||
|
||||
## Supported hardware
|
||||
We currently support two brands of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4000119152086.html) and the [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/). Most users should buy the T-Beam and a 18650 battery (total cost less than $35). Make
|
||||
sure to buy the frequency range which is legal for your country. For the USA, you should buy the 915MHz version. Getting a version that include a screen
|
||||
is optional, but highly recommended.
|
||||
|
||||
See (meshtastic.org) for 3D printable cases.
|
||||
We currently support three models of radios.
|
||||
|
||||
## Installing the firmware
|
||||
Prebuilt binaries for the supported radios is available in our [releases](https://github.com/geeksville/Meshtastic-esp32/releases). Your initial installation has to happen over USB from your Mac, Windows or Linux PC. Once our software is installed, all future software updates happen over bluetooth from your phone.
|
||||
- TTGO T-Beam
|
||||
|
||||
The instructions currently require a few commmand lines, but it should be pretty straightforward. Please post comments on our group chat if you have problems or successes. Steps to install:
|
||||
- [T-Beam V1.0 w/ NEO-M8N](https://www.aliexpress.com/item/33047631119.html) (Recommended)
|
||||
- [T-Beam V1.0 w/ NEO-6M](https://www.aliexpress.com/item/33050391850.html)
|
||||
- 3D printable cases
|
||||
- [T-Beam V0](https://www.thingiverse.com/thing:3773717)
|
||||
- [T-Beam V1](https://www.thingiverse.com/thing:3830711)
|
||||
|
||||
- [TTGO LORA32](https://www.aliexpress.com/item/4000211331316.html) - No GPS
|
||||
|
||||
- [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/) - No GPS
|
||||
- [3D Printable case](https://www.thingiverse.com/thing:3125854)
|
||||
|
||||
**Make sure to get the frequency for your country**
|
||||
|
||||
- US/JP/AU/NZ - 915MHz
|
||||
- CN - 470MHz
|
||||
- EU - 868MHz, 433MHz
|
||||
|
||||
Getting a version that includes a screen is optional, but highly recommended.
|
||||
|
||||
## Firmware Installation
|
||||
|
||||
Prebuilt binaries for the supported radios are available in our [releases](https://github.com/meshtastic/Meshtastic-esp32/releases). Your initial installation has to happen over USB from your Mac, Windows or Linux PC. Once our software is installed, all future software updates happen over bluetooth from your phone.
|
||||
|
||||
Please post comments on our [group chat](https://meshtastic.discourse.group/) if you have problems or successes.
|
||||
|
||||
### Installing from a GUI - Windows and Mac
|
||||
|
||||
1. Download and unzip the latest Meshtastic firmware [release](https://github.com/meshtastic/Meshtastic-esp32/releases).
|
||||
2. Download [ESPHome Flasher](https://github.com/esphome/esphome-flasher/releases) (either x86-32bit Windows or x64-64 bit Windows).
|
||||
3. Connect your radio to your USB port and open ESPHome Flasher.
|
||||
4. If your board is not showing under Serial Port then you likely need to install the drivers for the CP210X serial chip. In Windows you can check by searching “Device Manager” and ensuring the device is shown under “Ports”.
|
||||
5. If there is an error, download the drivers [here](https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers), then unzip and run the Installer application.
|
||||
6. In ESPHome Flasher, refresh the serial ports and select your board.
|
||||
7. Browse to the previously downloaded firmware and select the correct firmware based on the board type, country and frequency.
|
||||
8. Select Flash ESP.
|
||||
9. Once complete, “Done! Flashing is complete!” will be shown.
|
||||
10. Debug messages sent from the Meshtastic device can be viewed with a terminal program such as [PuTTY](https://www.putty.org/) (Windows only). Within PuTTY, click “Serial”, enter the “Serial line” com port (can be found at step 4), enter “Speed” as 921600, then click “Open”.
|
||||
|
||||
### Installing from a commandline
|
||||
|
||||
These instructions currently require a few commmand lines, but it should be pretty straightforward.
|
||||
|
||||
1. Install "pip". Pip is the python package manager we use to get the esptool installer app. Instructions [here](https://www.makeuseof.com/tag/install-pip-for-python/). If you are using OS-X, see these [special instructions](docs/software/install-OSX.md).
|
||||
2. Run "pip install --upgrade esptool" to get esptool installed on your machine.
|
||||
3. Connect your radio to your USB port.
|
||||
4. Confirm that your device is talking to your PC by running "esptool.py chip_id". The Heltec build also works on the TTGO LORA32 radio. You should see something like:
|
||||
|
||||
1. Purchase a radio (see above) with the correct frequencies for your country (915MHz for US or JP, 470MHz for CN, 870MHz for EU).
|
||||
2. Install "pip". Pip is the python package manager we use to get the esptool installer app. Instructions [here](https://www.makeuseof.com/tag/install-pip-for-python/).
|
||||
3. Run "pip install --upgrade esptool" to get esptool installed on your machine
|
||||
4. Connect your radio to your USB port
|
||||
5. Confirm that your device is talking to your PC by running "esptool.py chip_id". You should see something like:
|
||||
```
|
||||
mydir$ esptool.py chip_id
|
||||
esptool.py v2.6
|
||||
@@ -49,50 +88,107 @@ Warning: ESP32 has no Chip ID. Reading MAC instead.
|
||||
MAC: 24:6f:28:b5:36:71
|
||||
Hard resetting via RTS pin...
|
||||
```
|
||||
6. Install the correct firmware for your board with "esptool.py write_flash 0x10000 firmware-_board_-_country_.bin". For instance "esptool.py write_flash 0x10000 release/firmware-HELTEC-US-0.0.3.bin". You should see something like this:
|
||||
|
||||
5. cd into the directory where the release zip file was expanded.
|
||||
6. Install the correct firmware for your board with `device-install.sh firmware-_board_-_country_.bin`.
|
||||
- Example: `./device-install.sh firmware-HELTEC-US-0.0.3.bin`.
|
||||
7. To update run `device-update.sh firmware-_board_-_country_.bin`
|
||||
- Example: `./device-update.sh firmware-HELTEC-US-0.0.3.bin`.
|
||||
|
||||
Note: If you have previously installed meshtastic, you don't need to run this full script instead just run `esptool.py --baud 921600 write_flash 0x10000 firmware-_board_-_country_-_version_.bin`. This will be faster, also all of your current preferences will be preserved.
|
||||
|
||||
You should see something like this:
|
||||
|
||||
```
|
||||
~/development/meshtastic/meshtastic-esp32$ esptool.py write_flash 0x10000 release/firmware-HELTEC-US-0.0.3.bin
|
||||
kevinh@kevin-server:~/development/meshtastic/meshtastic-esp32/release/latest$ ./device-install.sh firmware-TBEAM-US-0.1.8.bin
|
||||
Trying to flash firmware-TBEAM-US-0.1.8.bin, but first erasing and writing system information
|
||||
esptool.py v2.6
|
||||
Found 2 serial ports
|
||||
Serial port /dev/ttyUSB0
|
||||
Connecting......
|
||||
Connecting........____
|
||||
Detecting chip type... ESP32
|
||||
Chip is ESP32D0WDQ6 (revision 1)
|
||||
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
|
||||
MAC: 24:6f:28:b5:36:71
|
||||
MAC: 24:6f:28:b2:01:6c
|
||||
Uploading stub...
|
||||
Running stub...
|
||||
Stub running...
|
||||
Changing baud rate to 921600
|
||||
Changed.
|
||||
Erasing flash (this may take a while)...
|
||||
Chip erase completed successfully in 6.1s
|
||||
Hard resetting via RTS pin...
|
||||
esptool.py v2.6
|
||||
Found 2 serial ports
|
||||
Serial port /dev/ttyUSB0
|
||||
Connecting.......
|
||||
Detecting chip type... ESP32
|
||||
Chip is ESP32D0WDQ6 (revision 1)
|
||||
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
|
||||
MAC: 24:6f:28:b2:01:6c
|
||||
Uploading stub...
|
||||
Running stub...
|
||||
Stub running...
|
||||
Changing baud rate to 921600
|
||||
Changed.
|
||||
Configuring flash size...
|
||||
Auto-detected Flash size: 8MB
|
||||
Compressed 1184800 bytes to 652635...
|
||||
Wrote 1184800 bytes (652635 compressed) at 0x00010000 in 57.6 seconds (effective 164.5 kbit/s)...
|
||||
Auto-detected Flash size: 4MB
|
||||
Flash params set to 0x0220
|
||||
Compressed 61440 bytes to 11950...
|
||||
Wrote 61440 bytes (11950 compressed) at 0x00001000 in 0.2 seconds (effective 3092.4 kbit/s)...
|
||||
Hash of data verified.
|
||||
|
||||
Leaving...
|
||||
Hard resetting via RTS pin...
|
||||
esptool.py v2.6
|
||||
Found 2 serial ports
|
||||
Serial port /dev/ttyUSB0
|
||||
Connecting.....
|
||||
Detecting chip type... ESP32
|
||||
Chip is ESP32D0WDQ6 (revision 1)
|
||||
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
|
||||
MAC: 24:6f:28:b2:01:6c
|
||||
Uploading stub...
|
||||
Running stub...
|
||||
Stub running...
|
||||
Changing baud rate to 921600
|
||||
Changed.
|
||||
Configuring flash size...
|
||||
Auto-detected Flash size: 4MB
|
||||
Compressed 1223568 bytes to 678412...
|
||||
Wrote 1223568 bytes (678412 compressed) at 0x00010000 in 10.7 seconds (effective 912.0 kbit/s)...
|
||||
Hash of data verified.
|
||||
|
||||
Leaving...
|
||||
Hard resetting via RTS pin...
|
||||
```
|
||||
7. The board will boot and show the Meshtastic logo.
|
||||
8. Please post a comment on our chat so we know if these instructions worked for you ;-). If you find bugs/have-questions post there also - we will be rapidly iterating over the next few weeks.
|
||||
|
||||
## Meshtastic Android app
|
||||
The source code for the (optional) Meshtastic Android app is [here](https://github.com/geeksville/Meshtastic-Android).
|
||||
Soon our first alpha release of will be released here:
|
||||
[](https://play.google.com/store/apps/details?id=com.geeksville.mesh&referrer=utm_source%3Dgithub%26utm_medium%3Desp32-readme%26utm_campaign%3Dmeshtastic-esp32%2520readme%26anid%3Dadmob&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1)
|
||||
7. The board will boot and show the Meshtastic logo.
|
||||
8. Please post a comment on our chat so we know if these instructions worked for you ;-). If you find bugs/have-questions post there also - we will be rapidly iterating over the next few weeks.
|
||||
|
||||
# Meshtastic Android app
|
||||
|
||||
The companion (optional) Meshtastic Android app is [here](https://play.google.com/store/apps/details?id=com.geeksville.mesh&referrer=utm_source%3Dgithub-dev-readme). You can also download it on Google Play.
|
||||
|
||||
# Python API
|
||||
|
||||
We offer a [python API](https://github.com/meshtastic/Meshtastic-python) that makes it easy to use these devices to provide mesh networking for your custom projects.
|
||||
|
||||
# Development
|
||||
The following sections are probably only interesting if you want to join us in developing the software.
|
||||
|
||||
## Power measurements
|
||||
Since one of the main goals of this project is long battery life, it is important to consider that in our software/protocol design. Based on initial measurements it seems that the current code should run about three days between charging, and with a bit more software work (see the [TODO list](TODO.md)) a battery life of eight days should be quite doable. Our current power measurements/model is in [this spreadsheet](https://docs.google.com/spreadsheets/d/1ft1bS3iXqFKU8SApU8ZLTq9r7QQEGESYnVgdtvdT67k/edit?usp=sharing).
|
||||
We'd love to have you join us on this merry little project. Please see our [development documents](./docs/software/sw-design.md) and [join us in our discussion forum](https://meshtastic.discourse.group/).
|
||||
|
||||
## Build instructions
|
||||
This project uses the simple PlatformIO build system. You can use the IDE, but for brevity
|
||||
in these instructions I describe use of their command line tool.
|
||||
# Credits
|
||||
|
||||
1. Purchase a suitable radio (see above)
|
||||
2. Install [PlatformIO](https://platformio.org/platformio-ide)
|
||||
3. Download this git repo and cd into it
|
||||
4. Plug the radio into your USB port
|
||||
4. Type "pio run -t upload" (This command will fetch dependencies, build the project and install it on the board via USB)
|
||||
5. Platform IO also installs a very nice VisualStudio Code based IDE, see their [tutorial](https://docs.platformio.org/en/latest/tutorials/espressif32/arduino_debugging_unit_testing.html) if you'd like to use it
|
||||
This project is run by volunteers. Past contributors include:
|
||||
|
||||
- @astro-arphid: Added support for 433MHz radios in europe.
|
||||
- @claesg: Various documentation fixes and 3D print enclosures
|
||||
- @girtsf: Lots of improvements
|
||||
- @spattinson: Fixed interrupt handling for the AXP192 part
|
||||
|
||||
# IMPORTANT DISCLAIMERS AND FAQ
|
||||
|
||||
For a listing of currently missing features and a FAQ click [here](docs/faq.md).
|
||||
|
||||
Copyright 2019 Geeksville Industries, LLC. GPL V3 Licensed.
|
||||
|
||||
185
TODO.md
185
TODO.md
@@ -1,185 +0,0 @@
|
||||
# High priority
|
||||
|
||||
Items to complete soon (next couple of alpha releases).
|
||||
|
||||
* text messages are not showing on local screen if screen was on
|
||||
|
||||
* The following three items are all the same:
|
||||
Have state machine properly enter deep sleep based on loss of mesh and phone comms.
|
||||
Default to enter deep sleep if no LORA received for two hours (indicates user has probably left the mesh).
|
||||
If the phone doesn't read fromradio mailbox within X seconds, assume the phone is gone and we can stop queing location msgs
|
||||
for it (because it will redownload the nodedb when it comes back)
|
||||
|
||||
* lower wait_bluetooth_secs to 30 seconds once we have the GPS power on (but GPS in sleep mode) across light sleep. For the time
|
||||
being I have it set at 2 minutes to ensure enough time for a GPS lock from scratch.
|
||||
|
||||
* retest BLE software update for both board types
|
||||
* report on wikifactory
|
||||
* send note to the guy who designed the cases
|
||||
* remeasure wake time power draws now that we run CPU down at 80MHz
|
||||
|
||||
# Medium priority
|
||||
|
||||
Items to complete before the first beta release.
|
||||
|
||||
* check fcc rules on duty cycle. we might not need to freq hop. https://www.sunfiretesting.com/LoRa-FCC-Certification-Guide/
|
||||
* use fuse bits to store the board type and region. So one load can be used on all boards
|
||||
* "AXP192 interrupt is not firing, remove this temporary polling of battery state"
|
||||
* make mesh aware network timing state machine (sync wake windows to gps time)
|
||||
* turn light sleep on aggressively (while lora is on but BLE off)
|
||||
* research and implement better mesh algorithm
|
||||
* the BLE stack is leaking about 200 bytes each time we go to light sleep
|
||||
* use gps sleep mode instead of killing its power (to allow fast position when we wake)
|
||||
* rx signal measurements -3 marginal, -9 bad, 10 great, -10 means almost unusable. So scale this into % signal strength. preferably as a graph, with an X indicating loss of comms.
|
||||
* assign every "channel" a random shared 8 bit sync word (per 4.2.13.6 of datasheet) - use that word to filter packets before even checking CRC. This will ensure our CPU will only wake for packets on our "channel"
|
||||
* Note: we do not do address filtering at the chip level, because we might need to route for the mesh
|
||||
* Use the Periodic class for both position and user periodic broadcasts
|
||||
* make debug info screen show real data (including battery level & charging)
|
||||
* don't forward redundant pings or ping responses to the phone, it just wastes phone battery
|
||||
* don't treat north as up, instead adjust shown bearings for our guess at the users heading (i.e. subtract one from the other)
|
||||
* answer to pings (because some other user is looking at our nodeinfo) with our latest location
|
||||
* show radio and gps signal strength as an image
|
||||
* only BLE advertise for a short time after the screen is on and button pressed - to save power and prevent people for sniffing for our BT app.
|
||||
* use https://platformio.org/lib/show/1260/OneButton if necessary
|
||||
* make an about to sleep screen
|
||||
* don't send location packets if we haven't moved
|
||||
* scrub default radio config settings for bandwidth/range/speed
|
||||
* add basic crypto - https://github.com/chegewara/esp32-mbedtls-aes-test/blob/master/main/main.c https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation - use ECB at first (though it is shit) because it doesn't require us to send 16 bytes of IV with each packet. Then OFB per example
|
||||
* override peekAtMessage so we can see any messages that pass through our node (even if not broadcast)? would that be useful?
|
||||
* sendToMesh can currently block for a long time, instead have it just queue a packet for a radio freertos thread
|
||||
* How do avalanche beacons work? Could this do that as well? possibly by using beacon mode feature of the RF95?
|
||||
* use std::map<NodeInfo*, std::string> in node db
|
||||
* make a HAM build: yep - that's a great idea. I'll add it to the TODO. should be pretty painless - just a new frequency list, a bool to say 'never do encryption' and use hte callsign as that node's unique id. -from Girts
|
||||
* add frequency hopping, dependent on the gps time, make the switch moment far from the time anyone is going to be transmitting
|
||||
* publish update articles on the web
|
||||
|
||||
# Pre-beta priority
|
||||
|
||||
During the beta timeframe the following improvements 'would be nice' (and yeah - I guess some of these items count as features, but it is a hobby project ;-) )
|
||||
|
||||
* Figure out why the RF95 ISR is never seeing RH_RF95_VALID_HEADER, so it is not protecting our rx packets from getting stomped on by sends
|
||||
* use BLEDevice::setPower to lower our BLE transmit power - extra range doesn't help us, it costs amps and it increases snoopability
|
||||
* make an install script to let novices install software on their boards
|
||||
* fix the frequency error reading in the RF95 RX code (can't do floating point math in an ISR ;-)
|
||||
* See CustomRF95::send and fix the problem of dropping partially received packets if we want to start sending
|
||||
* use variable length arduino Strings in protobufs (instead of current fixed buffers)
|
||||
* don't even power on bluetooth until we have some data to send to the android phone. Most of the time we should be sleeping in a lowpower "listening for lora" only mode. Once we have some packets for the phone, then power on bluetooth
|
||||
until the phone pulls those packets. Ever so often power on bluetooth just so we can see if the phone wants to send some packets. Possibly might need ULP processor to help with this wake process.
|
||||
* do hibernation mode to get power draw down to 2.5uA https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/
|
||||
* make sure main cpu is not woken for packets with bad crc or not addressed to this node - do that in the radio hw
|
||||
* enable fast init inside the gps chip
|
||||
* triple check fcc compliance
|
||||
* pick channel center frequency based on name? "dolphin" would hash to 900Mhz, "cat" to 905MHz etc? Or is that too opaque?
|
||||
* scan to find channels with low background noise?
|
||||
* share channel settings over Signal (or qr code) by embedding an an URL which is handled by the MeshUtil app.
|
||||
|
||||
# Low priority
|
||||
|
||||
Items after the first final candidate release.
|
||||
|
||||
* split out the software update utility so other projects can use it. Have the appload specify the URL for downloads.
|
||||
* Use CAD mode of the RF95 to automatically find low noise channels
|
||||
* read the PMU battery fault indicators and blink/led/warn user on screen
|
||||
* make a no bluetooth configured yet screen - include this screen in the loop if the user hasn't yet paired
|
||||
* the AXP debug output says it is trying to charge at 700mA, but the max I've seen is 180mA, so AXP registers probably need to be set to tell them the circuit can only provide 300mAish max. So that the low charge rate kicks in faster and we don't wear out batteries.
|
||||
* increase the max charging rate a bit for 18650s, currently it limits to 180mA (at 4V). Work backwards from the 500mA USB limit (at 5V) and let the AXP charge at that rate.
|
||||
* if radio params change fundamentally, discard the nodedb
|
||||
* discard very old nodedb records (> 1wk)
|
||||
* using the genpartitions based table doesn't work on TTGO so for now I stay with my old memory map
|
||||
* We let anyone BLE scan for us (FIXME, perhaps only allow that until we are paired with a phone and configured)
|
||||
* use two different buildenv flags for ttgo vs lora32. https://docs.platformio.org/en/latest/ide/vscode.html#key-bindings
|
||||
* sim gps data for testing nodes that don't have hardware
|
||||
* do debug serial logging to android over bluetooth
|
||||
* break out my bluetooth OTA software as a seperate library so others can use it
|
||||
* Heltec LoRa32 has 8MB flash, use a bigger partition table if needed - TTGO is 4MB but has PSRAM
|
||||
* add a watchdog timer
|
||||
* fix GPS.zeroOffset calculation it is wrong
|
||||
* handle millis() rollover in GPS.getTime - otherwise we will break after 50 days
|
||||
* reneable the bluetooth battery level service on the T-BEAM, because we can read battery level there
|
||||
* report esp32 device code bugs back to the mothership via android
|
||||
|
||||
# Done
|
||||
|
||||
* change the partition table to take advantage of the 4MB flash on the wroom: http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
|
||||
* wrap in nice MeshRadio class
|
||||
* add mesh send & rx
|
||||
* make message send from android go to service, then to mesh radio
|
||||
* make message receive from radio go through to android
|
||||
* test loopback tx/rx path code without using radio
|
||||
* notify phone when rx packets arrive, currently the phone polls at startup only
|
||||
* figure out if we can use PA_BOOST - yes, it seems to be on both boards
|
||||
* implement new ble characteristics
|
||||
* have MeshService keep a node DB by sniffing user messages
|
||||
* have a state machine return the correct FromRadio packet to the phone, it isn't always going to be a MeshPacket. Do a notify on fromnum to force the radio to read our state machine generated packets
|
||||
* send my_node_num when phone sends WantsNodes
|
||||
* have meshservice periodically send location data on mesh (if device has a GPS)
|
||||
* implement getCurrentTime() - set based off gps but then updated locally
|
||||
* make default owner record have valid usernames
|
||||
* message loop between node 0x28 and 0x7c
|
||||
* check in my radiolib fixes
|
||||
* figure out what is busted with rx
|
||||
* send our owner info at boot, reply if we see anyone send theirs
|
||||
* add manager layers
|
||||
* confirm second device receives that gps message and updates device db
|
||||
* send correct hw vendor in the bluetooth info - needed so the android app can update different radio models
|
||||
* correctly map nodeids to nodenums, currently we just do a proof of concept by always doing a broadcast
|
||||
* add interrupt detach/sleep mode config to lora radio so we can enable deepsleep without panicing
|
||||
* make jtag work on second board
|
||||
* implement regen owner and radio prefs
|
||||
* use a better font
|
||||
* make nice screens (boot, about to sleep, debug info (gps signal, #people), latest text, person info - one frame per person on network)
|
||||
* turn framerate from ui->state.frameState to 1 fps (or less) unless in transition
|
||||
* switch to my gui layout manager
|
||||
* make basic gui. different screens: debug, one page for each user in the user db, last received text message
|
||||
* make button press cycle between screens
|
||||
* save our node db on entry to sleep
|
||||
* fix the logo
|
||||
* sent/received packets (especially if a node was just reset) have variant of zero sometimes - I think there is a bug (race-condtion?) in the radio send/rx path.
|
||||
* DONE dynamic nodenum assignment tasks
|
||||
* make jtag debugger id stable: https://askubuntu.com/questions/49910/how-to-distinguish-between-identical-usb-to-serial-adapters
|
||||
* reported altitude is crap
|
||||
* good tips on which bands might be more free https://github.com/TheThingsNetwork/ttn/issues/119
|
||||
* finish power measurements (GPS on during sleep vs LCD on during sleep vs LORA on during sleep) and est battery life
|
||||
* make screen sleep behavior work
|
||||
* make screen advance only when a new node update arrives, a new text arrives or the user presses a button, turn off screen after a while
|
||||
* after reboot, channel number is getting reset to zero! fix!
|
||||
* send user and location events much less often
|
||||
* send location (or if not available user) when the user wakes the device from display sleep (both for testing and to improve user experience)
|
||||
* make real implementation of getNumOnlineNodes
|
||||
* very occasionally send our position and user packet based on the schedule in the radio info (if for nothing else so that other nodes update last_seen)
|
||||
* show real text info on the text screen
|
||||
* apply radio settings from android land
|
||||
* cope with nodes that have 0xff or 0x00 as the last byte of their mac
|
||||
* allow setting full radio params from android
|
||||
* add receive timestamps to messages, inserted by esp32 when message is received but then shown on the phone
|
||||
* update build to generate both board types
|
||||
* have node info screen show real info (including distance and heading)
|
||||
* blink the power led less often
|
||||
* have radiohead ISR send messages to RX queue directly, to allow that thread to block until we have something to send
|
||||
* move lora rx/tx to own thread and block on IO
|
||||
* keep our pseudo time moving forward even if we enter deep sleep (use esp32 rtc)
|
||||
* for non GPS equipped devices, set time from phone
|
||||
* GUI on oled hangs for a few seconds occasionally, but comes back
|
||||
* update local GPS position (but do not broadcast) at whatever rate the GPS is giving it
|
||||
* don't send our times to other nodes
|
||||
* don't trust times from other nodes
|
||||
* draw compass rose based off local walking track
|
||||
* add requestResponse optional bool - use for location broadcasts when sending tests
|
||||
* post sample video to signal forum
|
||||
* support non US frequencies
|
||||
* send pr https://github.com/ThingPulse/esp8266-oled-ssd1306 to tell them about this project
|
||||
* document rules for sleep wrt lora/bluetooth/screen/gps. also: if I have text messages (only) for the phone, then give a few seconds in the hopes BLE can get it across before we have to go back to sleep.
|
||||
* wake from light sleep as needed for our next scheduled periodic task (needed for gps position broadcasts etc)
|
||||
* turn bluetooth off based on our sleep policy
|
||||
* blink LED while in LS sleep mode
|
||||
* scrolling between screens based on press is busted
|
||||
* Use Neo-M8M API to put it in sleep mode (on hold until my new boards arrive)
|
||||
* update the prebuilt bins for different regulatory regions
|
||||
* don't enter NB state if we've recently talked to the phone (to prevent breaking syncing or bluetooth sw update)
|
||||
* have sw update prevent BLE sleep
|
||||
* manually delete characteristics/descs
|
||||
* leave lora receiver always on
|
||||
* protobufs are sometimes corrupted after sleep!
|
||||
* stay awake while charging
|
||||
* check gps battery voltage
|
||||
* if a position report includes ground truth time and we don't have time yet, set our clock from that. It is better than nothing.
|
||||
@@ -4,11 +4,13 @@ set -e
|
||||
|
||||
source bin/version.sh
|
||||
|
||||
# COUNTRIES="US EU CN JP"
|
||||
COUNTRIES=US
|
||||
COUNTRIES="US EU433 EU865 CN JP"
|
||||
#COUNTRIES=US
|
||||
#COUNTRIES=CN
|
||||
|
||||
BOARDS="ttgo-lora32-v2 ttgo-lora32-v1 tbeam heltec"
|
||||
#BOARDS=tbeam
|
||||
|
||||
SRCMAP=.pio/build/esp32/output.map
|
||||
SRCBIN=.pio/build/esp32/firmware.bin
|
||||
OUTDIR=release/latest
|
||||
|
||||
# We keep all old builds (and their map files in the archive dir)
|
||||
@@ -16,27 +18,41 @@ ARCHIVEDIR=release/archive
|
||||
|
||||
rm -f $OUTDIR/firmware*
|
||||
|
||||
mkdir -p $OUTDIR/bins $OUTDIR/elfs
|
||||
rm -f $OUTDIR/bins/*
|
||||
|
||||
# build the named environment and copy the bins to the release directory
|
||||
function do_build {
|
||||
ENV_NAME=$1
|
||||
echo "Building for $ENV_NAME with $PLATFORMIO_BUILD_FLAGS"
|
||||
SRCBIN=.pio/build/$ENV_NAME/firmware.bin
|
||||
SRCELF=.pio/build/$ENV_NAME/firmware.elf
|
||||
rm -f $SRCBIN
|
||||
|
||||
# The shell vars the build tool expects to find
|
||||
export HW_VERSION="1.0-$COUNTRY"
|
||||
export APP_VERSION=$VERSION
|
||||
export COUNTRY
|
||||
|
||||
pio run --jobs 4 --environment $ENV_NAME # -v
|
||||
cp $SRCBIN $OUTDIR/bins/firmware-$ENV_NAME-$COUNTRY-$VERSION.bin
|
||||
cp $SRCELF $OUTDIR/elfs/firmware-$ENV_NAME-$COUNTRY-$VERSION.elf
|
||||
}
|
||||
|
||||
# Make sure our submodules are current
|
||||
git submodule update
|
||||
|
||||
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
|
||||
platformio lib update
|
||||
|
||||
for COUNTRY in $COUNTRIES; do
|
||||
|
||||
HWVERSTR="1.0-$COUNTRY"
|
||||
COMMONOPTS="-DAPP_VERSION=$VERSION -DHW_VERSION_$COUNTRY -DHW_VERSION=$HWVERSTR -Wall -Wextra -Wno-missing-field-initializers -Isrc -Os -Wl,-Map,.pio/build/esp32/output.map -DAXP_DEBUG_PORT=Serial"
|
||||
|
||||
export PLATFORMIO_BUILD_FLAGS="-DT_BEAM_V10 $COMMONOPTS"
|
||||
echo "Building with $PLATFORMIO_BUILD_FLAGS"
|
||||
rm -f $SRCBIN $SRCMAP
|
||||
pio run # -v
|
||||
cp $SRCBIN $OUTDIR/firmware-TBEAM-$COUNTRY-$VERSION.bin
|
||||
cp $SRCMAP $ARCHIVEDIR/firmware-TBEAM-$COUNTRY-$VERSION.map
|
||||
|
||||
export PLATFORMIO_BUILD_FLAGS="-DHELTEC_LORA32 $COMMONOPTS"
|
||||
rm -f $SRCBIN $SRCMAP
|
||||
pio run # -v
|
||||
cp $SRCBIN $OUTDIR/firmware-HELTEC-$COUNTRY-$VERSION.bin
|
||||
cp $SRCMAP $ARCHIVEDIR/firmware-HELTEC-$COUNTRY-$VERSION.map
|
||||
for BOARD in $BOARDS; do
|
||||
do_build $BOARD
|
||||
done
|
||||
done
|
||||
|
||||
# keep the bins in archive also
|
||||
cp $OUTDIR/firmware* $ARCHIVEDIR
|
||||
cp $OUTDIR/bins/firmware* $OUTDIR/elfs/firmware* $ARCHIVEDIR
|
||||
|
||||
cat >$OUTDIR/curfirmwareversion.xml <<XML
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
@@ -50,6 +66,7 @@ Generated by bin/buildall.sh -->
|
||||
</resources>
|
||||
XML
|
||||
|
||||
zip $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/firmware-*-$VERSION.bin
|
||||
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
|
||||
|
||||
echo BUILT ALL
|
||||
echo BUILT ALL
|
||||
|
||||
11
bin/device-install.sh
Executable file
11
bin/device-install.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
FILENAME=$1
|
||||
|
||||
echo "Trying to flash $FILENAME, but first erasing and writing system information"
|
||||
esptool.py --baud 921600 erase_flash
|
||||
esptool.py --baud 921600 write_flash 0x1000 system-info.bin
|
||||
esptool.py --baud 921600 write_flash 0x10000 $FILENAME
|
||||
|
||||
8
bin/device-update.sh
Executable file
8
bin/device-update.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
FILENAME=$1
|
||||
|
||||
echo "Trying to update $FILENAME"
|
||||
esptool.py --baud 921600 write_flash 0x10000 $FILENAME
|
||||
3
bin/dump-ram-users.sh
Executable file
3
bin/dump-ram-users.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
arm-none-eabi-readelf -s -e .pio/build/nrf52dk/firmware.elf | head -80
|
||||
|
||||
nm -CSr --size-sort .pio/build/nrf52dk/firmware.elf | grep '^200'
|
||||
329
bin/exception_decoder.py
Executable file
329
bin/exception_decoder.py
Executable file
@@ -0,0 +1,329 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""ESP Exception Decoder
|
||||
|
||||
github: https://github.com/janLo/EspArduinoExceptionDecoder
|
||||
license: GPL v3
|
||||
author: Jan Losinski
|
||||
|
||||
Meshtastic notes:
|
||||
* original version is at: https://github.com/janLo/EspArduinoExceptionDecoder
|
||||
* version that's checked into meshtastic repo is based on: https://github.com/me21/EspArduinoExceptionDecoder
|
||||
which adds in ESP32 Backtrace decoding.
|
||||
* this also updates the defaults to use ESP32, instead of ESP8266 and defaults to the built firmware.bin
|
||||
|
||||
To use, copy the "Backtrace: 0x...." line to a file, e.g., backtrace.txt, then run:
|
||||
$ bin/exception_decoder.py backtrace.txt
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import subprocess
|
||||
from collections import namedtuple
|
||||
|
||||
import sys
|
||||
|
||||
import os
|
||||
|
||||
EXCEPTIONS = [
|
||||
"Illegal instruction",
|
||||
"SYSCALL instruction",
|
||||
"InstructionFetchError: Processor internal physical address or data error during instruction fetch",
|
||||
"LoadStoreError: Processor internal physical address or data error during load or store",
|
||||
"Level1Interrupt: Level-1 interrupt as indicated by set level-1 bits in the INTERRUPT register",
|
||||
"Alloca: MOVSP instruction, if caller's registers are not in the register file",
|
||||
"IntegerDivideByZero: QUOS, QUOU, REMS, or REMU divisor operand is zero",
|
||||
"reserved",
|
||||
"Privileged: Attempt to execute a privileged operation when CRING ? 0",
|
||||
"LoadStoreAlignmentCause: Load or store to an unaligned address",
|
||||
"reserved",
|
||||
"reserved",
|
||||
"InstrPIFDataError: PIF data error during instruction fetch",
|
||||
"LoadStorePIFDataError: Synchronous PIF data error during LoadStore access",
|
||||
"InstrPIFAddrError: PIF address error during instruction fetch",
|
||||
"LoadStorePIFAddrError: Synchronous PIF address error during LoadStore access",
|
||||
"InstTLBMiss: Error during Instruction TLB refill",
|
||||
"InstTLBMultiHit: Multiple instruction TLB entries matched",
|
||||
"InstFetchPrivilege: An instruction fetch referenced a virtual address at a ring level less than CRING",
|
||||
"reserved",
|
||||
"InstFetchProhibited: An instruction fetch referenced a page mapped with an attribute that does not permit instruction fetch",
|
||||
"reserved",
|
||||
"reserved",
|
||||
"reserved",
|
||||
"LoadStoreTLBMiss: Error during TLB refill for a load or store",
|
||||
"LoadStoreTLBMultiHit: Multiple TLB entries matched for a load or store",
|
||||
"LoadStorePrivilege: A load or store referenced a virtual address at a ring level less than CRING",
|
||||
"reserved",
|
||||
"LoadProhibited: A load referenced a page mapped with an attribute that does not permit loads",
|
||||
"StoreProhibited: A store referenced a page mapped with an attribute that does not permit stores"
|
||||
]
|
||||
|
||||
PLATFORMS = {
|
||||
"ESP8266": "lx106",
|
||||
"ESP32": "esp32"
|
||||
}
|
||||
|
||||
BACKTRACE_REGEX = re.compile(r"(?:\s+(0x40[0-2](?:\d|[a-f]|[A-F]){5}):0x(?:\d|[a-f]|[A-F]){8})\b")
|
||||
EXCEPTION_REGEX = re.compile("^Exception \\((?P<exc>[0-9]*)\\):$")
|
||||
COUNTER_REGEX = re.compile('^epc1=(?P<epc1>0x[0-9a-f]+) epc2=(?P<epc2>0x[0-9a-f]+) epc3=(?P<epc3>0x[0-9a-f]+) '
|
||||
'excvaddr=(?P<excvaddr>0x[0-9a-f]+) depc=(?P<depc>0x[0-9a-f]+)$')
|
||||
CTX_REGEX = re.compile("^ctx: (?P<ctx>.+)$")
|
||||
POINTER_REGEX = re.compile('^sp: (?P<sp>[0-9a-f]+) end: (?P<end>[0-9a-f]+) offset: (?P<offset>[0-9a-f]+)$')
|
||||
STACK_BEGIN = '>>>stack>>>'
|
||||
STACK_END = '<<<stack<<<'
|
||||
STACK_REGEX = re.compile(
|
||||
'^(?P<off>[0-9a-f]+):\W+(?P<c1>[0-9a-f]+) (?P<c2>[0-9a-f]+) (?P<c3>[0-9a-f]+) (?P<c4>[0-9a-f]+)(\W.*)?$')
|
||||
|
||||
StackLine = namedtuple("StackLine", ["offset", "content"])
|
||||
|
||||
|
||||
class ExceptionDataParser(object):
|
||||
def __init__(self):
|
||||
self.exception = None
|
||||
|
||||
self.epc1 = None
|
||||
self.epc2 = None
|
||||
self.epc3 = None
|
||||
self.excvaddr = None
|
||||
self.depc = None
|
||||
|
||||
self.ctx = None
|
||||
|
||||
self.sp = None
|
||||
self.end = None
|
||||
self.offset = None
|
||||
|
||||
self.stack = []
|
||||
|
||||
def _parse_backtrace(self, line):
|
||||
if line.startswith('Backtrace:'):
|
||||
self.stack = [StackLine(offset=0, content=(addr,)) for addr in BACKTRACE_REGEX.findall(line)]
|
||||
return None
|
||||
return self._parse_backtrace
|
||||
|
||||
def _parse_exception(self, line):
|
||||
match = EXCEPTION_REGEX.match(line)
|
||||
if match is not None:
|
||||
self.exception = int(match.group('exc'))
|
||||
return self._parse_counters
|
||||
return self._parse_exception
|
||||
|
||||
def _parse_counters(self, line):
|
||||
match = COUNTER_REGEX.match(line)
|
||||
if match is not None:
|
||||
self.epc1 = match.group("epc1")
|
||||
self.epc2 = match.group("epc2")
|
||||
self.epc3 = match.group("epc3")
|
||||
self.excvaddr = match.group("excvaddr")
|
||||
self.depc = match.group("depc")
|
||||
return self._parse_ctx
|
||||
return self._parse_counters
|
||||
|
||||
def _parse_ctx(self, line):
|
||||
match = CTX_REGEX.match(line)
|
||||
if match is not None:
|
||||
self.ctx = match.group("ctx")
|
||||
return self._parse_pointers
|
||||
return self._parse_ctx
|
||||
|
||||
def _parse_pointers(self, line):
|
||||
match = POINTER_REGEX.match(line)
|
||||
if match is not None:
|
||||
self.sp = match.group("sp")
|
||||
self.end = match.group("end")
|
||||
self.offset = match.group("offset")
|
||||
return self._parse_stack_begin
|
||||
return self._parse_pointers
|
||||
|
||||
def _parse_stack_begin(self, line):
|
||||
if line == STACK_BEGIN:
|
||||
return self._parse_stack_line
|
||||
return self._parse_stack_begin
|
||||
|
||||
def _parse_stack_line(self, line):
|
||||
if line != STACK_END:
|
||||
match = STACK_REGEX.match(line)
|
||||
if match is not None:
|
||||
self.stack.append(StackLine(offset=match.group("off"),
|
||||
content=(match.group("c1"), match.group("c2"), match.group("c3"),
|
||||
match.group("c4"))))
|
||||
return self._parse_stack_line
|
||||
return None
|
||||
|
||||
def parse_file(self, file, platform, stack_only=False):
|
||||
if platform == 'ESP32':
|
||||
func = self._parse_backtrace
|
||||
else:
|
||||
func = self._parse_exception
|
||||
if stack_only:
|
||||
func = self._parse_stack_begin
|
||||
|
||||
for line in file:
|
||||
func = func(line.strip())
|
||||
if func is None:
|
||||
break
|
||||
|
||||
if func is not None:
|
||||
print("ERROR: Parser not complete!")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class AddressResolver(object):
|
||||
def __init__(self, tool_path, elf_path):
|
||||
self._tool = tool_path
|
||||
self._elf = elf_path
|
||||
self._address_map = {}
|
||||
|
||||
def _lookup(self, addresses):
|
||||
cmd = [self._tool, "-aipfC", "-e", self._elf] + [addr for addr in addresses if addr is not None]
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
output = subprocess.check_output(cmd)
|
||||
else:
|
||||
output = subprocess.check_output(cmd, encoding="utf-8")
|
||||
|
||||
line_regex = re.compile("^(?P<addr>[0-9a-fx]+): (?P<result>.+)$")
|
||||
|
||||
last = None
|
||||
for line in output.splitlines():
|
||||
line = line.strip()
|
||||
match = line_regex.match(line)
|
||||
|
||||
if match is None:
|
||||
if last is not None and line.startswith('(inlined by)'):
|
||||
line = line [12:].strip()
|
||||
self._address_map[last] += ("\n \-> inlined by: " + line)
|
||||
continue
|
||||
|
||||
if match.group("result") == '?? ??:0':
|
||||
continue
|
||||
|
||||
self._address_map[match.group("addr")] = match.group("result")
|
||||
last = match.group("addr")
|
||||
|
||||
def fill(self, parser):
|
||||
addresses = [parser.epc1, parser.epc2, parser.epc3, parser.excvaddr, parser.sp, parser.end, parser.offset]
|
||||
for line in parser.stack:
|
||||
addresses.extend(line.content)
|
||||
|
||||
self._lookup(addresses)
|
||||
|
||||
def _sanitize_addr(self, addr):
|
||||
if addr.startswith("0x"):
|
||||
addr = addr[2:]
|
||||
|
||||
fill = "0" * (8 - len(addr))
|
||||
return "0x" + fill + addr
|
||||
|
||||
def resolve_addr(self, addr):
|
||||
out = self._sanitize_addr(addr)
|
||||
|
||||
if out in self._address_map:
|
||||
out += ": " + self._address_map[out]
|
||||
|
||||
return out
|
||||
|
||||
def resolve_stack_addr(self, addr, full=True):
|
||||
addr = self._sanitize_addr(addr)
|
||||
if addr in self._address_map:
|
||||
return addr + ": " + self._address_map[addr]
|
||||
|
||||
if full:
|
||||
return "[DATA (0x" + addr + ")]"
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def print_addr(name, value, resolver):
|
||||
print("{}:{} {}".format(name, " " * (8 - len(name)), resolver.resolve_addr(value)))
|
||||
|
||||
|
||||
def print_stack_full(lines, resolver):
|
||||
print("stack:")
|
||||
for line in lines:
|
||||
print(line.offset + ":")
|
||||
for content in line.content:
|
||||
print(" " + resolver.resolve_stack_addr(content))
|
||||
|
||||
|
||||
def print_stack(lines, resolver):
|
||||
print("stack:")
|
||||
for line in lines:
|
||||
for content in line.content:
|
||||
out = resolver.resolve_stack_addr(content, full=False)
|
||||
if out is None:
|
||||
continue
|
||||
print(out)
|
||||
|
||||
|
||||
def print_result(parser, resolver, platform, full=True, stack_only=False):
|
||||
if platform == 'ESP8266' and not stack_only:
|
||||
print('Exception: {} ({})'.format(parser.exception, EXCEPTIONS[parser.exception]))
|
||||
|
||||
print("")
|
||||
print_addr("epc1", parser.epc1, resolver)
|
||||
print_addr("epc2", parser.epc2, resolver)
|
||||
print_addr("epc3", parser.epc3, resolver)
|
||||
print_addr("excvaddr", parser.excvaddr, resolver)
|
||||
print_addr("depc", parser.depc, resolver)
|
||||
|
||||
print("")
|
||||
print("ctx: " + parser.ctx)
|
||||
|
||||
print("")
|
||||
print_addr("sp", parser.sp, resolver)
|
||||
print_addr("end", parser.end, resolver)
|
||||
print_addr("offset", parser.offset, resolver)
|
||||
|
||||
print("")
|
||||
if full:
|
||||
print_stack_full(parser.stack, resolver)
|
||||
else:
|
||||
print_stack(parser.stack, resolver)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="decode ESP Stacktraces.")
|
||||
|
||||
parser.add_argument("-p", "--platform", help="The platform to decode from", choices=PLATFORMS.keys(),
|
||||
default="ESP32")
|
||||
parser.add_argument("-t", "--tool", help="Path to the xtensa toolchain",
|
||||
default="~/.platformio/packages/toolchain-xtensa32/")
|
||||
parser.add_argument("-e", "--elf", help="path to elf file",
|
||||
default=".pio/build/esp32/firmware.elf")
|
||||
parser.add_argument("-f", "--full", help="Print full stack dump", action="store_true")
|
||||
parser.add_argument("-s", "--stack_only", help="Decode only a stractrace", action="store_true")
|
||||
parser.add_argument("file", help="The file to read the exception data from ('-' for STDIN)", default="-")
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parse_args()
|
||||
|
||||
if args.file == "-":
|
||||
file = sys.stdin
|
||||
else:
|
||||
if not os.path.exists(args.file):
|
||||
print("ERROR: file " + args.file + " not found")
|
||||
sys.exit(1)
|
||||
file = open(args.file, "r")
|
||||
|
||||
addr2line = os.path.join(os.path.abspath(os.path.expanduser(args.tool)),
|
||||
"bin/xtensa-" + PLATFORMS[args.platform] + "-elf-addr2line")
|
||||
if os.name == 'nt':
|
||||
addr2line += '.exe'
|
||||
if not os.path.exists(addr2line):
|
||||
print("ERROR: addr2line not found (" + addr2line + ")")
|
||||
|
||||
elf_file = os.path.abspath(os.path.expanduser(args.elf))
|
||||
if not os.path.exists(elf_file):
|
||||
print("ERROR: elf file not found (" + elf_file + ")")
|
||||
|
||||
parser = ExceptionDataParser()
|
||||
resolver = AddressResolver(addr2line, elf_file)
|
||||
|
||||
parser.parse_file(file, args.platform, args.stack_only)
|
||||
resolver.fill(parser)
|
||||
|
||||
print_result(parser, resolver, args.platform, args.full, args.stack_only)
|
||||
4
bin/nrf52-console.sh
Executable file
4
bin/nrf52-console.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
|
||||
|
||||
# JLinkRTTViewer
|
||||
JLinkRTTClient
|
||||
3
bin/nrf52-gdbserver.sh
Executable file
3
bin/nrf52-gdbserver.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52840_XXAA
|
||||
@@ -3,4 +3,4 @@ set -e
|
||||
|
||||
source bin/version.sh
|
||||
|
||||
esptool.py --baud 921600 write_flash 0x10000 release/firmware-HELTEC-US-$VERSION.bin
|
||||
esptool.py --baud 921600 write_flash 0x10000 release/latest/bins/firmware-heltec-US-$VERSION.bin
|
||||
|
||||
6
bin/program-release-tbeam.sh
Executable file
6
bin/program-release-tbeam.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
|
||||
set -e
|
||||
|
||||
source bin/version.sh
|
||||
|
||||
esptool.py --baud 921600 write_flash 0x10000 release/latest/bins/firmware-tbeam-US-$VERSION.bin
|
||||
1
bin/read-system-info.sh
Executable file
1
bin/read-system-info.sh
Executable file
@@ -0,0 +1 @@
|
||||
esptool.py --baud 921600 read_flash 0x1000 0xf000 system-info.img
|
||||
@@ -1 +1,6 @@
|
||||
/home/kevinh/packages/nanopb-0.4.1-linux-x86/generator-bin/protoc --nanopb_out=-v:src -I=../MeshUtil/app/src/main/proto mesh.proto
|
||||
#!/bin/bash
|
||||
|
||||
echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.1"
|
||||
# the nanopb tool seems to require that the .options file be in the current directory!
|
||||
cd proto
|
||||
../../nanopb-0.4.1-linux-x86/generator-bin/protoc --nanopb_out=-v:../src/mesh -I=../proto mesh.proto
|
||||
|
||||
1
bin/run-1-monitor.sh
Executable file
1
bin/run-1-monitor.sh
Executable file
@@ -0,0 +1 @@
|
||||
pio run --upload-port /dev/ttyUSB1 -t upload -t monitor
|
||||
@@ -1 +0,0 @@
|
||||
pio device monitor -b 115200
|
||||
1
bin/start-terminal0.sh
Executable file
1
bin/start-terminal0.sh
Executable file
@@ -0,0 +1 @@
|
||||
pio device monitor -b 921600
|
||||
1
bin/start-terminal1.sh
Executable file
1
bin/start-terminal1.sh
Executable file
@@ -0,0 +1 @@
|
||||
pio device monitor -p /dev/ttyUSB1 -b 921600
|
||||
@@ -1,3 +1,3 @@
|
||||
|
||||
|
||||
export VERSION=0.0.5
|
||||
export VERSION=0.7.8
|
||||
46
boards/nrf52840_dk_modified.json
Normal file
46
boards/nrf52840_dk_modified.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_PCA10056 -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [["0x239A", "0x4404"]],
|
||||
"usb_product": "SimPPR",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "pca10056-rc-clock",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52840.svd"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "A modified NRF52840-DK devboard (Adafruit BSP)",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"require_upload_port": true,
|
||||
"speed": 115200,
|
||||
"protocol": "jlink",
|
||||
"protocols": ["jlink", "nrfjprog", "stlink"]
|
||||
},
|
||||
"url": "https://meshtastic.org/",
|
||||
"vendor": "Nordic Semi"
|
||||
}
|
||||
46
boards/ppr.json
Normal file
46
boards/ppr.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_PPR -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [["0x239A", "0x4403"]],
|
||||
"usb_product": "PPR",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "ppr",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52840.svd"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Meshtastic PPR (Adafruit BSP)",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"require_upload_port": true,
|
||||
"speed": 115200,
|
||||
"protocol": "jlink",
|
||||
"protocols": ["jlink", "nrfjprog", "stlink"]
|
||||
},
|
||||
"url": "https://meshtastic.org/",
|
||||
"vendor": "Othernet"
|
||||
}
|
||||
9
docs/.well-known/assetlinks.json
Normal file
9
docs/.well-known/assetlinks.json
Normal file
@@ -0,0 +1,9 @@
|
||||
[{
|
||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "com.geeksville.mesh",
|
||||
"sha256_cert_fingerprints":
|
||||
["D0:05:E7:8B:D2:1B:FA:94:56:1D:6B:90:EB:53:07:1A:74:4F:D9:C2:6F:13:87:6A:D9:17:4F:C2:59:48:02:9D", "42:17:52:DC:57:40:38:B5:6B:86:61:1C:2F:47:DB:2B:0F:A2:EA:59:E1:18:9C:AA:90:8D:37:D6:CD:40:0E:BB", "A9:3B:45:65:68:C1:75:DB:08:00:A0:9F:06:77:7F:89:2D:81:24:32:AD:B8:A3:DF:73:BC:3E:7F:06:C8:0C:6D"]
|
||||
}
|
||||
}]
|
||||
@@ -1,80 +1,79 @@
|
||||
# What is Meshtastic?
|
||||
|
||||
Meshtastic is a project that lets you use
|
||||
inexpensive ($30 ish) GPS radios as an extensible, super long battery life mesh GPS communicator. These radios are great for hiking, skiing, paragliding -
|
||||
essentially any hobby where you don't have reliable internet access. Each member of your private mesh can always see the location and distance of all other
|
||||
members and any text messages sent to your group chat.
|
||||
Meshtastic™ is a project that lets you use
|
||||
inexpensive (\$30 ish) GPS radios as an extensible, long battery life, secure, mesh GPS communicator. These radios are great for hiking, skiing, paragliding - essentially any hobby where you don't have reliable internet access. Each member of your private mesh can always see the location and distance of all other members and any text messages sent to your group chat.
|
||||
|
||||
The radios automatically create a mesh to forward packets as needed, so everyone in the group can receive messages from even the furthest member. The radios
|
||||
will optionally work with your phone, but no phone is required.
|
||||
The radios automatically create a mesh to forward packets as needed, so everyone in the group can receive messages from even the furthest member. The radios will optionally work with your phone, but no phone is required.
|
||||
|
||||
Note: Questions after reading this? See our new [forum](https://meshtastic.discourse.group/).
|
||||
|
||||
### Uses
|
||||
|
||||
* Outdoor sports where cellular coverage is limited. (Hiking, Skiing, Boating, Paragliding, Gliders etc..)
|
||||
* Applications where closed source GPS communicators just won't cut it (it is easy to add features for glider pilots etc...)
|
||||
* Secure long-range communication within groups without depending on cellular providers
|
||||
* Finding your lost kids ;-)
|
||||
- Outdoor sports where cellular coverage is limited. (Hiking, Skiing, Boating, Paragliding, Gliders etc..)
|
||||
- Applications where closed source GPS communicators just won't cut it (it is easy to add features for glider pilots etc...)
|
||||
- Secure long-range communication within groups without depending on cellular providers
|
||||
- Finding your lost kids ;-)
|
||||
- Through our [python API](https://pypi.org/project/meshtastic/) use these inexpensive radios to easily add mesh networking to your own projects.
|
||||
|
||||
[](https://www.youtube.com/watch?v=WlNbMbVZlHI "Meshtastic early demo")
|
||||
|
||||
### Features
|
||||
Not all of these features are fully implemented yet - see below. But they should be in by the time we decide to call this project beta (three months?)
|
||||
|
||||
* Very long battery life (should be about eight days with the beta software)
|
||||
* Built in GPS and [LoRa](https://en.wikipedia.org/wiki/LoRa) radio, but we manage the radio automatically for you
|
||||
* Long range - a few miles per node but each node will forward packets as needed
|
||||
* Shows direction and distance to all members of your channel
|
||||
* Directed or broadcast text messages for channel members
|
||||
* Open and extensible codebase supporting multiple hardware vendors - no lock in to one vendor
|
||||
* Communication API for bluetooth devices (such as our Android app) to use the mesh. So if you have some application that needs long range low power networking, this might work for you.
|
||||
* Eventually (within a couple of months) we should have a modified version of Signal that works with this project.
|
||||
* Very easy sharing of private secured channels. Just share a special link or QR code with other users and they can join your encrypted mesh
|
||||
|
||||
This project is currently in early alpha - if you have questions please join our chat [](https://gitter.im/Meshtastic/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge).
|
||||
Not all of these features are fully implemented yet - see **important** disclaimers below. But they should be in by the time we decide to call this project beta (three months?)
|
||||
|
||||
This software is 100% open source and developed by a group of hobbyist experimenters. No warranty is provided, if you'd like to improve it - we'd love your help. Please post in the [chat](https://gitter.im/Meshtastic/community).
|
||||
- Very long battery life (should be about eight days with the beta software)
|
||||
- Built in GPS and [LoRa](https://en.wikipedia.org/wiki/LoRa) radio, but we manage the radio automatically for you
|
||||
- Long range - a few miles per node but each node will forward packets as needed
|
||||
- Secure - channels are encrypted by AES256 (But see important disclaimers below wrt this feature)
|
||||
- Shows direction and distance to all members of your channel
|
||||
- Directed or broadcast text messages for channel members
|
||||
- Open and extensible codebase supporting multiple hardware vendors - no lock in to one vendor
|
||||
- Communication API for bluetooth devices (such as our Android app) to use the mesh. An iOS application is in the works. And [Meshtastic-python](https://pypi.org/project/meshtastic/) provides access from desktop computers.
|
||||
- Very easy sharing of private secured channels. Just share a special link or QR code with friends and they can join your encrypted mesh
|
||||
|
||||
This project is currently in beta testing but it is fairly stable and feature complete - if you have questions please [join our discussion forum](https://meshtastic.discourse.group/).
|
||||
|
||||
This software is 100% open source and developed by a group of hobbyist experimenters. No warranty is provided, if you'd like to improve it - we'd love your help. Please post in the [forum](https://meshtastic.discourse.group/).
|
||||
|
||||
# Updates
|
||||
|
||||
* 02/25/2020 - 0.0.4 of the Android app is released. This is a very early alpha, see below to join the alpha-testers group.
|
||||
* 02/23/2020 - 0.0.4 release. Still very bleeding edge but much closer to the final power management, a charged T-BEAM should run for many days with this load. If you'd like to try it, we'd love your feedback. Click [here](https://github.com/geeksville/Meshtastic-esp32/blob/master/README.md) for instructions.
|
||||
* 02/20/2020 - Our first alpha release (0.0.3) of the radio software is ready for early users.
|
||||
Note: Updates are happening almost daily, only major updates are listed below. For more details see our forum.
|
||||
|
||||
- 06/04/2020 - 0.6.7 Beta releases of both the application and the device code are released. Features are fairly solid now with a sizable number of users.
|
||||
- 04/28/2020 - 0.6.0 [Python API](https://pypi.org/project/meshtastic/) released. Makes it easy to use meshtastic devices as "zero config / just works" mesh transport adapters for other projects.
|
||||
- 04/20/2020 - 0.4.3 Pretty solid now both for the android app and the device code. Many people have donated translations and code. Probably going to call it a beta soon.
|
||||
- 03/03/2020 - 0.0.9 of the Android app and device code is released. Still an alpha but fairly functional.
|
||||
- 02/25/2020 - 0.0.4 of the Android app is released. This is a very early alpha, see below to join the alpha-testers group.
|
||||
- 02/23/2020 - 0.0.4 release. Still very bleeding edge but much closer to the final power management, a charged T-BEAM should run for many days with this load. If you'd like to try it, we'd love your feedback. Click [here](https://github.com/meshtastic/Meshtastic-esp32/blob/master/README.md) for instructions.
|
||||
- 02/20/2020 - Our first alpha release (0.0.3) of the radio software is ready brave early people.
|
||||
|
||||
## Meshtastic Android app
|
||||
Once out of alpha the companion Android application will be released here:
|
||||
|
||||
[](https://play.google.com/store/apps/details?id=com.geeksville.mesh&referrer=utm_source%3Dhomepage%26anid%3Dadmob)
|
||||
Our Android application is available here:
|
||||
|
||||
But if you want the bleeding edge app now, we'd love to have your help testing. Three steps to opt-in to the alpha- test:
|
||||
[](https://play.google.com/store/apps/details?id=com.geeksville.mesh&referrer=utm_source%3Dgithub-homepage)
|
||||
|
||||
The link above will return older more stable releases. We would prefer if you join our alpha-test group, because the application is rapidly improving. Three steps to opt-in to the alpha- test:
|
||||
|
||||
1. Join [this Google group](https://groups.google.com/forum/#!forum/meshtastic-alpha-testers) with the account you use in Google Play.
|
||||
2. Go to this [URL](https://play.google.com/apps/testing/com.geeksville.mesh) to opt-in to the alpha test.
|
||||
3. If you encounter any problems or have questions, post in our gitter chat and we'll help.
|
||||
3. If you encounter any problems or have questions, post in our [forum](https://meshtastic.discourse.group/) and we'll help.
|
||||
|
||||
If you'd like to help with development, the source code is [on github](https://github.com/geeksville/Meshtastic-Android).
|
||||
If you'd like to help with development, the source code is [on github](https://github.com/meshtastic/Meshtastic-Android).
|
||||
|
||||
## Supported hardware
|
||||
We currently support two brands of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4000119152086.html) and the [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/). Most users should buy the T-Beam and a 18650 battery (total cost less than $35). Make
|
||||
sure to buy the frequency range which is legal for your country. For the USA, you should buy the 915MHz version. Getting a version that include a screen
|
||||
is optional, but highly recommended.
|
||||
|
||||
Instructions for installing prebuilt firmware can be found [here](https://github.com/geeksville/Meshtastic-esp32/blob/master/README.md).
|
||||
We currently support two brands of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4000119152086.html) and the [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/). Most people should buy the T-Beam and a 18650 battery (total cost less than \$35). Make
|
||||
sure to buy the frequency range which is legal for your country. For the USA, you should buy the 915MHz version. Getting a version that include a screen is optional, but highly recommended.
|
||||
|
||||
For a nice TTGO 3D printable case see this [design](https://www.thingiverse.com/thing:3773717) by [bsiege](https://www.thingiverse.com/bsiege).
|
||||
For a nice Heltec 3D printable case see this [design](https://www.thingiverse.com/thing:3125854) by [ornotermes](https://www.thingiverse.com/ornotermes).
|
||||
Instructions for installing prebuilt firmware can be found [here](https://github.com/meshtastic/Meshtastic-esp32/blob/master/README.md).
|
||||
|
||||
# Disclaimers
|
||||
For a nice printable cases:
|
||||
|
||||
This project is still pretty young but moving at a pretty good pace. Not all features are fully implemented in the current alpha builds.
|
||||
Most of these problems should be solved by the beta release:
|
||||
1. TTGO T-Beam V0 see this [design](https://www.thingiverse.com/thing:3773717) by [bsiege](https://www.thingiverse.com/bsiege).
|
||||
2. TTGO T_Beam V1 see this [design](https://www.thingiverse.com/thing:3830711) by [rwanrooy](https://www.thingiverse.com/rwanrooy) or this [remix](https://www.thingiverse.com/thing:3949330) by [8ung](https://www.thingiverse.com/8ung)
|
||||
3. Heltec Lora32 see this [design](https://www.thingiverse.com/thing:3125854) by [ornotermes](https://www.thingiverse.com/ornotermes).
|
||||
|
||||
* We don't make these devices and they haven't been tested by UL or the FCC. If you use them you are experimenting and we can't promise they won't burn your house down ;-)
|
||||
* Encryption is turned off for now
|
||||
* A number of (straightforward) software work items have to be completed before battery life matches our measurements, currently battery life is about two days. Join us on chat if you want the spreadsheet of power measurements/calculations.
|
||||
* The current Android GUI is pretty ugly still
|
||||
* The Android API needs to be documented better
|
||||
* The Bluetooth API needs to be documented better
|
||||
* The mesh protocol is turned off for now, currently we only send packets one hop distant
|
||||
* No one has written an iOS app yet ;-)
|
||||
# IMPORTANT DISCLAIMERS AND FAQ
|
||||
|
||||
For more details see the [device software TODO](https://github.com/geeksville/Meshtastic-esp32/blob/master/TODO.md) or the [Android app TODO](https://github.com/geeksville/Meshtastic-Android/blob/master/TODO.md).
|
||||
For a listing of currently missing features and a FAQ click [here](faq.md).
|
||||
|
||||
@@ -3,3 +3,6 @@ theme: jekyll-theme-cayman
|
||||
title: Meshtastic
|
||||
description: An opensource hiking, pilot, skiing, Signal-App-extending GPS mesh communicator
|
||||
google_analytics: G-DRZ5H5EXHV
|
||||
|
||||
include: [".well-known"]
|
||||
|
||||
|
||||
34
docs/faq.md
Normal file
34
docs/faq.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Disclaimers
|
||||
|
||||
This project is still pretty young but moving at a pretty good pace. Not all features are fully implemented in the current alpha builds.
|
||||
Most of these problems should be solved by the beta release (within three months):
|
||||
|
||||
- We don't make these devices and they haven't been tested by UL or the FCC. If you use them you are experimenting and we can't promise they won't burn your house down ;-)
|
||||
- The encryption implementation is good but see this list of [caveats](software/crypto.md#summary-of-strengthsweaknesses-of-our-current-implementation) to determine risks you might face.
|
||||
- A number of (straightforward) software work items have to be completed before battery life matches our measurements, currently battery life is about three days. Join us on chat if you want the spreadsheet of power measurements/calculations.
|
||||
- The Android API needs to be documented better
|
||||
- No one has written an iOS app yet. But some good souls [are talking about it](https://github.com/meshtastic/Meshtastic-esp32/issues/14) ;-)
|
||||
|
||||
For more details see the [device software TODO](https://github.com/meshtastic/Meshtastic-esp32/blob/master/docs/software/TODO.md) or the [Android app TODO](https://github.com/meshtastic/Meshtastic-Android/blob/master/TODO.md).
|
||||
|
||||
# FAQ
|
||||
|
||||
If you have a question missing from this faq, please [ask in our discussion forum](https://meshtastic.discourse.group/). And if you are feeling extra generous send in a pull-request for this faq.md with whatever we answered ;-).
|
||||
|
||||
## Q: Which of the various supported radios should I buy?
|
||||
|
||||
Basically you just need the radio + (optional but recommended) battery. The TBEAM is usually better because it has gps and huge battery socket. The Heltec is basically the same hardware but without the GPS (the phone provides position data to the radio in that case, so the behavior is similar - but it does burn some battery in the phone). Also the battery for the Heltec can be smaller.
|
||||
|
||||
In addition to Aliexpress, (banggood.com) usually has stock and faster shipping, or Amazon. If buying a TBEAM, make sure to buy a version that includes the OLED screen - this project doesn't absolutely require the screen, but we use it if is installed.
|
||||
|
||||
@claesg has added links to various 3D printable cases, you can see them at (www.meshtastic.org).
|
||||
|
||||
## Q: Do you have plans to commercialize this project
|
||||
|
||||
Nope. though if some other person/group wanted to use this software and a more customized device we think that would be awesome (as long as they obey the GPL license).
|
||||
|
||||
## Q: Does this project use patented algorithms?
|
||||
|
||||
(Kindly borrowed from the geeks at [ffmpeg](http://ffmpeg.org/legal.html))
|
||||
|
||||
We do not know, we are not lawyers so we are not qualified to answer this. Also we have never read patents to implement any part of this, so even if we were qualified we could not answer it as we do not know what is patented. Furthermore the sheer number of software patents makes it impossible to read them all so no one (lawyer or not) could answer such a question with a definite no. We are merely geeks experimenting on a fun and free project.
|
||||
BIN
docs/hardware/AN1200_22_Semtech_LoRa_Basics_v2_STD.pdf
Normal file
BIN
docs/hardware/AN1200_22_Semtech_LoRa_Basics_v2_STD.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/AXP192 Datasheet_v1.1_en_draft_2211.pdf
Normal file
BIN
docs/hardware/AXP192 Datasheet_v1.1_en_draft_2211.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/DS_SX1276-7-8-9_W_APP_V6.pdf
Normal file
BIN
docs/hardware/DS_SX1276-7-8-9_W_APP_V6.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/NEO-6_DataSheet_(GPS.G6-HW-09005).pdf
Normal file
BIN
docs/hardware/NEO-6_DataSheet_(GPS.G6-HW-09005).pdf
Normal file
Binary file not shown.
BIN
docs/hardware/RFM95_96_97_98W.pdf
Normal file
BIN
docs/hardware/RFM95_96_97_98W.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/T_BeamV1.0.pdf
Normal file
BIN
docs/hardware/T_BeamV1.0.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/WIFI_LoRa_32_V2(868-915).PDF
Normal file
BIN
docs/hardware/WIFI_LoRa_32_V2(868-915).PDF
Normal file
Binary file not shown.
BIN
docs/hardware/axp192-complete-datasheet-chinese.pdf
Normal file
BIN
docs/hardware/axp192-complete-datasheet-chinese.pdf
Normal file
Binary file not shown.
6
docs/hardware/cubecell-TODO.md
Normal file
6
docs/hardware/cubecell-TODO.md
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
https://heltec-automation-docs.readthedocs.io/en/latest/cubecell/index.html
|
||||
|
||||
https://github.com/HelTecAutomation/ASR650x-Arduino?utm_source=platformio.org&utm_medium=docs
|
||||
|
||||
* Either portfreertos or make not theaded versions of Lock, WorkerThread, Queue (probably the latter).
|
||||
BIN
docs/hardware/esp32_datasheet_en.pdf
Normal file
BIN
docs/hardware/esp32_datasheet_en.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/lora-fcc-requirements.pdf
Normal file
BIN
docs/hardware/lora-fcc-requirements.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/nrf52/nrf52-programming.png
Normal file
BIN
docs/hardware/nrf52/nrf52-programming.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
BIN
docs/hardware/pe4259ds-rf-switch.pdf
Normal file
BIN
docs/hardware/pe4259ds-rf-switch.pdf
Normal file
Binary file not shown.
@@ -5,7 +5,7 @@ We don't collect any personal identifying information.
|
||||
If you have opted-in to analytics (thank you - that helps us know what things we need to improve), we'll receive anonymized information about user behavior. i.e. which screens you used in the app etc... We never
|
||||
capture usernames, the contents of your texts or your location.
|
||||
|
||||
This is an open-source project run by hobbyists and we try to be completely transparent. If you have questions on this policy, please file [a github issue](https://github.com/geeksville/meshtastic-esp32/issues) and we'll reply/clarify/correct.
|
||||
This is an open-source project run by hobbyists and we try to be completely transparent. If you have questions on this policy, please file [a github issue](https://github.com/meshtastic/meshtastic-esp32/issues) and we'll reply/clarify/correct.
|
||||
|
||||
Keep being awesome!
|
||||
|
||||
|
||||
@@ -10,6 +10,13 @@ See [this site](https://www.rfwireless-world.com/Tutorials/LoRa-channels-list.ht
|
||||
|
||||
The maximum power allowed is +14dBM.
|
||||
|
||||
### 433 MHz
|
||||
|
||||
There are eight channels defined with a 0.2 MHz gap between them.
|
||||
Channel zero starts at 433.175 MHz
|
||||
|
||||
### 870 MHz
|
||||
|
||||
There are eight channels defined with a 0.3 MHz gap between them.
|
||||
Channel zero starts at 865.20 MHz
|
||||
|
||||
|
||||
65
docs/software/TODO.md
Normal file
65
docs/software/TODO.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Geeksville's current work queue
|
||||
|
||||
You probably don't care about this section - skip to the next one.
|
||||
|
||||
- bluetooth toggle enable stress test, we are not properly restarting our connect
|
||||
- make new android release
|
||||
- check in our modified arduino binaries
|
||||
- post bug on esp32-arduino
|
||||
- implement first cut of router mode: preferentially handle flooding, and change sleep and GPS behaviors
|
||||
- let users set arbitrary params in android
|
||||
- NRF52 BLE support
|
||||
|
||||
# Medium priority
|
||||
|
||||
Items to complete before 1.0.
|
||||
|
||||
# Post 1.0 ideas
|
||||
|
||||
- finish DSR for unicast
|
||||
- check fcc rules on duty cycle. we might not need to freq hop. https://www.sunfiretesting.com/LoRa-FCC-Certification-Guide/ . Might need to add enforcement for europe though.
|
||||
- make a no bluetooth configured yet screen - include this screen in the loop if the user hasn't yet paired
|
||||
- if radio params change fundamentally, discard the nodedb
|
||||
- re-enable the bluetooth battery level service on the T-BEAM
|
||||
- provide generalized (but slow) internet message forwarding service if one of our nodes has internet connectivity (MQTT) [ Not a requirement but a personal interest ]
|
||||
|
||||
# Low priority ideas
|
||||
|
||||
Items after the first final candidate release.
|
||||
|
||||
- dynamic frequency scaling could save a lot of power on ESP32, but it seems to corrupt uart (even with ref_tick set correctly)
|
||||
- Change back to using a fixed sized MemoryPool rather than MemoryDynamic (see bug #149)
|
||||
- scan to find channels with low background noise? (Use CAD mode of the RF95 to automatically find low noise channels)
|
||||
- If the phone doesn't read fromradio mailbox within X seconds, assume the phone is gone and we can stop queing location msgs
|
||||
for it (because it will redownload the nodedb when it comes back)
|
||||
- add frequency hopping, dependent on the gps time, make the switch moment far from the time anyone is going to be transmitting
|
||||
- assign every "channel" a random shared 8 bit sync word (per 4.2.13.6 of datasheet) - use that word to filter packets before even checking CRC. This will ensure our CPU will only wake for packets on our "channel"
|
||||
- the BLE stack is leaking about 200 bytes each time we go to light sleep
|
||||
- use fuse bits to store the board type and region. So one load can be used on all boards
|
||||
- Don't store position packets in the to phone fifo if we are disconnected. The phone will get that info for 'free' when it
|
||||
fetches the fresh nodedb.
|
||||
- Use the RFM95 sequencer to stay in idle mode most of the time, then automatically go to receive mode and automatically go from transmit to receive mode. See 4.2.8.2 of manual.
|
||||
- Use fixed32 for node IDs, packetIDs, successid, failid, and lat/lon - will require all nodes to be updated, but make messages slightly smaller.
|
||||
- add "store and forward" support for messages, or move to the DB sync model. This would allow messages to be eventually delivered even if nodes are out of contact at the moment.
|
||||
- use variable length Strings in protobufs (instead of current fixed buffers). This would save lots of RAM
|
||||
- use BLEDevice::setPower to lower our BLE transmit power - extra range doesn't help us, it costs amps and it increases snoopability
|
||||
- make a HAM build: just a new frequency list, a bool to say 'never do encryption' and use hte callsign as that node's unique id. -from Girts
|
||||
- don't forward redundant pings or ping responses to the phone, it just wastes phone battery
|
||||
- don't send location packets if we haven't moved significantly
|
||||
- scrub default radio config settings for bandwidth/range/speed
|
||||
- show radio and gps signal strength as an image
|
||||
- only BLE advertise for a short time after the screen is on and button pressed - to save power and prevent people for sniffing for our BT app.
|
||||
- make mesh aware network timing state machine (sync wake windows to gps time) - this can save LOTS of battery
|
||||
- split out the software update utility so other projects can use it. Have the appload specify the URL for downloads.
|
||||
- read the PMU battery fault indicators and blink/led/warn user on screen
|
||||
- discard very old nodedb records (> 1wk)
|
||||
- add a watchdog timer
|
||||
- handle millis() rollover in GPS.getTime - otherwise we will break after 50 days
|
||||
- report esp32 device code bugs back to the mothership via android
|
||||
- change BLE bonding to something more secure. see comment by pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND)
|
||||
|
||||
# Spinoff project ideas
|
||||
|
||||
- an open source version of https://www.burnair.ch/skynet/
|
||||
- a paragliding app like http://airwhere.co.uk/
|
||||
- How do avalanche beacons work? Could this do that as well? possibly by using beacon mode feature of the RF95?
|
||||
107
docs/software/bluetooth-api.md
Normal file
107
docs/software/bluetooth-api.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Bluetooth API
|
||||
|
||||
The Bluetooth API is design to have only a few characteristics and most polymorphism comes from the flexible set of Google Protocol Buffers which are sent over the wire. We use protocol buffers extensively both for the bluetooth API and for packets inside the mesh or when providing packets to other applications on the phone.
|
||||
|
||||
## A note on MTU sizes
|
||||
|
||||
This device will work with any MTU size, but it is highly recommended that you call your phone's "setMTU function to increase MTU to 512 bytes" as soon as you connect to a service. This will dramatically improve performance when reading/writing packets.
|
||||
|
||||
## MeshBluetoothService
|
||||
|
||||
This is the main bluetooth service for the device and provides the API your app should use to get information about the mesh, send packets or provision the radio.
|
||||
|
||||
For a reference implementation of a client that uses this service see [RadioInterfaceService](https://github.com/meshtastic/Meshtastic-Android/blob/master/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt).
|
||||
|
||||
Typical flow when a phone connects to the device should be the following (if you want to watch this flow from the python app just run "meshtastic --debug --info" - the flow over BLE is identical):
|
||||
|
||||
- There are only three relevant endpoints (and they have built in BLE documentation - so use a BLE tool of your choice to watch them): FromRadio, FromNum (sends notifies when new data is available in FromRadio) and ToRadio
|
||||
- SetMTU size to 512
|
||||
- Write a ToRadio.startConfig protobuf to the "ToRadio" endpoint" - this tells the radio you are a new connection and you need the entire NodeDB sent down.
|
||||
- Read repeatedly from the "FromRadio" endpoint. Each time you read you will get back a FromRadio protobuf (see Meshtatastic-protobuf). Keep reading from this endpoint until you get back and empty buffer.
|
||||
- See below for the expected sequence for your initial download.
|
||||
- After the initial download, you should subscribe for BLE "notify" on the "FromNum" endpoint. If a notification arrives, that means there are now one or more FromRadio packets waiting inside FromRadio. Read from FromRadio until you get back an empty packet.
|
||||
- Any time you want to send packets to the radio, you should write a ToRadio packet into ToRadio.
|
||||
|
||||
Expected sequence for initial download:
|
||||
|
||||
- After your send startConfig, you will receive a series of FromRadio packets. The sequence of these packets will be as follows (but you are best not counting on this, instead just update your model for whatever packet you receive - based on looking at the type)
|
||||
- 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
|
||||
|
||||
For definitions (and documentation) on FromRadio, ToRadio, MyNodeInfo, NodeInfo and User protocol buffers see [mesh.proto](https://github.com/meshtastic/Meshtastic-protobufs/blob/master/mesh.proto)
|
||||
|
||||
UUID for the service: 6ba1b218-15a8-461f-9fa8-5dcae273eafd
|
||||
|
||||
Each characteristic is listed as follows:
|
||||
|
||||
UUID
|
||||
Properties
|
||||
Description (including human readable name)
|
||||
|
||||
8ba2bcc2-ee02-4a55-a531-c525c5e454d5
|
||||
read
|
||||
fromradio - contains a newly received FromRadio packet destined towards the phone (up to MAXPACKET bytes per packet).
|
||||
After reading the esp32 will put the next packet in this mailbox. If the FIFO is empty it will put an empty packet in this
|
||||
mailbox.
|
||||
|
||||
f75c76d2-129e-4dad-a1dd-7866124401e7
|
||||
write
|
||||
toradio - write ToRadio protobufs to this characteristic to send them (up to MAXPACKET len)
|
||||
|
||||
ed9da18c-a800-4f66-a670-aa7547e34453
|
||||
read,notify,write
|
||||
fromnum - the current packet # in the message waiting inside fromradio, if the phone sees this notify it should read messages
|
||||
until it catches up with this number.
|
||||
|
||||
The phone can write to this register to go backwards up to FIXME packets, to handle the rare case of a fromradio packet was dropped after the esp32 callback was called, but before it arrives at the phone. If the phone writes to this register the esp32 will discard older packets and put the next packet >= fromnum in fromradio.
|
||||
When the esp32 advances fromnum, it will delay doing the notify by 100ms, in the hopes that the notify will never actally need to be sent if the phone is already pulling from fromradio.
|
||||
|
||||
Note: that if the phone ever sees this number decrease, it means the esp32 has rebooted.
|
||||
|
||||
Re: queue management
|
||||
Not all messages are kept in the fromradio queue (filtered based on SubPacket):
|
||||
|
||||
- only the most recent Position and User messages for a particular node are kept
|
||||
- all Data SubPackets are kept
|
||||
- No WantNodeNum / DenyNodeNum messages are kept
|
||||
A variable keepAllPackets, if set to true will suppress this behavior and instead keep everything for forwarding to the phone (for debugging)
|
||||
|
||||
## Protobuf API
|
||||
|
||||
On connect, you should send a want_config_id protobuf to the device. This will cause the device to send its node DB and radio config via the fromradio endpoint. After sending the full DB, the radio will send a want_config_id to indicate it is done sending the configuration.
|
||||
|
||||
## Other bluetooth services
|
||||
|
||||
This document focuses on the core mesh service, but it is worth noting that the following other Bluetooth services are also
|
||||
provided by the device.
|
||||
|
||||
### BluetoothSoftwareUpdate
|
||||
|
||||
The software update service. For a sample function that performs a software update using this API see [startUpdate](https://github.com/meshtastic/Meshtastic-Android/blob/master/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt).
|
||||
|
||||
SoftwareUpdateService UUID cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30
|
||||
|
||||
Characteristics
|
||||
|
||||
| UUID | properties | description |
|
||||
| ------------------------------------ | ----------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||
| e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e | write,read | total image size, 32 bit, write this first, then read read back to see if it was acceptable (0 mean not accepted) |
|
||||
| e272ebac-d463-4b98-bc84-5cc1a39ee517 | write | data, variable sized, recommended 512 bytes, write one for each block of file |
|
||||
| 4826129c-c22a-43a3-b066-ce8f0d5bacc6 | write | crc32, write last - writing this will complete the OTA operation, now you can read result |
|
||||
| 5e134862-7411-4424-ac4a-210937432c77 | read,notify | result code, readable but will notify when the OTA operation completes |
|
||||
| GATT_UUID_SW_VERSION_STR/0x2a28 | read | We also implement these standard GATT entries because SW update probably needs them: |
|
||||
| GATT_UUID_MANU_NAME/0x2a29 | read | |
|
||||
| GATT_UUID_HW_VERSION_STR/0x2a27 | read | |
|
||||
|
||||
### DeviceInformationService
|
||||
|
||||
Implements the standard BLE contract for this service (has software version, hardware model, serial number, etc...)
|
||||
|
||||
### BatteryLevelService
|
||||
|
||||
Implements the standard BLE contract service, provides battery level in a way that most client devices should automatically understand (i.e. it should show in the bluetooth devices screen automatically)
|
||||
22
docs/software/build-instructions.md
Normal file
22
docs/software/build-instructions.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Build instructions
|
||||
|
||||
This project uses the simple PlatformIO build system. You can use the IDE, but for brevity
|
||||
in these instructions I describe use of their command line tool.
|
||||
|
||||
1. Purchase a suitable radio (see above)
|
||||
2. Install [PlatformIO](https://platformio.org/platformio-ide)
|
||||
3. Download this git repo and cd into it
|
||||
4. 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`).
|
||||
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
|
||||
|
||||
If you get a crash, you can decode the addresses from the `Backtrace:` line:
|
||||
|
||||
1. Save the `Backtrace: 0x....` line to a file, e.g., `backtrace.txt`.
|
||||
2. Run `bin/exception_decoder.py backtrace.txt` (this uses symbols from the
|
||||
last `firmware.elf`, so you must be running the same binary that's still in
|
||||
your `.pio/build` directory).
|
||||
51
docs/software/crypto.md
Normal file
51
docs/software/crypto.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Encryption in Meshtastic
|
||||
|
||||
Cryptography is tricky, so we've tried to 'simply' apply standard crypto solutions to our implementation. However,
|
||||
the project developers are not cryptography experts. Therefore we ask two things:
|
||||
|
||||
- If you are a cryptography expert, please review these notes and our questions below. Can you help us by reviewing our
|
||||
notes below and offering advice? We will happily give as much or as little credit as you wish ;-).
|
||||
- Consider our existing solution 'alpha' and probably fairly secure against a not particularly aggressive adversary
|
||||
(but we can't yet make a more confident statement).
|
||||
|
||||
## Summary of strengths/weaknesses of our current implementation
|
||||
|
||||
Based on comments from reviewers (see below), here's some tips for usage of these radios. So you can know the level of protection offered:
|
||||
|
||||
* It is pretty likely that the AES256 security is implemented 'correctly' and an observer will not be able to decode your messages.
|
||||
* Warning: If an attacker is able to get one of the radios in their position, they could either a) extract the channel key from that device or b) use that radio to listen to new communications.
|
||||
* Warning: If an attacker is able to get the "Channel QR code/URL" that you share with others - that attacker could then be able to read any messages sent on the channel (either tomorrow or in the past - if they kept a raw copy of those broadcast packets)
|
||||
|
||||
Possible future areas of work (if there is enough interest - post in our [forum](https://meshtastic.discourse.group) if you want this):
|
||||
|
||||
1. Optionally requiring users to provide a PIN to regain access to the mesh. This could be based on: intentionally locking the device, time since last use, or any member could force all members to reauthenticate,
|
||||
2. Until a device reauthenticates, any other access via BLE or USB would be blocked (this would protect against attackers who are not prepared to write custom software to extract and reverse engineer meshtastic flash memory)
|
||||
3. Turning on read-back protection in the device fuse-bits (this would extend protection in #2 to block all but **extremely** advanced attacks involving chip disassembly)
|
||||
4. Time limiting keys used for message transmission and automatically cycling them on a schedule. This would protect past messages from being decoded even if an attacker learns the current key.
|
||||
|
||||
### Notes for reviewers
|
||||
|
||||
If you are reviewing our implementation, this is a brief statement of our method.
|
||||
|
||||
- We do all crypto at the SubPacket (payload) level only, so that all meshtastic nodes will route for others - even those channels which are encrypted with a different key.
|
||||
- Mostly based on reading [Wikipedia](<https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR)>) and using the modes the ESP32 provides support for in hardware.
|
||||
- We use AES256-CTR as a stream cypher (with zero padding on the last BLOCK) because it is well supported with hardware acceleration.
|
||||
- Our AES key is 128 or 256 bits, shared as part of the 'Channel' specification.
|
||||
- The node number concatenated with the packet number is used as the NONCE. This nonce will be stored in flash in the device and should essentially never repeat. If the user makes a new 'Channel' (i.e. picking a new random 256 bit key), the packet number will start at zero.
|
||||
- The packet number is sent in cleartext with each packet. The node number can be derived from the "from" field of each packet. (Cleartext is acceptable because it merely provides IV for each encryption run)
|
||||
- Each 16 byte BLOCK for a packet has an incrementing COUNTER. COUNTER starts at zero for the first block of each packet.
|
||||
- The IV for each block is constructed by concatenating the NONCE as the upper 96 bits of the IV and the COUNTER as the bottom 32 bits. Since our packets are small counter portion will really never be higher than 32 (five bits).
|
||||
|
||||
## Comments from reviewer #1
|
||||
|
||||
This reviewer is a cryptography professional, but would like to remain anonymous. We thank them for their comments ;-):
|
||||
|
||||
I'm assuming that meshtastic is being used to hike in places where someone capable is trying to break it - like you were going to walk around DefCon using these. I spent about an hour reviewing the encryption, and have the following notes:
|
||||
|
||||
* The write-up isn't quite as clear as the code.
|
||||
* The code is using AES-CTR mode correctly to ensure confidentiality.
|
||||
* The comment for initNonce really covers the necessary information.
|
||||
* I think the bigger encryption question is "what does the encryption need to do"? As it stands, an attacker who has yet to capture any of the devices cannot reasonably capture text or location data. An attacker who captures any device in the channel/mesh can read everything going to that device, everything stored on that device, and any other communication within the channel that they captured in encrypted form. If that capability basically matches your expectations, it is suitable for whatever adventures this was intended for, then, based on information publicly available or widely disclosed, the encryption is good. If those properties are distressing (like, device history is deliberately limited and you don't want a device captured today to endanger the information sent over the channel yesterday) we could talk about ways to achieve that (most likely synchronizing time and replacing the key with its own SHA256 every X hours, and ensuring the old key is not retained unnecessarily).
|
||||
* Two other things to keep in mind are that AES-CTR does not itself provide authenticity (e.g. an attacker can flip bits in replaying data and scramble the resulting plaintext), and that the current scheme gives some hints about transmission in the size. So, if you worry about an adversary deliberately messing-up messages or knowing the length of a text message, it looks like those might be possible.
|
||||
|
||||
I'm guessing that the network behaves somewhat like a store-and-forward network - or, at least, that the goal is to avoid establishing a two-way connection to transmit data. I'm afraid I haven't worked with mesh networks much, but remember studying them briefly in school about ten years ago.
|
||||
16
docs/software/esp32-arduino-build-notes.md
Normal file
16
docs/software/esp32-arduino-build-notes.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# esp32-arduino build instructions
|
||||
|
||||
We build our own custom version of esp32-arduino, in order to get some fixes we've made but haven't yet been merged in master.
|
||||
|
||||
These are a set of currently unformatted notes on how to build and install them. Most developers should not care about this, because
|
||||
you'll automatically get our fixed libraries.
|
||||
|
||||
```
|
||||
last EDF release in arduino is: https://github.com/espressif/arduino-esp32/commit/1977370e6fc069e93ffd8818798fbfda27ae7d99
|
||||
IDF release/v3.3 46b12a560
|
||||
IDF release/v3.3 367c3c09c
|
||||
https://docs.espressif.com/projects/esp-idf/en/release-v3.3/get-started/linux-setup.html
|
||||
kevinh@kevin-server:~/development/meshtastic/esp32-arduino-lib-builder\$ python /home/kevinh/development/meshtastic/esp32-arduino-lib-builder/esp-idf/components/esptool*py/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dout --flash_freq 40m --flash_size detect 0x1000 /home/kevinh/development/meshtastic/esp32-arduino-lib-builder/build/bootloader/bootloader.bin
|
||||
cp -a out/tools/sdk/* components/arduino/tools/sdk
|
||||
cp -ar components/arduino/ ~/.platformio/packages/framework-arduinoespressif32@src-fba9d33740f719f712e9f8b07da6ea13/
|
||||
```
|
||||
27
docs/software/install-OSX.md
Normal file
27
docs/software/install-OSX.md
Normal file
@@ -0,0 +1,27 @@
|
||||
(Here's some quick tips on installing the device code from OS-X, thanks to @android606)
|
||||
|
||||
First time using LoRa for anything, just checking it out.
|
||||
|
||||
I bought a T-Beam on eBay, followed the instructions to install the firmware here:
|
||||
[https://github.com/meshtastic/Meshtastic-esp32](https://github.com/meshtastic/Meshtastic-esp32)
|
||||
|
||||
I'm using a Mac for this, so that might account for differences in the steps to get it working. I just swapped out my SSD last month, I'm using a pretty fresh install of OS X 10.15.3/Catalina.
|
||||
|
||||
I got it working fairly smoothly, but there were two hang-ups I thought I'd mention:
|
||||
|
||||
1. I am about 0% familiar with Python, so there were some issues getting esptool.py working. Basically, this OS X comes with Python 2.7 and no pip. Pip installed okay, so I used it to install esptool. Esptool appeared to install correctly, but I couldn't get it to work to save my life. Simply typing "esptool.py" doesn't work, and I just don't know enough python to figure out why. For some reason, it installs but isn't in the \$PATH anywhere, and I don't know where it went. Python 2.7 kept giving me warning messages about being old and unsupported, so I figured that might be a hint that I should upgrade.
|
||||
|
||||
I ended up doing this:
|
||||
|
||||
- brew install pyenv (to install pyenv)
|
||||
- pyenv install 3.7.7 (to install and select python 3.7.7)
|
||||
- pyenv global 3.7.7 (to select the new version of python)
|
||||
- brew install pip (to install pip3)
|
||||
- pip3 install --upgrade esptool (note I specifically had to use "pip3", not "pip")
|
||||
|
||||
...then I was able to execute esptool.py
|
||||
|
||||
2. esptool.py didn't work though, because the virtual com port wasn't showing up as a device. I had to install a driver from Silicon Labs, which I got here:
|
||||
[driver for the CP210X USB to UART bridge from Silicon Labs](https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers)
|
||||
|
||||
After I installed that, esptool.py was completely happy and the firmware loaded right up.
|
||||
167
docs/software/mesh-alg.md
Normal file
167
docs/software/mesh-alg.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Mesh broadcast algorithm
|
||||
|
||||
great source of papers and class notes: http://www.cs.jhu.edu/~cs647/
|
||||
|
||||
flood routing improvements
|
||||
|
||||
- DONE if we don't see anyone rebroadcast our want_ack=true broadcasts, retry as needed.
|
||||
|
||||
reliable messaging tasks (stage one for DSR):
|
||||
|
||||
- DONE generalize naive flooding
|
||||
- DONE add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as three bits in the header.
|
||||
- DONE add a 'snoopReceived' hook for all messages that pass through our node.
|
||||
- DONE use the same 'recentmessages' array used for broadcast msgs to detect duplicate retransmitted messages.
|
||||
- DONE in the router receive path?, send an ack packet if want_ack was set and we are the final destination. FIXME, for now don't handle multihop or merging of data replies with these acks.
|
||||
- DONE keep a list of packets waiting for acks
|
||||
- DONE for each message keep a count of # retries (max of three). Local to the node, only for the most immediate hop, ignorant of multihop routing.
|
||||
- DONE delay some random time for each retry (large enough to allow for acks to come in)
|
||||
- DONE once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender
|
||||
- DONE after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service)
|
||||
- DONE test one hop ack/nak with the python framework
|
||||
- Do stress test with acks
|
||||
|
||||
dsr tasks
|
||||
|
||||
- oops I might have broken message reception
|
||||
- DONE Don't use broadcasts for the network pings (close open github issue)
|
||||
- DONE add ignoreSenders to radioconfig to allow testing different mesh topologies by refusing to see certain senders
|
||||
- test multihop delivery with the python framework
|
||||
|
||||
optimizations / low priority:
|
||||
|
||||
- read @cyclomies long email with good ideas on optimizations and reply
|
||||
- Remove NodeNum assignment algorithm (now that we use 4 byte node nums)
|
||||
- make android app warn if firmware is too old or too new to talk to
|
||||
- change nodenums and packetids in protobuf to be fixed32
|
||||
- low priority: think more careful about reliable retransmit intervals
|
||||
- make ReliableRouter.pending threadsafe
|
||||
- bump up PacketPool size for all the new ack/nak/routing packets
|
||||
- handle 51 day rollover in doRetransmissions
|
||||
- use a priority queue for the messages waiting to send. Send acks first, then routing messages, then data messages, then broadcasts?
|
||||
|
||||
when we send a packet
|
||||
|
||||
- do "hop by hop" routing
|
||||
- when sending, if destnodeinfo.next_hop is zero (and no message is already waiting for an arp for that node), startRouteDiscovery() for that node. Queue the message in the 'waiting for arp queue' so we can send it later when then the arp completes.
|
||||
- otherwise, use next_hop and start sending a message (with ack request) towards that node (starting with next_hop).
|
||||
|
||||
when we receive any packet
|
||||
|
||||
- sniff and update tables (especially useful to find adjacent nodes). Update user, network and position info.
|
||||
- if we need to route() that packet, resend it to the next_hop based on our nodedb.
|
||||
- if it is broadcast or destined for our node, deliver locally
|
||||
- handle routereply/routeerror/routediscovery messages as described below
|
||||
- then free it
|
||||
|
||||
routeDiscovery
|
||||
|
||||
- if we've already passed through us (or is from us), then it ignore it
|
||||
- use the nodes already mentioned in the request to update our routing table
|
||||
- if they were looking for us, send back a routereply
|
||||
- NOT DOING FOR NOW -if max_hops is zero and they weren't looking for us, drop (FIXME, send back error - I think not though?)
|
||||
- if we receive a discovery packet, and we don't have next_hop set in our nodedb, we use it to populate next_hop (if needed) towards the requester (after decrementing max_hops)
|
||||
- if we receive a discovery packet, and we have a next_hop in our nodedb for that destination we send a (reliable) we send a route reply towards the requester
|
||||
|
||||
when sending any reliable packet
|
||||
|
||||
- if timeout doing retries, send a routeError (nak) message back towards the original requester. all nodes eavesdrop on that packet and update their route caches.
|
||||
|
||||
when we receive a routereply packet
|
||||
|
||||
- update next_hop on the node, if the new reply needs fewer hops than the existing one (we prefer shorter paths). fixme, someday use a better heuristic
|
||||
|
||||
when we receive a routeError packet
|
||||
|
||||
- delete the route for that failed recipient, restartRouteDiscovery()
|
||||
- if we receive routeerror in response to a discovery,
|
||||
- fixme, eventually keep caches of possible other routes.
|
||||
|
||||
TODO:
|
||||
|
||||
- optimize our generalized flooding with heuristics, possibly have particular nodes self mark as 'router' nodes.
|
||||
|
||||
- DONE reread the radiohead mesh implementation - hop to hop acknowledgement seems VERY expensive but otherwise it seems like DSR
|
||||
- DONE read about mesh routing solutions (DSR and AODV)
|
||||
- DONE read about general mesh flooding solutions (naive, MPR, geo assisted)
|
||||
- DONE reread the disaster radio protocol docs - seems based on Babel (which is AODVish)
|
||||
- REJECTED - seems dying - possibly dash7? https://www.slideshare.net/MaartenWeyn1/dash7-alliance-protocol-technical-presentation https://github.com/MOSAIC-LoPoW/dash7-ap-open-source-stack - does the opensource stack implement multihop routing? flooding? their discussion mailing list looks dead-dead
|
||||
- update duty cycle spreadsheet for our typical usecase
|
||||
|
||||
a description of DSR: https://tools.ietf.org/html/rfc4728 good slides here: https://www.slideshare.net/ashrafmath/dynamic-source-routing
|
||||
good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept
|
||||
|
||||
interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf
|
||||
It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728
|
||||
https://en.wikipedia.org/wiki/Dynamic_Source_Routing
|
||||
|
||||
broadcast solution:
|
||||
Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent)
|
||||
Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set
|
||||
|
||||
compare to db sync algorithm?
|
||||
|
||||
what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies.
|
||||
|
||||
If Y were to die, at least the neighbor nodes of Y would have their last known position of Y.
|
||||
|
||||
## approach 1
|
||||
|
||||
- send all broadcasts with a TTL
|
||||
- periodically(?) do a survey to find the max TTL that is needed to fully cover the current network.
|
||||
- to do a study first send a broadcast (maybe our current initial user announcement?) with TTL set to one (so therefore no one will rebroadcast our request)
|
||||
- survey replies are sent unicast back to us (and intervening nodes will need to keep the route table that they have built up based on past packets)
|
||||
- count the number of replies to this TTL 1 attempt. That is the number of nodes we can reach without any rebroadcasts
|
||||
- repeat the study with a TTL of 2 and then 3. stop once the # of replies stops going up.
|
||||
- it is important for any node to do listen before talk to prevent stomping on other rebroadcasters...
|
||||
- For these little networks I bet a max TTL would never be higher than 3?
|
||||
|
||||
## approach 2
|
||||
|
||||
- send a TTL1 broadcast, the replies let us build a list of the nodes (stored as a bitvector?) that we can see (and their rssis)
|
||||
- we then broadcast out that bitvector (also TTL1) asking "can any of ya'll (even indirectly) see anyone else?"
|
||||
- if a node can see someone I missed (and they are the best person to see that node), they reply (unidirectionally) with the missing nodes and their rssis (other nodes might sniff (and update their db) based on this reply but they don't have to)
|
||||
- given that the max number of nodes in this mesh will be like 20 (for normal cases), I bet globally updating this db of "nodenums and who has the best rssi for packets from that node" would be useful
|
||||
- once the global DB is shared, when a node wants to broadcast, it just sends out its broadcast . the first level receivers then make a decision "am I the best to rebroadcast to someone who likely missed this packet?" if so, rebroadcast
|
||||
|
||||
## approach 3
|
||||
|
||||
- when a node X wants to know other nodes positions, it broadcasts its position with want_replies=true. Then each of the nodes that received that request broadcast their replies (possibly by using special timeslots?)
|
||||
- all nodes constantly update their local db based on replies they witnessed.
|
||||
- after 10s (or whatever) if node Y notices that it didn't hear a reply from node Z (that Y has heard from recently ) to that initial request, that means Z never heard the request from X. Node Y will reply to X on Z's behalf.
|
||||
- could this work for more than one hop? Is more than one hop needed? Could it work for sending messages (i.e. for a msg sent to Z with want-reply set).
|
||||
|
||||
## approach 4
|
||||
|
||||
look into the literature for this idea specifically.
|
||||
|
||||
- don't view it as a mesh protocol as much as a "distributed db unification problem". When nodes talk to nearby nodes they work together
|
||||
to update their nodedbs. Each nodedb would have a last change date and any new changes that only one node has would get passed to the
|
||||
other node. This would nicely allow distant nodes to propogate their position to all other nodes (eventually).
|
||||
- handle group messages the same way, there would be a table of messages and time of creation.
|
||||
- when a node has a new position or message to send out, it does a broadcast. All the adjacent nodes update their db instantly (this handles 90% of messages I'll bet).
|
||||
- Occasionally a node might broadcast saying "anyone have anything newer than time X?" If someone does, they send the diffs since that date.
|
||||
- essentially everything in this variant becomes broadcasts of "request db updates for >time X - for _all_ or for a particular nodenum" and nodes sending (either due to request or because they changed state) "here's a set of db updates". Every node is constantly trying to
|
||||
build the most recent version of reality, and if some nodes are too far, then nodes closer in will eventually forward their changes to the distributed db.
|
||||
- construct non ambigious rules for who broadcasts to request db updates. ideally the algorithm should nicely realize node X can see most other nodes, so they should just listen to all those nodes and minimize the # of broadcasts. the distributed picture of nodes rssi could be useful here?
|
||||
- possibly view the BLE protocol to the radio the same way - just a process of reconverging the node/msgdb database.
|
||||
|
||||
# Old notes
|
||||
|
||||
FIXME, merge into the above:
|
||||
|
||||
good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept
|
||||
|
||||
interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf
|
||||
It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728
|
||||
https://en.wikipedia.org/wiki/Dynamic_Source_Routing
|
||||
|
||||
broadcast solution:
|
||||
Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent)
|
||||
Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set
|
||||
|
||||
compare to db sync algorithm?
|
||||
|
||||
what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies.
|
||||
|
||||
If Y were to die, at least the neighbor nodes of Y would have their last known position of Y.
|
||||
195
docs/software/nrf52-TODO.md
Normal file
195
docs/software/nrf52-TODO.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# NRF52 TODO
|
||||
|
||||
## Misc work items
|
||||
|
||||
RAM investigation.
|
||||
nRF52832-QFAA 64KB ram, 512KB flash vs
|
||||
nrf52832-QFAB 32KB ram, 512kb flash
|
||||
nrf52840 256KB RAM, 1MB flash
|
||||
|
||||
platform.json
|
||||
|
||||
"framework-arduinoadafruitnrf52": {
|
||||
"type": "framework",
|
||||
"optional": true,
|
||||
"version": "https://github.com/meshtastic/Adafruit_nRF52_Arduino.git"
|
||||
},
|
||||
|
||||
## Initial work items
|
||||
|
||||
Minimum items needed to make sure hardware is good.
|
||||
|
||||
- DONE set power UICR per https://devzone.nordicsemi.com/f/nordic-q-a/28562/nrf52840-regulator-configuration
|
||||
- switch charge controller into / out of performance mode (see 8.3.1 in datasheet)
|
||||
- write UC1701 wrapper
|
||||
- Test hardfault handler for null ptrs (if one isn't already installed)
|
||||
- test my hackedup bootloader on the real hardware
|
||||
- Use the PMU driver on real hardware
|
||||
- Use new radio driver on real hardware
|
||||
- Use UC1701 LCD driver on real hardware. Still need to create at startup and probe on SPI. Make sure SPI is atomic.
|
||||
- set vbus voltage per https://infocenter.nordicsemi.com/topic/ps_nrf52840/power.html?cp=4_0_0_4_2
|
||||
- test the LEDs
|
||||
- test the buttons
|
||||
|
||||
## Secondary work items
|
||||
|
||||
Needed to be fully functional at least at the same level of the ESP32 boards. At this point users would probably want them.
|
||||
|
||||
- DONE get serial API working
|
||||
- get full BLE api working
|
||||
- make power management/sleep work properly
|
||||
- make a settimeofday implementation
|
||||
- DONE increase preamble length? - will break other clients? so all devices must update
|
||||
- DONE enable BLE DFU somehow
|
||||
- report appversion/hwversion in BLE
|
||||
- use new LCD driver from screen.cpp. Still need to hook it to a subclass of (poorly named) OLEDDisplay, and override display() to stream bytes out to the screen.
|
||||
- we need to enable the external tcxo for the sx1262 (on dio3)?
|
||||
- figure out which regulator mode the sx1262 is operating in
|
||||
- turn on security for BLE, make pairing work
|
||||
- make ble endpoints not require "start config", just have them start in config mode
|
||||
- use new PMU to provide battery voltage/% full to app (both bluetooth and screen)
|
||||
- do initial power measurements, measure effects of more preamble bits, measure power management and confirm battery life
|
||||
- set UICR.CUSTOMER to indicate board model & version
|
||||
|
||||
## Items to be 'feature complete'
|
||||
|
||||
- check datasheet about sx1262 temperature compensation
|
||||
- enable brownout detection and watchdog
|
||||
- stop polling for GPS characters, instead stay blocked on read in a thread
|
||||
- figure out what the correct current limit should be for the sx1262, currently we just use the default 100
|
||||
- put sx1262 in sleepmode when processor gets shutdown (or rebooted), ideally even for critical faults (to keep power draw low). repurpose deepsleep state for this.
|
||||
- good power management tips: https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/optimizing-power-on-nrf52-designs
|
||||
- call PMU set_ADC_CONV(0) during sleep, to stop reading PMU adcs and decrease current draw
|
||||
- do final power measurements
|
||||
- backport the common PMU API between AXP192 and PmuBQ25703A
|
||||
- use the new buttons in the UX
|
||||
- currently using soft device SD140, is that ideal?
|
||||
- turn on the watchdog timer, require servicing from key application threads
|
||||
- nrf52setup should call randomSeed(tbd)
|
||||
- implement SYSTEMOFF behavior per https://infocenter.nordicsemi.com/topic/ps_nrf52840/power.html?cp=4_0_0_4_2
|
||||
|
||||
## Things to do 'someday'
|
||||
|
||||
Nice ideas worth considering someday...
|
||||
|
||||
- enable monitor mode debugging (need to use real jlink): https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/monitor-mode-debugging-with-j-link-and-gdbeclipse
|
||||
- Improve efficiency of PeriodicTimer by only checking the next queued timer event, and carefully sorting based on schedule
|
||||
- make a Mfg Controller and device under test classes as examples of custom app code for third party devs. Make a post about this. Use a custom payload type code. Have device under test send a broadcast with max hopcount of 0 for the 'mfgcontroller' payload type. mfg controller will read SNR and reply. DOT will declare failure/success and switch to the regular app screen.
|
||||
- Hook Segger RTT to the nordic logging framework. https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/debugging-with-real-time-terminal
|
||||
- Use nordic logging for DEBUG_MSG
|
||||
- use the Jumper simulator to run meshes of simulated hardware: https://docs.jumper.io/docs/install.html
|
||||
- make/find a multithread safe debug logging class (include remote logging and timestamps and levels). make each log event atomic.
|
||||
- turn on freertos stack size checking
|
||||
- Currently we use Nordic's vendor ID, which is apparently okay: https://devzone.nordicsemi.com/f/nordic-q-a/44014/using-nordic-vid-and-pid-for-nrf52840 and I just picked a PID of 0x4403
|
||||
- Use NRF logger module (includes flash logging etc...) instead of DEBUG_MSG
|
||||
- Use "LED softblink" library on NRF52 to do nice pretty "breathing" LEDs. Don't whack LED from main thread anymore.
|
||||
- decrease BLE xmit power "At 0dBm with the DC/DC on, the nRF52832 transmitter draws 5.3mA. Increasing the TX power to +4dBm adds only 2.2mA. Decreasing it to -40 dBm saves only 2.6mA."
|
||||
- in addition to the main CPU watchdog, use the PMU watchdog as a really big emergency hammer
|
||||
- turn on 'shipping mode' in the PMU when device is 'off' - to cut battery draw to essentially zero
|
||||
- make Lorro_BQ25703A read/write operations atomic, current version could let other threads sneak in (once we start using threads)
|
||||
- make the segger logbuffer larger, move it to RAM that is preserved across reboots and support reading it out at runtime (to allow full log messages to be included in crash reports). Share this code with ESP32 (use gcc noinit attribute)
|
||||
- convert hardfaults/panics/asserts/wd exceptions into fault codes sent to phone
|
||||
- stop enumerating all i2c devices at boot, it wastes power & time
|
||||
- consider using "SYSTEMOFF" deep sleep mode, without RAM retension. Only useful for 'truly off - wake only by button press' only saves 1.5uA vs SYSTEMON. (SYSTEMON only costs 1.5uA). Possibly put PMU into shipping mode?
|
||||
- change the BLE protocol to be more symmetric. Have the phone _also_ host a GATT service which receives writes to
|
||||
'fromradio'. This would allow removing the 'fromnum' mailbox/notify scheme of the current approach and decrease the number of packet handoffs when a packet is received.
|
||||
- Using the preceeding, make a generalized 'nrf52/esp32 ble to internet' bridge service. To let nrf52 apps do MQTT/UDP/HTTP POST/HTTP GET operations to web services.
|
||||
- lower advertise interval to save power, lower ble transmit power to save power
|
||||
- the SX126x class does SPI transfers on a byte by byte basis, which is very ineffecient. Much better to do block writes/reads.
|
||||
|
||||
## Old unorganized notes
|
||||
|
||||
## Notes on PCA10059 Dongle
|
||||
|
||||
- docs: https://infocenter.nordicsemi.com/pdf/nRF52840_Dongle_User_Guide_v1.0.pdf
|
||||
|
||||
- Currently using Nordic PCA10059 Dongle hardware
|
||||
- https://community.platformio.org/t/same-bootloader-same-softdevice-different-board-different-pins/11411/9
|
||||
|
||||
- To make Segger JLink more reliable, turn off its fake filesystem. "JLinkExe MSDDisable" per https://learn.adafruit.com/circuitpython-on-the-nrf52/nrf52840-bootloader
|
||||
|
||||
## Done
|
||||
|
||||
- DONE add "DFU trigger library" to application load
|
||||
- DONE: using this: Possibly use this bootloader? https://github.com/adafruit/Adafruit_nRF52_Bootloader
|
||||
- DONE select and install a bootloader (adafruit)
|
||||
- DONE get old radio driver working on NRF52
|
||||
- DONE basic test of BLE
|
||||
- DONE get a debug 'serial' console working via the ICE passthrough feature
|
||||
- DONE switch to RadioLab? test it with current radio. https://github.com/jgromes/RadioLib
|
||||
- DONE change rx95 to radiolib
|
||||
- DONE track rxbad, rxgood, txgood
|
||||
- DONE neg 7 error code from receive
|
||||
- DONE remove unused sx1262 lib from github
|
||||
- at boot we are starting our message IDs at 1, rather we should start them at a random number. also, seed random based on timer. this could be the cause of our first message not seen bug.
|
||||
- add a NEMA based GPS driver to test GPS
|
||||
- DONE use "variants" to get all gpio bindings
|
||||
- DONE plug in correct variants for the real board
|
||||
- turn on DFU assistance in the appload using the nordic DFU helper lib call
|
||||
- make a new boarddef with a variant.h file. Fix pins in that file. In particular (at least):
|
||||
#define PIN_SPI_MISO (46)
|
||||
#define PIN_SPI_MOSI (45)
|
||||
#define PIN_SPI_SCK (47)
|
||||
#define PIN_WIRE_SDA (26)
|
||||
#define PIN_WIRE_SCL (27)
|
||||
- customize the bootloader to use proper button bindings
|
||||
- remove the MeshRadio wrapper - we don't need it anymore, just do everything in RadioInterface subclasses.
|
||||
- DONE use SX126x::startReceiveDutyCycleAuto to save power by sleeping and briefly waking to check for preamble bits. Change xmit rules to have more preamble bits.
|
||||
- scheduleOSCallback doesn't work yet - it is way too fast (causes rapid polling of busyTx, high power draw etc...)
|
||||
- find out why we reboot while debugging - it was bluetooth/softdevice
|
||||
- make a file system implementation (preferably one that can see the files the bootloader also sees) - preferably https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/libraries/InternalFileSytem/examples/Internal_ReadWrite/Internal_ReadWrite.ino else use https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.3.0/lib_fds_usage.html?cp=7_5_0_3_55_3
|
||||
- change packet numbers to be 32 bits
|
||||
|
||||
```
|
||||
|
||||
/*
|
||||
per
|
||||
https://docs.platformio.org/en/latest/tutorials/nordicnrf52/arduino_debugging_unit_testing.html
|
||||
|
||||
ardunino github is here https://github.com/sandeepmistry/arduino-nRF5
|
||||
devboard hw docs here:
|
||||
https://infocenter.nordicsemi.com/topic/ug_nrf52840_dk/UG/nrf52840_DK/hw_buttons_leds.html?cp=4_0_4_7_6
|
||||
|
||||
https://docs.platformio.org/en/latest/boards/nordicnrf52/nrf52840_dk_adafruit.html
|
||||
|
||||
must install adafruit bootloader first!
|
||||
https://learn.adafruit.com/circuitpython-on-the-nrf52/nrf52840-bootloader
|
||||
see link above and turn off jlink filesystem if we see unreliable serial comms
|
||||
over USBCDC
|
||||
|
||||
adafruit bootloader install commands (from their readme)
|
||||
kevinh@kevin-server:~/.platformio/packages/framework-arduinoadafruitnrf52/bootloader$
|
||||
nrfjprog -e -f nrf52 Erasing user available code and UICR flash areas. Applying
|
||||
system reset.
|
||||
kevinh@kevin-server:~/.platformio/packages/framework-arduinoadafruitnrf52/bootloader$
|
||||
nrfjprog --program pca10056/pca10056_bootloader-0.3.2_s140_6.1.1.hex -f nrf52
|
||||
Parsing hex file.
|
||||
Reading flash area to program to guarantee it is erased.
|
||||
Checking that the area to write is not protected.
|
||||
Programming device.
|
||||
kevinh@kevin-server:~/.platformio/packages/framework-arduinoadafruitnrf52/bootloader$
|
||||
nrfjprog --reset -f nrf52 Applying system reset. Run.
|
||||
|
||||
install jlink tools from here:
|
||||
https://www.segger.com/downloads/jlink#J-LinkSoftwareAndDocumentationPack
|
||||
|
||||
install nrf tools from here:
|
||||
https://www.nordicsemi.com/Software-and-tools/Development-Tools/nRF-Command-Line-Tools/Download#infotabs
|
||||
|
||||
examples of turning off the loop call to save power:
|
||||
https://learn.adafruit.com/bluefruit-nrf52-feather-learning-guide/advertising-beacon
|
||||
|
||||
example of a more complex BLE service:
|
||||
https://learn.adafruit.com/bluefruit-nrf52-feather-learning-guide/custom-hrm
|
||||
*/
|
||||
|
||||
// See g_ADigitalPinMap to see how arduino maps to the real gpio#s - and all in
|
||||
// P0
|
||||
#define LED1 14
|
||||
#define LED2 13
|
||||
|
||||
/*
|
||||
good led ble demo:
|
||||
https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/libraries/Bluefruit52Lib/examples/Peripheral/nrf_blinky/nrf_blinky.ino
|
||||
*/
|
||||
```
|
||||
27
docs/software/pinetab.md
Normal file
27
docs/software/pinetab.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Pinetab
|
||||
|
||||
These are **preliminary** notes on support for Meshtastic in the Pinetab.
|
||||
|
||||
A RF95 is connected via a CS341 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
|
||||
|
||||
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/
|
||||
|
||||
## 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
|
||||
Use ARM linux: https://platformio.org/platforms/linux_arm
|
||||
And linux native: https://platformio.org/platforms/native
|
||||
|
||||
* Test cs341 driver - just test reading/writing a register and detecting interrupts, confirm can see rf95
|
||||
* Make a radiolib spi module that targets the cs341 (and builds on linux)
|
||||
* use new radiolib module to hook pinebook lora to meshtastic, confirm mesh discovery works
|
||||
* Make a subclass of StreamAPI that works as a posix TCP server
|
||||
* Use new TCP endpoint from meshtastic-python
|
||||
92
docs/software/power.md
Normal file
92
docs/software/power.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Power Management State Machine
|
||||
|
||||
i.e. sleep behavior
|
||||
|
||||
## Power measurements
|
||||
|
||||
Since one of the main goals of this project is long battery life, it is important to consider that in our software/protocol design. Based on initial measurements it seems that the current code should run about three days between charging, and with a bit more software work (see the [TODO list](TODO.md)) a battery life of eight days should be quite doable. Our current power measurements/model is in [this spreadsheet](https://docs.google.com/spreadsheets/d/1ft1bS3iXqFKU8SApU8ZLTq9r7QQEGESYnVgdtvdT67k/edit?usp=sharing).
|
||||
|
||||
## States
|
||||
|
||||
From lower to higher power consumption.
|
||||
|
||||
- Super-deep-sleep (SDS) - everything is off, CPU, radio, bluetooth, GPS. Only wakes due to timer or button press. We enter this mode only after no radio comms for a few hours, used to put the device into what is effectively "off" mode.
|
||||
onEntry: setBluetoothOn(false), call doDeepSleep
|
||||
onExit: (standard bootup code, starts in DARK)
|
||||
|
||||
- deep-sleep (DS) - CPU is off, radio is on, bluetooth and GPS is off. Note: This mode is never used currently, because it only saves 1.5mA vs light-sleep
|
||||
(Not currently used)
|
||||
|
||||
- light-sleep (LS) - CPU is suspended (RAM stays alive), radio is on, bluetooth is off, GPS is off. Note: currently GPS is not turned
|
||||
off during light sleep, but there is a TODO item to fix this.
|
||||
NOTE: On NRF52 platforms (because CPU current draw is so low), light-sleep state is never used.
|
||||
onEntry: setBluetoothOn(false), setGPSPower(false), doLightSleep()
|
||||
onIdle: (if we wake because our led blink timer has expired) blink the led then go back to sleep until we sleep for ls_secs
|
||||
onExit: setGPSPower(true), start trying to get gps lock: gps.startLock(), once lock arrives service.sendPosition(BROADCAST)
|
||||
|
||||
- No bluetooth (NB) - CPU is running, radio is on, GPS is on but bluetooth is off, screen is off.
|
||||
onEntry: setBluetoothOn(false)
|
||||
onExit:
|
||||
|
||||
- running dark (DARK) - Everything is on except screen
|
||||
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:
|
||||
|
||||
## Behavior
|
||||
|
||||
### events that increase CPU activity
|
||||
|
||||
- At cold boot: The initial state (after setup() has run) is DARK
|
||||
- While in DARK: if we receive EVENT_BOOT, transition to ON (and show the bootscreen). This event will be sent if we detect we woke due to reset (as opposed to deep sleep)
|
||||
- While in LS: Once every position_broadcast_secs (default 15 mins) - the unit will wake into DARK mode and broadcast a "networkPing" (our position) and stay alive for wait_bluetooth_secs (default 30 seconds). This allows other nodes to have a record of our last known position if we go away and allows a paired phone to hear from us and download messages.
|
||||
- While in LS: Every send*owner_interval (defaults to 4, i.e. one hour), when we wake to send our position we \_also* broadcast our owner. This lets new nodes on the network find out about us or correct duplicate node number assignments.
|
||||
- While in LS/NB/DARK: If the user presses a button (EVENT_PRESS) we go to full ON mode for screen_on_secs (default 30 seconds). Multiple presses keeps resetting this timeout
|
||||
- While in LS/NB/DARK: If we receive new text messages (EVENT_RECEIVED_TEXT_MSG), we go to full ON mode for screen_on_secs (same as if user pressed a button)
|
||||
- While in LS: while we receive packets on the radio (EVENT_RECEIVED_PACKET) we will wake and handle them and stay awake in NB mode for min_wake_secs (default 10 seconds)
|
||||
- While in NB: If we do have packets the phone (EVENT_PACKETS_FOR_PHONE) would want we transition to DARK mode for wait_bluetooth secs.
|
||||
- While in DARK/ON: If we receive EVENT_BLUETOOTH_PAIR we transition to ON and start our screen_on_secs timeout
|
||||
- While in NB/DARK/ON: If we receive EVENT_NODEDB_UPDATED we transition to ON (so the new screen can be shown)
|
||||
- While in DARK: While the phone talks to us over BLE (EVENT_CONTACT_FROM_PHONE) reset any sleep timers and stay in DARK (needed for bluetooth sw update and nice user experience if the user is reading/replying to texts)
|
||||
- while in LS/NB/DARK: if SERIAL_CONNECTED, go to serial
|
||||
|
||||
### events that decrease cpu activity
|
||||
|
||||
- While in SERIAL: if SERIAL_DISCONNECTED, go to NB
|
||||
- While in ON: If PRESS event occurs, reset screen_on_secs timer and tell the screen to handle the pess
|
||||
- While in ON: If it has been more than screen_on_secs since a press, lower to DARK
|
||||
- While in DARK: If time since last contact by our phone exceeds phone_timeout_secs (15 minutes), we transition down into NB mode
|
||||
- While in DARK or NB: If nothing above is forcing us to stay in a higher mode (wait_bluetooth_secs, min_wake_secs) we will lower down to LS state
|
||||
- While in LS: If either phone_sds_timeout_secs (default 2 hr) or mesh_sds_timeout_secs (default 2 hr) are exceeded we will lower into SDS mode for sds_secs (default 1 yr) (or a button press). (Note: phone_sds_timeout_secs is currently disabled for now, because most users
|
||||
are using without a phone)
|
||||
- Any time we enter LS mode: We stay in that until an interrupt, button press or other state transition. Every ls_secs (default 1 hr) and let the arduino loop() run one iteration (FIXME, not sure if we need this at all), and then immediately reenter lightsleep mode on the CPU.
|
||||
|
||||
TODO: Eventually these scheduled intervals should be synchronized to the GPS clock, so that we can consider leaving the lora receiver off to save even more power.
|
||||
TODO: In NB mode we should put cpu into light sleep any time we really aren't that busy (without declaring LS state) - i.e. we should leave GPS on etc...
|
||||
|
||||
# Low power consumption tasks
|
||||
|
||||
General ideas to hit the power draws our spreadsheet predicts. Do the easy ones before beta, the last 15% can be done after 1.0.
|
||||
|
||||
- don't even power on the gps until someone else wants our position, just stay in lora deep sleep until press or rxpacket (except for once an hour updates)
|
||||
- (possibly bad idea - better to have lora radio always listen - check spreadsheet) have every node wake at the same tick and do their position syncs then go back to deep sleep
|
||||
- lower BT announce interval to save battery
|
||||
- change to use RXcontinuous mode and config to drop packets with bad CRC (see section 6.4 of datasheet) - I think this is already the case
|
||||
- have mesh service run in a thread that stays blocked until a packet arrives from the RF95
|
||||
- platformio sdkconfig CONFIG_PM and turn on modem sleep mode
|
||||
- keep cpu 100% in deepsleep until irq from radio wakes it. Then stay awake for 30 secs to attempt delivery to phone.
|
||||
- use https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/ association sleep pattern to save power - but see https://github.com/espressif/esp-idf/issues/2070 and https://esp32.com/viewtopic.php?f=13&t=12182 it seems with BLE on the 'easy' draw people are getting is 80mA
|
||||
- stop using loop() instead use a job queue and let cpu sleep
|
||||
- measure power consumption and calculate battery life assuming no deep sleep
|
||||
- do lowest sleep level possible where BT still works during normal sleeping, make sure cpu stays in that mode unless lora rx packet happens, bt rx packet happens or button press happens
|
||||
- optionally do lora messaging only during special scheduled intervals (unless nodes are told to go to low latency mode), then deep sleep except during those intervals - before implementing calculate what battery life would be with this feature
|
||||
- see section 7.3 of https://cdn.sparkfun.com/assets/learn_tutorials/8/0/4/RFM95_96_97_98W.pdf and have hope radio wake only when a valid packet is received. Possibly even wake the ESP32 from deep sleep via GPIO.
|
||||
- never enter deep sleep while connected to USB power (but still go to other low power modes)
|
||||
- when main cpu is idle (in loop), turn cpu clock rate down and/or activate special sleep modes. We want almost everything shutdown until it gets an interrupt.
|
||||
202
docs/software/ramusage-nrf52.txt
Normal file
202
docs/software/ramusage-nrf52.txt
Normal file
@@ -0,0 +1,202 @@
|
||||
23K + messages
|
||||
+ heap of 70ish packets, 300ish bytes per packet: 20KB
|
||||
+ 14KB soft device RAM
|
||||
|
||||
With max length Data inside the packet
|
||||
Size of NodeInfo 104
|
||||
Size of SubPacket 272
|
||||
Size of MeshPacket 304
|
||||
|
||||
If Data was smaller: for 70 data packets we would save 7KB. We would need to make SubPacket.data and MeshPacket.encrypted into "type:FT_POINTER" - variably sized mallocs
|
||||
Size of NodeInfo 104
|
||||
Size of SubPacket 96
|
||||
Size of MeshPacket 292 (could have been much smaller but I forgot to shrink MeshPacket.encrypted)
|
||||
|
||||
therefore:
|
||||
a) we should store all ToPhone message queued messages compressed as protobufs (since they will become that anyways)
|
||||
b) shrink packet pool size because none of that storage will be used for ToPhone packets
|
||||
c) don't allocate any storage in RAM for the tophone messages we save inside device state, instead just use nanopb callbacks to save/load those
|
||||
d) a smarter MeshPacket in memory representation would save about 7KB of RAM. call pb_release before freeing each freshly malloced MeshPacket
|
||||
|
||||
- nrf52 free memory https://learn.adafruit.com/bluefruit-nrf52-feather-learning-guide/hathach-memory-map
|
||||
|
||||
2000790c 00003558 B devicestate // 16KB
|
||||
2000b53c 00001000 b _cache_buffer // 4KB flash filesystem support
|
||||
20003b1c 000006b0 B console
|
||||
2000d5f4 00000400 b vApplicationGetTimerTaskMemory::uxTimerTaskStack
|
||||
2000da04 00000400 b _acUpBuffer
|
||||
2000c558 0000036c B Bluefruit
|
||||
2000c8d8 00000358 b _cdcd_itf
|
||||
2000e54c 00000258 B _midid_itf
|
||||
2000d0dc 00000200 b ucStaticTimerQueueStorage.9390
|
||||
2000e044 00000200 b _mscd_buf
|
||||
2000e284 000001cc b _vendord_itf
|
||||
2000d410 00000190 b vApplicationGetIdleTaskMemory::uxIdleTaskStack
|
||||
2000374c 0000016c D __global_locale
|
||||
2000de48 0000012c B USBDevice
|
||||
2000afa4 00000100 b Router::send(_MeshPacket*)::bytes
|
||||
2000aea4 00000100 b Router::perhapsDecode(_MeshPacket*)::bytes
|
||||
200039b0 000000f4 B powerFSM
|
||||
20004258 000000f0 B screen
|
||||
2000cd7c 000000c4 b _dcd
|
||||
2000cc68 000000c0 b _usbd_qdef_buf
|
||||
2000b3c4 000000bc B Wire
|
||||
2000cef4 000000a8 B Serial2
|
||||
2000ce4c 000000a8 B Serial1
|
||||
2000e498 000000a8 B _SEGGER_RTT
|
||||
2000b498 000000a4 B InternalFS
|
||||
2000dfb8 0000008c b _hidd_itf
|
||||
2000b260 00000088 b meshtastic::normalFrames
|
||||
2000cfdc 00000064 b pxReadyTasksLists
|
||||
2000b340 00000060 b meshtastic::drawTextMessageFrame(OLEDDisplay*, OLEDDisplayUiState*, short, short)::tempBuf
|
||||
200036ec 00000060 d impure_data
|
||||
2000b104 00000060 B bledfu
|
||||
2000b0a4 00000060 B blebas
|
||||
20003684 00000058 D _usbd_qdef
|
||||
200038c0 00000058 d tzinfo
|
||||
2000d5a0 00000054 b vApplicationGetTimerTaskMemory::xTimerTaskTCB
|
||||
2000d3bc 00000054 b vApplicationGetIdleTaskMemory::xIdleTaskTCB
|
||||
2000d308 00000050 b xStaticTimerQueue.9389
|
||||
2000b1f4 00000050 B hrmc
|
||||
2000b1a4 00000050 B bslc
|
||||
20004360 0000004c B service
|
||||
2000d374 00000048 b m_cb
|
||||
2000df74 00000042 b _desc_str
|
||||
2000cd3c 00000040 b _usbd_ctrl_buf
|
||||
20004214 00000040 B realRouter
|
||||
2000e244 00000040 b _mscd_itf
|
||||
2000b164 00000040 B bledis
|
||||
20003634 00000038 d _InternalFSConfig
|
||||
2000cc30 00000031 b _usbd_dev
|
||||
2000398c 00000020 B periodicScheduler
|
||||
2000cfa4 00000020 b callbacksInt
|
||||
2000de10 0000001f b fw_str.13525
|
||||
20003974 00000018 b object.9934
|
||||
2000ae68 00000018 B nodeDB
|
||||
2000366c 00000018 d _cache
|
||||
2000b314 00000014 b meshtastic::drawNodeInfo(OLEDDisplay*, OLEDDisplayUiState*, short, short)::signalStr
|
||||
2000b300 00000014 b meshtastic::drawNodeInfo(OLEDDisplay*, OLEDDisplayUiState*, short, short)::lastStr
|
||||
2000b2ec 00000014 b meshtastic::drawNodeInfo(OLEDDisplay*, OLEDDisplayUiState*, short, short)::distStr
|
||||
200041e0 00000014 b getDeviceName()::name
|
||||
2000d0b8 00000014 b xTasksWaitingTermination
|
||||
2000d0a4 00000014 b xSuspendedTaskList
|
||||
2000d08c 00000014 b xPendingReadyList
|
||||
2000d06c 00000014 b xDelayedTaskList2
|
||||
2000d058 00000014 b xDelayedTaskList1
|
||||
2000d2f0 00000014 b xActiveTimerList2
|
||||
2000d2dc 00000014 b xActiveTimerList1
|
||||
2000b480 00000014 B SPI
|
||||
2000c8c4 00000014 B Serial
|
||||
2000cd28 00000014 b _ctrl_xfer
|
||||
2000de30 00000011 b serial_str.13534
|
||||
2000c544 00000010 b BLEAdvertising::_start(unsigned short, unsigned short)::gap_adv
|
||||
20003614 00000010 d meshtastic::btPIN
|
||||
2000434c 00000010 b sendOwnerPeriod
|
||||
2000ae8c 00000010 b staticPool
|
||||
2000e484 00000010 B xQueueRegistry
|
||||
20003b04 00000010 B stateSERIAL
|
||||
20003af4 00000010 B stateSDS
|
||||
20003ae4 00000010 B stateON
|
||||
20003ad4 00000010 B stateNB
|
||||
20003ac4 00000010 B stateLS
|
||||
20003ab4 00000010 B stateDARK
|
||||
20003aa4 00000010 B stateBOOT
|
||||
200041f8 00000010 B ledPeriodic
|
||||
2000b244 00000010 B hrms
|
||||
2000d9f4 00000010 b _acDownBuffer
|
||||
2000b3b8 0000000c B preflightSleep
|
||||
20004208 0000000c B powerStatus
|
||||
2000e540 0000000c B nrf_nvic_state
|
||||
2000b3ac 0000000c B notifySleep
|
||||
2000b3a0 0000000c B notifyDeepSleep
|
||||
2000e463 0000000b b __tzname_std
|
||||
2000e458 0000000b b __tzname_dst
|
||||
2000b338 00000008 b meshtastic::estimatedHeading(double, double)::oldLon
|
||||
2000b330 00000008 b meshtastic::estimatedHeading(double, double)::oldLat
|
||||
200041d0 00000008 b zeroOffsetSecs
|
||||
2000ae80 00000008 b spiSettings
|
||||
200038b8 00000008 D _tzname
|
||||
20003b14 00000008 B noopPrint
|
||||
2000cfc4 00000008 b channelMap
|
||||
2000cf9c 00000008 b callbackDeferred
|
||||
200043ac 00000006 b ourMacAddr
|
||||
2000435c 00000004 b MeshService::onGPSChanged(void*)::lastGpsSend
|
||||
2000b32c 00000004 b meshtastic::estimatedHeading(double, double)::b
|
||||
2000b328 00000004 b meshtastic::drawNodeInfo(OLEDDisplay*, OLEDDisplayUiState*, short, short)::simRadian
|
||||
2000362c 00000004 d meshtastic::Screen::setup()::bootFrames
|
||||
20003628 00000004 d meshtastic::Screen::handleStartBluetoothPinScreen(unsigned long)::btFrames
|
||||
200039ac 00000004 b onEnter()::lastPingMs
|
||||
2000ae9c 00000004 b generatePacketId()::i
|
||||
2000ae88 00000004 B RadioLibInterface::instance
|
||||
2000b2e8 00000004 b meshtastic::nodeIndex
|
||||
20003610 00000004 d meshtastic::targetFramerate
|
||||
2000c554 00000004 B BLEService::lastService
|
||||
200041cc 00000004 b timeStartMsec
|
||||
200036dc 00000004 d sbrk_heap_top
|
||||
2000d364 00000004 b _loopHandle
|
||||
2000c540 00000004 b guard variable for BLEAdvertising::_start(unsigned short, unsigned short)::gap_adv
|
||||
2000d0d0 00000004 b xYieldPending
|
||||
2000d35c 00000004 b xTimerTaskHandle
|
||||
2000d358 00000004 b xTimerQueue
|
||||
2000d0cc 00000004 b xTickCount
|
||||
2000d0a0 00000004 b xSchedulerRunning
|
||||
2000d088 00000004 b xNumOfOverflows
|
||||
2000d084 00000004 b xNextTaskUnblockTime
|
||||
2000d304 00000004 b xLastTime.9343
|
||||
2000d080 00000004 b xIdleTaskHandle
|
||||
2000d054 00000004 b uxTopReadyPriority
|
||||
2000d050 00000004 b uxTaskNumber
|
||||
2000d04c 00000004 b uxSchedulerSuspended
|
||||
2000d048 00000004 b uxPendedTicks
|
||||
2000d044 00000004 b uxDeletedTasksWaitingCleanUp
|
||||
2000d040 00000004 b uxCurrentNumberOfTasks
|
||||
2000d360 00000004 b uxCriticalNesting
|
||||
2000cc64 00000004 b _usbd_q
|
||||
2000e478 00000004 B _timezone
|
||||
200036e0 00000004 D SystemCoreClock
|
||||
2000c53c 00000004 b _sem
|
||||
2000d0d8 00000004 b pxOverflowTimerList
|
||||
2000cfd8 00000004 b pxOverflowDelayedTaskList
|
||||
2000cfd4 00000004 b pxDelayedTaskList
|
||||
2000d0d4 00000004 b pxCurrentTimerList
|
||||
2000cfd0 00000004 B pxCurrentTCB
|
||||
2000e470 00000004 b prev_tzenv
|
||||
2000360c 00000004 D preftmp
|
||||
20003608 00000004 D preffile
|
||||
2000b25c 00000004 B nrf52Bluetooth
|
||||
2000d370 00000004 b m_usbevt_handler
|
||||
2000d36c 00000004 b m_sleepevt_handler
|
||||
2000d368 00000004 b m_pofwarn_handler
|
||||
2000e454 00000004 B __malloc_sbrk_start
|
||||
2000e450 00000004 B __malloc_free_list
|
||||
2000e480 00000004 B MAIN_MonCnt
|
||||
2000e47c 00000004 b initial_env
|
||||
200036e8 00000004 D _impure_ptr
|
||||
200041d8 00000004 B gps
|
||||
2000e7a4 00000004 B errno
|
||||
20003918 00000004 D environ
|
||||
2000cfcc 00000004 b enabled
|
||||
2000ae64 00000004 B displayedNodeNum
|
||||
2000e474 00000004 B _daylight
|
||||
2000b254 00000004 B crypto
|
||||
2000ce44 00000004 B count_duration
|
||||
2000de0c 00000004 b _cb_task
|
||||
2000de08 00000004 b _cb_queue
|
||||
2000de04 00000004 b _cb_qdepth
|
||||
2000de44 00000004 B bootloaderVersion
|
||||
200041f5 00000001 b ledBlinker()::ledOn
|
||||
20003604 00000001 d loop::showingBootScreen
|
||||
200041f4 00000001 b loop::wasPressed
|
||||
2000b494 00000001 b DefaultFontTableLookup(unsigned char)::LASTCHAR
|
||||
2000aea0 00000001 b generatePacketId()::didInit
|
||||
20003624 00000001 d meshtastic::prevFrame
|
||||
2000b258 00000001 b bleOn
|
||||
200041dc 00000001 B timeSetFromGPS
|
||||
20004348 00000001 B ssd1306_found
|
||||
2000ce49 00000001 B pin_sound
|
||||
2000e494 00000001 B nrfx_power_irq_enabled
|
||||
2000ce48 00000001 B no_stop
|
||||
20003630 00000001 D neo6M
|
||||
2000ce40 00000001 b _initialized
|
||||
200036e4 00000001 D __fdlib_version
|
||||
20003970 00000001 b completed.9929
|
||||
9
docs/software/sw-design.md
Normal file
9
docs/software/sw-design.md
Normal file
@@ -0,0 +1,9 @@
|
||||
This is a mini design doc for developing the meshtastic software.
|
||||
|
||||
* [Build instructions](build-instructions.md)
|
||||
* [TODO](TODO.md) - read this if you are looking for things to do (or curious about currently missing features)
|
||||
* Our [project board](https://github.com/orgs/meshtastic/projects/1) - shows what things we are currently working on and remaining work items for the current release.
|
||||
* [Power Management](power.md)
|
||||
* [Mesh algorithm](mesh-alg.md)
|
||||
* [Bluetooth API](bluetooth-api.md) and porting guide for new clients (iOS, python, etc...)
|
||||
* TODO: how to port the device code to a new device.
|
||||
@@ -1,126 +0,0 @@
|
||||
This is a mini design doc for various core behaviors...
|
||||
|
||||
# Rules for sleep
|
||||
|
||||
## States
|
||||
|
||||
From lower to higher power consumption.
|
||||
|
||||
* Super-deep-sleep (SDS) - everything is off, CPU, radio, bluetooth, GPS. Only wakes due to timer or button press
|
||||
onEntry: setBluetoothOn(false), call doDeepSleep
|
||||
onExit: (standard bootup code, starts in DARK)
|
||||
|
||||
* deep-sleep (DS) - CPU is off, radio is on, bluetooth and GPS is off. Note: This mode is never used currently, because it only saves 1.5mA vs light-sleep
|
||||
(Not currently used)
|
||||
|
||||
* light-sleep (LS) - CPU is suspended (RAM stays alive), radio is on, bluetooth is off, GPS is off. Note: currently GPS is not turned
|
||||
off during light sleep, but there is a TODO item to fix this.
|
||||
onEntry: setBluetoothOn(false), setGPSPower(false), doLightSleep()
|
||||
onIdle: (if we wake because our led blink timer has expired) blink the led then go back to sleep until we sleep for ls_secs
|
||||
onExit: setGPSPower(true), start trying to get gps lock: gps.startLock(), once lock arrives service.sendPosition(BROADCAST)
|
||||
|
||||
* No bluetooth (NB) - CPU is running, radio is on, GPS is on but bluetooth is off, screen is off.
|
||||
onEntry: setBluetoothOn(false)
|
||||
onExit:
|
||||
|
||||
* running dark (DARK) - Everything is on except screen
|
||||
onEntry: setBluetoothOn(true)
|
||||
onExit:
|
||||
|
||||
* full on (ON) - Everything is on
|
||||
onEntry: setBluetoothOn(true), screen.setOn(true)
|
||||
onExit: screen.setOn(false)
|
||||
|
||||
## Behavior
|
||||
|
||||
### events that increase CPU activity
|
||||
|
||||
* At cold boot: The initial state (after setup() has run) is DARK
|
||||
* While in DARK: if we receive EVENT_BOOT, transition to ON (and show the bootscreen). This event will be sent if we detect we woke due to reset (as opposed to deep sleep)
|
||||
* While in LS: Once every position_broadcast_secs (default 15 mins) - the unit will wake into DARK mode and broadcast a "networkPing" (our position) and stay alive for wait_bluetooth_secs (default 30 seconds). This allows other nodes to have a record of our last known position if we go away and allows a paired phone to hear from us and download messages.
|
||||
* While in LS: Every send_owner_interval (defaults to 4, i.e. one hour), when we wake to send our position we _also_ broadcast our owner. This lets new nodes on the network find out about us or correct duplicate node number assignments.
|
||||
* While in LS/NB/DARK: If the user presses a button (EVENT_PRESS) we go to full ON mode for screen_on_secs (default 30 seconds). Multiple presses keeps resetting this timeout
|
||||
* While in LS/NB/DARK: If we receive new text messages (EVENT_RECEIVED_TEXT_MSG), we go to full ON mode for screen_on_secs (same as if user pressed a button)
|
||||
* While in LS: while we receive packets on the radio (EVENT_RECEIVED_PACKET) we will wake and handle them and stay awake in NB mode for min_wake_secs (default 10 seconds)
|
||||
* While in NB: If we do have packets the phone (EVENT_PACKETS_FOR_PHONE) would want we transition to DARK mode for wait_bluetooth secs.
|
||||
* While in DARK/ON: If we receive EVENT_BLUETOOTH_PAIR we transition to ON and start our screen_on_secs timeout
|
||||
* While in NB/DARK/ON: If we receive EVENT_NODEDB_UPDATED we transition to ON (so the new screen can be shown)
|
||||
* While in DARK: While the phone talks to us over BLE (EVENT_CONTACT_FROM_PHONE) reset any sleep timers and stay in DARK (needed for bluetooth sw update and nice user experience if the user is reading/replying to texts)
|
||||
|
||||
### events that decrease cpu activity
|
||||
|
||||
* While in ON: If PRESS event occurs, reset screen_on_secs timer and tell the screen to handle the pess
|
||||
* While in ON: If it has been more than screen_on_secs since a press, lower to DARK
|
||||
* While in DARK: If time since last contact by our phone exceeds phone_timeout_secs (15 minutes), we transition down into NB mode
|
||||
* While in DARK or NB: If nothing above is forcing us to stay in a higher mode (wait_bluetooth_secs, min_wake_secs) we will lower down to LS state
|
||||
* While in into LS: If either phone_sds_timeout_secs (default 1 hr) or mesh_sds_timeout_secs (default 1 hr) are exceeded we will lower into SDS mode for sds_secs (default 1 hr) (or a button press).
|
||||
* Any time we enter LS mode: We stay in that mode for ls_secs (default 1 hr) (or until an interrupt, button press)
|
||||
|
||||
TODO: Eventually these scheduled intervals should be synchronized to the GPS clock, so that we can consider leaving the lora receiver off to save even more power.
|
||||
TODO: In NB mode we should put cpu into light sleep any time we really aren't that busy (without declaring LS state) - i.e. we should leave GPS on etc...
|
||||
|
||||
# Low power consumption tasks
|
||||
|
||||
General ideas to hit the power draws our spreadsheet predicts. Do the easy ones before beta, the last 15% can be done after 1.0.
|
||||
|
||||
* don't even power on the gps until someone else wants our position, just stay in lora deep sleep until press or rxpacket (except for once an hour updates)
|
||||
* (possibly bad idea - better to have lora radio always listen - check spreadsheet) have every node wake at the same tick and do their position syncs then go back to deep sleep
|
||||
* lower BT announce interval to save battery
|
||||
* change to use RXcontinuous mode and config to drop packets with bad CRC (see section 6.4 of datasheet) - I think this is already the case
|
||||
* have mesh service run in a thread that stays blocked until a packet arrives from the RF95
|
||||
* platformio sdkconfig CONFIG_PM and turn on modem sleep mode
|
||||
* keep cpu 100% in deepsleep until irq from radio wakes it. Then stay awake for 30 secs to attempt delivery to phone.
|
||||
* use https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/ association sleep pattern to save power - but see https://github.com/espressif/esp-idf/issues/2070 and https://esp32.com/viewtopic.php?f=13&t=12182 it seems with BLE on the 'easy' draw people are getting is 80mA
|
||||
* stop using loop() instead use a job queue and let cpu sleep
|
||||
* measure power consumption and calculate battery life assuming no deep sleep
|
||||
* do lowest sleep level possible where BT still works during normal sleeping, make sure cpu stays in that mode unless lora rx packet happens, bt rx packet happens or button press happens
|
||||
* optionally do lora messaging only during special scheduled intervals (unless nodes are told to go to low latency mode), then deep sleep except during those intervals - before implementing calculate what battery life would be with this feature
|
||||
* see section 7.3 of https://cdn.sparkfun.com/assets/learn_tutorials/8/0/4/RFM95_96_97_98W.pdf and have hope radio wake only when a valid packet is received. Possibly even wake the ESP32 from deep sleep via GPIO.
|
||||
* never enter deep sleep while connected to USB power (but still go to other low power modes)
|
||||
* when main cpu is idle (in loop), turn cpu clock rate down and/or activate special sleep modes. We want almost everything shutdown until it gets an interrupt.
|
||||
|
||||
# Mesh broadcast algoritm
|
||||
|
||||
FIXME - instead look for standard solutions. this approach seems really suboptimal, because too many nodes will try to rebroast. If
|
||||
all else fails could always use the stock radiohead solution - though super inefficent.
|
||||
|
||||
## approach 1
|
||||
|
||||
* send all broadcasts with a TTL
|
||||
* periodically(?) do a survey to find the max TTL that is needed to fully cover the current network.
|
||||
* to do a study first send a broadcast (maybe our current initial user announcement?) with TTL set to one (so therefore no one will rebroadcast our request)
|
||||
* survey replies are sent unicast back to us (and intervening nodes will need to keep the route table that they have built up based on past packets)
|
||||
* count the number of replies to this TTL 1 attempt. That is the number of nodes we can reach without any rebroadcasts
|
||||
* repeat the study with a TTL of 2 and then 3. stop once the # of replies stops going up.
|
||||
* it is important for any node to do listen before talk to prevent stomping on other rebroadcasters...
|
||||
* For these little networks I bet a max TTL would never be higher than 3?
|
||||
|
||||
## approach 2
|
||||
|
||||
* send a TTL1 broadcast, the replies let us build a list of the nodes (stored as a bitvector?) that we can see (and their rssis)
|
||||
* we then broadcast out that bitvector (also TTL1) asking "can any of ya'll (even indirectly) see anyone else?"
|
||||
* if a node can see someone I missed (and they are the best person to see that node), they reply (unidirectionally) with the missing nodes and their rssis (other nodes might sniff (and update their db) based on this reply but they don't have to)
|
||||
* given that the max number of nodes in this mesh will be like 20 (for normal cases), I bet globally updating this db of "nodenums and who has the best rssi for packets from that node" would be useful
|
||||
* once the global DB is shared, when a node wants to broadcast, it just sends out its broadcast . the first level receivers then make a decision "am I the best to rebroadcast to someone who likely missed this packet?" if so, rebroadcast
|
||||
|
||||
## approach 3
|
||||
|
||||
* when a node X wants to know other nodes positions, it broadcasts its position with want_replies=true. Then each of the nodes that received that request broadcast their replies (possibly by using special timeslots?)
|
||||
* all nodes constantly update their local db based on replies they witnessed.
|
||||
* after 10s (or whatever) if node Y notices that it didn't hear a reply from node Z (that Y has heard from recently ) to that initial request, that means Z never heard the request from X. Node Y will reply to X on Z's behalf.
|
||||
* could this work for more than one hop? Is more than one hop needed? Could it work for sending messages (i.e. for a msg sent to Z with want-reply set).
|
||||
|
||||
## approach 4
|
||||
|
||||
look into the literature for this idea specifically.
|
||||
|
||||
* don't view it as a mesh protocol as much as a "distributed db unification problem". When nodes talk to nearby nodes they work together
|
||||
to update their nodedbs. Each nodedb would have a last change date and any new changes that only one node has would get passed to the
|
||||
other node. This would nicely allow distant nodes to propogate their position to all other nodes (eventually).
|
||||
* handle group messages the same way, there would be a table of messages and time of creation.
|
||||
* when a node has a new position or message to send out, it does a broadcast. All the adjacent nodes update their db instantly (this handles 90% of messages I'll bet).
|
||||
* Occasionally a node might broadcast saying "anyone have anything newer than time X?" If someone does, they send the diffs since that date.
|
||||
* essentially everything in this variant becomes broadcasts of "request db updates for >time X - for _all_ or for a particular nodenum" and nodes sending (either due to request or because they changed state) "here's a set of db updates". Every node is constantly trying to
|
||||
build the most recent version of reality, and if some nodes are too far, then nodes closer in will eventually forward their changes to the distributed db.
|
||||
* construct non ambigious rules for who broadcasts to request db updates. ideally the algorithm should nicely realize node X can see most other nodes, so they should just listen to all those nodes and minimize the # of broadcasts. the distributed picture of nodes rssi could be useful here?
|
||||
* possibly view the BLE protocol to the radio the same way - just a process of reconverging the node/msgdb database.
|
||||
19
gdbinit
Normal file
19
gdbinit
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
# Setup Monitor Mode Debugging
|
||||
# Per .platformio/packages/framework-arduinoadafruitnrf52-old/cores/nRF5/linker/nrf52840_s140_v6.ld
|
||||
# our appload starts at 0x26000
|
||||
# Disable for now because our version on board doesn't support monitor mode debugging
|
||||
# mon exec SetMonModeDebug=1
|
||||
# mon exec SetMonModeVTableAddr=0x26000
|
||||
|
||||
# echo setting RTTAddr
|
||||
# eval "monitor exec SetRTTAddr %p", &_SEGGER_RTT
|
||||
|
||||
# the jlink debugger seems to want a pause after reset before we tell it to start running
|
||||
define restart
|
||||
echo Restarting
|
||||
monitor reset
|
||||
shell sleep 1
|
||||
cont
|
||||
end
|
||||
|
||||
BIN
images/system-info.bin
Normal file
BIN
images/system-info.bin
Normal file
Binary file not shown.
@@ -2,13 +2,11 @@
|
||||
"name": "BluetoothOTA",
|
||||
"keywords": "esp32, bluetooth",
|
||||
"description": "A BTLE based software OTA update tool",
|
||||
"repository":
|
||||
{
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/geeksville/fixme.git"
|
||||
},
|
||||
"authors":
|
||||
[
|
||||
"authors": [
|
||||
{
|
||||
"name": "Kevin Hester",
|
||||
"email": "kevinh@geeksville.com",
|
||||
@@ -20,7 +18,15 @@
|
||||
"frameworks": "arduino",
|
||||
"platforms": "*",
|
||||
"dependencies": [
|
||||
{ "name": "Update" },
|
||||
{ "name": "ESP32 BLE Arduino" }
|
||||
{
|
||||
"name": "Update"
|
||||
},
|
||||
{
|
||||
"name": "ESP32 BLE Arduino"
|
||||
},
|
||||
{
|
||||
"name": "arduino-fsm",
|
||||
"version": "https://github.com/meshtastic/arduino-fsm.git"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,304 +0,0 @@
|
||||
#include "BluetoothUtil.h"
|
||||
#include "BluetoothSoftwareUpdate.h"
|
||||
#include <esp_gatt_defs.h>
|
||||
#include <BLE2902.h>
|
||||
#include <Arduino.h>
|
||||
#include <Update.h>
|
||||
#include "configuration.h"
|
||||
#include "screen.h"
|
||||
|
||||
SimpleAllocator btPool;
|
||||
|
||||
/**
|
||||
* Create standard device info service
|
||||
**/
|
||||
BLEService *createDeviceInfomationService(BLEServer *server, std::string hwVendor, std::string swVersion, std::string hwVersion = "")
|
||||
{
|
||||
BLEService *deviceInfoService = server->createService(BLEUUID((uint16_t)ESP_GATT_UUID_DEVICE_INFO_SVC));
|
||||
|
||||
BLECharacteristic *swC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ);
|
||||
BLECharacteristic *mfC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_MANU_NAME), BLECharacteristic::PROPERTY_READ);
|
||||
// BLECharacteristic SerialNumberCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_SERIAL_NUMBER_STR), BLECharacteristic::PROPERTY_READ);
|
||||
|
||||
/*
|
||||
* Mandatory characteristic for device info service?
|
||||
|
||||
BLECharacteristic *m_pnpCharacteristic = m_deviceInfoService->createCharacteristic(ESP_GATT_UUID_PNP_ID, BLECharacteristic::PROPERTY_READ);
|
||||
|
||||
uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version;
|
||||
uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> 8), (uint8_t) version };
|
||||
m_pnpCharacteristic->setValue(pnp, sizeof(pnp));
|
||||
*/
|
||||
swC->setValue(swVersion);
|
||||
deviceInfoService->addCharacteristic(addBLECharacteristic(swC));
|
||||
mfC->setValue(hwVendor);
|
||||
deviceInfoService->addCharacteristic(addBLECharacteristic(mfC));
|
||||
if (!hwVersion.empty())
|
||||
{
|
||||
BLECharacteristic *hwvC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ);
|
||||
hwvC->setValue(hwVersion);
|
||||
deviceInfoService->addCharacteristic(addBLECharacteristic(hwvC));
|
||||
}
|
||||
//SerialNumberCharacteristic.setValue("FIXME");
|
||||
//deviceInfoService->addCharacteristic(&SerialNumberCharacteristic);
|
||||
|
||||
// m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, BLECharacteristic::PROPERTY_READ);
|
||||
// m_manufacturerCharacteristic->setValue(name);
|
||||
|
||||
/* add these later?
|
||||
ESP_GATT_UUID_SYSTEM_ID
|
||||
*/
|
||||
|
||||
// caller must call service->start();
|
||||
return deviceInfoService;
|
||||
}
|
||||
|
||||
bool _BLEClientConnected = false;
|
||||
|
||||
class MyServerCallbacks : public BLEServerCallbacks
|
||||
{
|
||||
void onConnect(BLEServer *pServer)
|
||||
{
|
||||
_BLEClientConnected = true;
|
||||
};
|
||||
|
||||
void onDisconnect(BLEServer *pServer)
|
||||
{
|
||||
_BLEClientConnected = false;
|
||||
}
|
||||
};
|
||||
|
||||
#define MAX_DESCRIPTORS 32
|
||||
#define MAX_CHARACTERISTICS 32
|
||||
|
||||
static BLECharacteristic *chars[MAX_CHARACTERISTICS];
|
||||
static size_t numChars;
|
||||
static BLEDescriptor *descs[MAX_DESCRIPTORS];
|
||||
static size_t numDescs;
|
||||
|
||||
/// Add a characteristic that we will delete when we restart
|
||||
BLECharacteristic *addBLECharacteristic(BLECharacteristic *c)
|
||||
{
|
||||
assert(numChars < MAX_CHARACTERISTICS);
|
||||
chars[numChars++] = c;
|
||||
return c;
|
||||
}
|
||||
|
||||
/// Add a characteristic that we will delete when we restart
|
||||
BLEDescriptor *addBLEDescriptor(BLEDescriptor *c)
|
||||
{
|
||||
assert(numDescs < MAX_DESCRIPTORS);
|
||||
descs[numDescs++] = c;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
// Help routine to add a description to any BLECharacteristic and add it to the service
|
||||
// We default to require an encrypted BOND for all these these characterstics
|
||||
void addWithDesc(BLEService *service, BLECharacteristic *c, const char *description)
|
||||
{
|
||||
c->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
|
||||
|
||||
BLEDescriptor *desc = new BLEDescriptor(BLEUUID((uint16_t)ESP_GATT_UUID_CHAR_DESCRIPTION), strlen(description) + 1);
|
||||
assert(desc);
|
||||
desc->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
|
||||
desc->setValue(description);
|
||||
c->addDescriptor(desc);
|
||||
service->addCharacteristic(c);
|
||||
addBLECharacteristic(c);
|
||||
addBLEDescriptor(desc);
|
||||
}
|
||||
|
||||
static BLECharacteristic *batteryLevelC;
|
||||
|
||||
/**
|
||||
* Create a battery level service
|
||||
*/
|
||||
BLEService *createBatteryService(BLEServer *server)
|
||||
{
|
||||
// Create the BLE Service
|
||||
BLEService *pBattery = server->createService(BLEUUID((uint16_t)0x180F));
|
||||
|
||||
batteryLevelC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_BATTERY_LEVEL), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
|
||||
|
||||
addWithDesc(pBattery, batteryLevelC, "Percentage 0 - 100");
|
||||
batteryLevelC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification
|
||||
|
||||
// I don't think we need to advertise this
|
||||
// server->getAdvertising()->addServiceUUID(pBattery->getUUID());
|
||||
pBattery->start();
|
||||
|
||||
return pBattery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the battery level we are currently telling clients.
|
||||
* level should be a pct between 0 and 100
|
||||
*/
|
||||
void updateBatteryLevel(uint8_t level)
|
||||
{
|
||||
// Pretend to update battery levels - fixme do elsewhere
|
||||
if (batteryLevelC)
|
||||
{
|
||||
batteryLevelC->setValue(&level, 1);
|
||||
batteryLevelC->notify();
|
||||
}
|
||||
}
|
||||
|
||||
void dumpCharacteristic(BLECharacteristic *c)
|
||||
{
|
||||
std::string value = c->getValue();
|
||||
|
||||
if (value.length() > 0)
|
||||
{
|
||||
DEBUG_MSG("New value: ");
|
||||
for (int i = 0; i < value.length(); i++)
|
||||
DEBUG_MSG("%c", value[i]);
|
||||
|
||||
DEBUG_MSG("\n");
|
||||
}
|
||||
}
|
||||
|
||||
/** converting endianness pull out a 32 bit value */
|
||||
uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue)
|
||||
{
|
||||
std::string value = c->getValue();
|
||||
uint32_t r = defaultValue;
|
||||
|
||||
if (value.length() == 4)
|
||||
r = value[0] | (value[1] << 8UL) | (value[2] << 16UL) | (value[3] << 24UL);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
class MySecurity : public BLESecurityCallbacks
|
||||
{
|
||||
|
||||
bool onConfirmPIN(uint32_t pin)
|
||||
{
|
||||
Serial.printf("onConfirmPIN %u\n", pin);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t onPassKeyRequest()
|
||||
{
|
||||
Serial.println("onPassKeyRequest");
|
||||
return 123511; // not used
|
||||
}
|
||||
|
||||
void onPassKeyNotify(uint32_t pass_key)
|
||||
{
|
||||
Serial.printf("onPassKeyNotify %u\n", pass_key);
|
||||
screen_start_bluetooth(pass_key);
|
||||
}
|
||||
|
||||
bool onSecurityRequest()
|
||||
{
|
||||
Serial.println("onSecurityRequest");
|
||||
return true;
|
||||
}
|
||||
|
||||
void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl)
|
||||
{
|
||||
if (cmpl.success)
|
||||
{
|
||||
uint16_t length;
|
||||
esp_ble_gap_get_whitelist_size(&length);
|
||||
Serial.printf(" onAuthenticationComplete -> success size: %d\n", length);
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.printf("onAuthenticationComplete -> fail %d\n", cmpl.fail_reason);
|
||||
}
|
||||
|
||||
// Remove our custom screen
|
||||
screen_set_frames();
|
||||
}
|
||||
};
|
||||
|
||||
BLEServer *pServer;
|
||||
|
||||
BLEService *pDevInfo, *pUpdate;
|
||||
|
||||
void deinitBLE()
|
||||
{
|
||||
assert(pServer);
|
||||
|
||||
pServer->getAdvertising()->stop();
|
||||
|
||||
destroyUpdateService();
|
||||
|
||||
pUpdate->stop();
|
||||
pDevInfo->stop();
|
||||
pUpdate->stop(); // we delete them below
|
||||
|
||||
// First shutdown bluetooth
|
||||
BLEDevice::deinit(false);
|
||||
|
||||
// do not delete this - it is dynamically allocated, but only once - statically in BLEDevice
|
||||
// delete pServer->getAdvertising();
|
||||
|
||||
delete pUpdate;
|
||||
delete pDevInfo;
|
||||
delete pServer;
|
||||
|
||||
batteryLevelC = NULL; // Don't let anyone generate bogus notifies
|
||||
|
||||
for (int i = 0; i < numChars; i++)
|
||||
delete chars[i];
|
||||
numChars = 0;
|
||||
|
||||
for (int i = 0; i < numDescs; i++)
|
||||
delete descs[i];
|
||||
numDescs = 0;
|
||||
|
||||
btPool.reset();
|
||||
}
|
||||
|
||||
BLEServer *initBLE(std::string deviceName, std::string hwVendor, std::string swVersion, std::string hwVersion)
|
||||
{
|
||||
BLEDevice::init(deviceName);
|
||||
BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT);
|
||||
|
||||
/*
|
||||
* Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation
|
||||
*/
|
||||
static MySecurity mySecurity;
|
||||
BLEDevice::setSecurityCallbacks(&mySecurity);
|
||||
|
||||
// Create the BLE Server
|
||||
pServer = BLEDevice::createServer();
|
||||
static MyServerCallbacks myCallbacks;
|
||||
pServer->setCallbacks(&myCallbacks);
|
||||
|
||||
pDevInfo = createDeviceInfomationService(pServer, hwVendor, swVersion, hwVersion);
|
||||
|
||||
// We now let users create the battery service only if they really want (not all devices have a battery)
|
||||
// BLEService *pBattery = createBatteryService(pServer);
|
||||
|
||||
pUpdate = createUpdateService(pServer, hwVendor, swVersion, hwVersion); // We need to advertise this so our android ble scan operation can see it
|
||||
|
||||
// It seems only one service can be advertised - so for now don't advertise our updater
|
||||
// pServer->getAdvertising()->addServiceUUID(pUpdate->getUUID());
|
||||
|
||||
// start all our services (do this after creating all of them)
|
||||
pDevInfo->start();
|
||||
pUpdate->start();
|
||||
|
||||
// FIXME turn on this restriction only after the device is paired with a phone
|
||||
// advert->setScanFilter(false, true); // We let anyone scan for us (FIXME, perhaps only allow that until we are paired with a phone and configured) but only let whitelist phones connect
|
||||
|
||||
static BLESecurity security; // static to avoid allocs
|
||||
BLESecurity *pSecurity = &security;
|
||||
pSecurity->setCapability(ESP_IO_CAP_OUT);
|
||||
pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND);
|
||||
pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);
|
||||
|
||||
return pServer;
|
||||
}
|
||||
|
||||
// Called from loop
|
||||
void loopBLE()
|
||||
{
|
||||
bluetoothRebootCheck();
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
#include "PowerFSM.h" // FIXME - someday I want to make this OTA thing a separate lb at at that point it can't touch this
|
||||
#include "BLECharacteristic.h"
|
||||
|
||||
/**
|
||||
* This mixin just lets the power management state machine know the phone is still talking to us
|
||||
*/
|
||||
class BLEKeepAliveCallbacks : public BLECharacteristicCallbacks
|
||||
{
|
||||
public:
|
||||
void onRead(BLECharacteristic *c)
|
||||
{
|
||||
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE);
|
||||
}
|
||||
|
||||
void onWrite(BLECharacteristic *c)
|
||||
{
|
||||
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A characterstic with a set of overridable callbacks
|
||||
*/
|
||||
class CallbackCharacteristic : public BLECharacteristic, public BLEKeepAliveCallbacks
|
||||
{
|
||||
public:
|
||||
CallbackCharacteristic(const char *uuid, uint32_t btprops)
|
||||
: BLECharacteristic(uuid, btprops)
|
||||
{
|
||||
setCallbacks(this);
|
||||
}
|
||||
};
|
||||
@@ -11,7 +11,7 @@
|
||||
*****************************************************************/
|
||||
|
||||
/* Enable support for dynamically allocated fields */
|
||||
/* #define PB_ENABLE_MALLOC 1 */
|
||||
#define PB_ENABLE_MALLOC 1
|
||||
|
||||
/* Define this if your CPU / compiler combination does not support
|
||||
* unaligned memory access to packed structures. */
|
||||
46
linker/nrf52840_s140_sim832.ld
Normal file
46
linker/nrf52840_s140_sim832.ld
Normal file
@@ -0,0 +1,46 @@
|
||||
/* Linker script to configure memory regions.
|
||||
|
||||
geeksville: modified this to simulate a nrf52832 but with a sd140 soft device. So I can
|
||||
see how the memory footprint works on this lower end CPU. Note: to work with sd140 in my bootloader
|
||||
I need to start ram at 0x6000 (instead of the correct 0x3600 for sd132) - so I have less
|
||||
RAM available than on a real 832.
|
||||
*/
|
||||
|
||||
SEARCH_DIR(.)
|
||||
GROUP(-lgcc -lc -lnosys)
|
||||
|
||||
MEMORY
|
||||
{
|
||||
FLASH (rx) : ORIGIN = 0x26000, LENGTH = 0x6D000 - 0x26000
|
||||
/* FLASH (rx) : ORIGIN = 0x26000, LENGTH = 0xED000 - 0x26000 */
|
||||
|
||||
/* SRAM required by S132 depend on
|
||||
* - Attribute Table Size
|
||||
* - Vendor UUID count
|
||||
* - Max ATT MTU
|
||||
* - Concurrent connection peripheral + central + secure links
|
||||
* - Event Len, HVN queue, Write CMD queue
|
||||
*/
|
||||
/* RAM (rwx) : ORIGIN = 0x20003600, LENGTH = 0x20010000 - 0x20003600 */
|
||||
RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 0x20010000 - 0x20006000
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = ALIGN(4);
|
||||
.svc_data :
|
||||
{
|
||||
PROVIDE(__start_svc_data = .);
|
||||
KEEP(*(.svc_data))
|
||||
PROVIDE(__stop_svc_data = .);
|
||||
} > RAM
|
||||
|
||||
.fs_data :
|
||||
{
|
||||
PROVIDE(__start_fs_data = .);
|
||||
KEEP(*(.fs_data))
|
||||
PROVIDE(__stop_fs_data = .);
|
||||
} > RAM
|
||||
} INSERT AFTER .data;
|
||||
|
||||
INCLUDE "nrf52_common.ld"
|
||||
@@ -1 +0,0 @@
|
||||
../MeshUtil/app/src/main/proto/mesh.options
|
||||
@@ -1 +0,0 @@
|
||||
../MeshUtil/app/src/main/proto/mesh.proto
|
||||
159
platformio.ini
159
platformio.ini
@@ -9,37 +9,48 @@
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[platformio]
|
||||
default_envs = tbeam ; Note: the github actions CI test build can't yet build NRF52 targets
|
||||
|
||||
[env:esp32]
|
||||
platform = espressif32
|
||||
board = ttgo-t-beam
|
||||
; board = heltec_wifi_lora_32_V2
|
||||
framework = arduino
|
||||
[common]
|
||||
; common is not currently used
|
||||
|
||||
; REQUIRED environment variables - if not set the specified default will be sued
|
||||
; The following environment variables must be set in the shell if you'd like to override them.
|
||||
; They are used in this ini file as systenv.VARNAME, so in your shell do export "VARNAME=fish"
|
||||
; COUNTRY (default US), i.e. "export COUNTRY=EU865"
|
||||
; APP_VERSION (default emptystring)
|
||||
; HW_VERSION (default emptystring)
|
||||
|
||||
[env]
|
||||
|
||||
; 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 dependencies on arduino-fsm
|
||||
build_flags = -Wall -Wextra -Wno-missing-field-initializers -I.pio/libdeps/esp32/arduino-fsm -Isrc -Os -Wl,-Map,.pio/build/esp32/output.map -DAXP_DEBUG_PORT=Serial
|
||||
; FIXME: fix lib/BluetoothOTA dependency back on src/ so we can remove -Isrc
|
||||
build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -Ilib/nanopb/include -Os -Wl,-Map,.pio/build/output.map
|
||||
-DAXP_DEBUG_PORT=Serial
|
||||
-DHW_VERSION_${sysenv.COUNTRY}
|
||||
-DAPP_VERSION=${sysenv.APP_VERSION}
|
||||
-DHW_VERSION=${sysenv.HW_VERSION}
|
||||
|
||||
; not needed included in ttgo-t-beam board file
|
||||
; also to use PSRAM https://docs.platformio.org/en/latest/platforms/espressif32.html#external-ram-psram
|
||||
; -DBOARD_HAS_PSRAM
|
||||
; -mfix-esp32-psram-cache-issue
|
||||
|
||||
|
||||
; -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
|
||||
|
||||
upload_speed = 921600
|
||||
upload_port = /dev/ttyUSB0
|
||||
; leave this commented out to avoid breaking Windows
|
||||
;upload_port = /dev/ttyUSB0
|
||||
;monitor_port = /dev/ttyUSB0
|
||||
|
||||
; the default is esptool
|
||||
; upload_protocol = esp-prog
|
||||
|
||||
monitor_port = /dev/ttyUSB0
|
||||
monitor_speed = 115200
|
||||
; monitor_speed = 115200
|
||||
monitor_speed = 921600
|
||||
|
||||
# debug_tool = esp-prog
|
||||
# debug_port = /dev/ttyACM0
|
||||
@@ -53,22 +64,122 @@ debug_tool = jlink
|
||||
;debug_init_cmds =
|
||||
; monitor adapter_khz 10000
|
||||
|
||||
debug_init_break = tbreak setup
|
||||
|
||||
lib_deps =
|
||||
https://github.com/geeksville/RadioHead.git
|
||||
TinyGPSPlus
|
||||
https://github.com/geeksville/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
|
||||
AXP202X_Library
|
||||
https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
|
||||
SPI
|
||||
OneButton
|
||||
CRC32 ; explicitly needed because dependency is missing in the ble ota update lib
|
||||
; 1260 ; OneButton - not used yet
|
||||
1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib
|
||||
Wire ; explicitly needed here because the AXP202 library forgets to add it
|
||||
https://github.com/geeksville/arduino-fsm.git
|
||||
https://github.com/meshtastic/arduino-fsm.git
|
||||
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git
|
||||
https://github.com/meshtastic/RadioLib.git
|
||||
https://github.com/meshtastic/TinyGPSPlus.git
|
||||
|
||||
; Common settings for ESP targes, mixin with extends = esp32_base
|
||||
[esp32_base]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
src_filter =
|
||||
${env.src_filter} -<nrf52/>
|
||||
upload_speed = 921600
|
||||
debug_init_break = tbreak setup
|
||||
build_flags =
|
||||
${env.build_flags} -Wall -Wextra -Isrc/esp32
|
||||
lib_ignore = segger_rtt
|
||||
platform_packages =
|
||||
framework-arduinoespressif32 @ https://github.com/meshtastic/arduino-esp32.git
|
||||
|
||||
;[env:tbeam]
|
||||
; 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
|
||||
build_flags =
|
||||
${esp32_base.build_flags} -D TBEAM_V10
|
||||
|
||||
; The original TBEAM board without the AXP power chip and a few other changes
|
||||
; Note: I've heard reports this didn't work. Disabled until someone with a 0.7 can test and debug.
|
||||
;[env:tbeam0.7]
|
||||
;extends = esp32_base
|
||||
;board = ttgo-t-beam
|
||||
;build_flags =
|
||||
; ${esp32_base.build_flags} -D TBEAM_V07
|
||||
|
||||
;[env:heltec]
|
||||
[env:heltec]
|
||||
;build_type = debug ; to make it possible to step through our jtag debugger
|
||||
;board = heltec_wifi_lora_32_V2
|
||||
extends = esp32_base
|
||||
board = heltec_wifi_lora_32_V2
|
||||
|
||||
[env:ttgo-lora32-v1]
|
||||
extends = esp32_base
|
||||
board = ttgo-lora32-v1
|
||||
build_flags =
|
||||
${esp32_base.build_flags} -D TTGO_LORA_V1
|
||||
|
||||
; note: the platformio definition for lora32-v2 seems stale, it is missing a pins_arduino.h file, therefore I don't think it works
|
||||
[env:ttgo-lora32-v2]
|
||||
extends = esp32_base
|
||||
board = ttgo-lora32-v1
|
||||
build_flags =
|
||||
${esp32_base.build_flags} -D TTGO_LORA_V2
|
||||
|
||||
; The Heltec Cubecell plus
|
||||
; IMPORTANT NOTE: This target doesn't yet work and probably won't ever work. I'm keeping it around for now.
|
||||
; For more details see my post in the forum.
|
||||
[env:cubecellplus]
|
||||
platform = https://github.com/HelTecAutomation/platform-asrmicro650x.git ; we use top-of-tree because stable version has too many bugs - asrmicro650x
|
||||
framework = arduino
|
||||
board = cubecell_board_plus
|
||||
; FIXME, bug in cubecell arduino - they are supposed to set ARDUINO
|
||||
build_flags = ${env.build_flags} -DARDUINO=100 -Isrc/cubecell
|
||||
src_filter =
|
||||
${env.src_filter} -<esp32/> -<nrf52/>
|
||||
|
||||
; Common settings for NRF52 based targets
|
||||
[nrf52_base]
|
||||
platform = nordicnrf52
|
||||
framework = arduino
|
||||
debug_tool = jlink
|
||||
build_type = debug ; I'm debugging with ICE a lot now
|
||||
; note: liboberon provides the AES256 implementation for NRF52 (though not using the hardware acceleration of the NRF52840 - FIXME)
|
||||
build_flags =
|
||||
${env.build_flags} -Wno-unused-variable -Isrc/nrf52 -Isdk-nrfxlib/crypto/nrf_oberon/include -Lsdk-nrfxlib/crypto/nrf_oberon/lib/cortex-m4/hard-float/ -lliboberon_3.0.3
|
||||
;-DCFG_DEBUG=3
|
||||
src_filter =
|
||||
${env.src_filter} -<esp32/>
|
||||
lib_ignore =
|
||||
BluetoothOTA
|
||||
monitor_port = /dev/ttyACM1
|
||||
|
||||
debug_extra_cmds =
|
||||
source gdbinit
|
||||
|
||||
; after programming the flash, reset the initial PC
|
||||
; debug_load_cmds = load
|
||||
|
||||
; Set initial breakpoint (defaults to main)
|
||||
debug_init_break =
|
||||
;debug_init_break = tbreak loop
|
||||
;debug_init_break = tbreak Reset_Handler
|
||||
|
||||
; The NRF52840-dk development board
|
||||
[env:nrf52dk]
|
||||
extends = nrf52_base
|
||||
board = nrf52840_dk_modified
|
||||
|
||||
# For experimenting with RAM sizes
|
||||
# board_build.ldscript = linker/nrf52840_s140_sim832.ld
|
||||
|
||||
; The PPR board
|
||||
[env:ppr]
|
||||
extends = nrf52_base
|
||||
board = ppr
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
UC1701
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
1
proto
Submodule
1
proto
Submodule
Submodule proto added at 72cbde93ff
1
release/.gitignore
vendored
1
release/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
*.elf
|
||||
*.bin
|
||||
*.map
|
||||
*.zip
|
||||
1
release/latest/.gitignore
vendored
Normal file
1
release/latest/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
curfirmwareversion.xml
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!-- This file is kept in source control because it reflects the last stable
|
||||
release. It is used by the android app for forcing software updates. Do not edit.
|
||||
Generated by bin/buildall.sh -->
|
||||
|
||||
<resources>
|
||||
<string name="cur_firmware_version">0.0.5</string>
|
||||
</resources>
|
||||
1
sdk-nrfxlib
Submodule
1
sdk-nrfxlib
Submodule
Submodule sdk-nrfxlib added at 17e8453553
@@ -1,187 +0,0 @@
|
||||
#include "CustomRF95.h"
|
||||
#include <pb_encode.h>
|
||||
#include <pb_decode.h>
|
||||
#include "configuration.h"
|
||||
#include "assert.h"
|
||||
#include "NodeDB.h"
|
||||
|
||||
/// A temporary buffer used for sending/receving packets, sized to hold the biggest buffer we might need
|
||||
#define MAX_RHPACKETLEN 251
|
||||
static uint8_t radiobuf[MAX_RHPACKETLEN];
|
||||
|
||||
CustomRF95::CustomRF95(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &_rxDest)
|
||||
: RH_RF95(NSS_GPIO, DIO0_GPIO),
|
||||
pool(_pool),
|
||||
rxDest(_rxDest),
|
||||
txQueue(MAX_TX_QUEUE),
|
||||
sendingPacket(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
bool CustomRF95::canSleep()
|
||||
{
|
||||
// We allow initializing mode, because sometimes while testing we don't ever call init() to turn on the hardware
|
||||
return (_mode == RHModeInitialising || _mode == RHModeIdle || _mode == RHModeRx) && !_isReceiving && txQueue.isEmpty();
|
||||
}
|
||||
|
||||
bool CustomRF95::sleep()
|
||||
{
|
||||
// we no longer care about interrupts from this device
|
||||
prepareDeepSleep();
|
||||
|
||||
// FIXME - leave the device state in rx mode instead
|
||||
return RH_RF95::sleep();
|
||||
}
|
||||
|
||||
bool CustomRF95::init()
|
||||
{
|
||||
bool ok = RH_RF95::init();
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
/// Send a packet (possibly by enquing in a private fifo). This routine will
|
||||
/// later free() the packet to pool. This routine is not allowed to stall because it is called from
|
||||
/// bluetooth comms code. If the txmit queue is empty it might return an error
|
||||
ErrorCode CustomRF95::send(MeshPacket *p)
|
||||
{
|
||||
// We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one).
|
||||
// To do otherwise would be doubly bad because not only would we drop the packet that was on the way in,
|
||||
// we almost certainly guarantee no one outside will like the packet we are sending.
|
||||
if (_mode == RHModeIdle || (_mode == RHModeRx && !_isReceiving))
|
||||
{
|
||||
// if the radio is idle, we can send right away
|
||||
DEBUG_MSG("immedate send on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", txGood(), rxGood(), rxBad());
|
||||
startSend(p);
|
||||
return ERRNO_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_MSG("enquing packet for send from=0x%x, to=0x%x\n", p->from, p->to);
|
||||
ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN;
|
||||
|
||||
if (res != ERRNO_OK) // we weren't able to queue it, so we must drop it to prevent leaks
|
||||
pool.release(p);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as necessary
|
||||
void CustomRF95::handleInterrupt()
|
||||
{
|
||||
RH_RF95::handleInterrupt();
|
||||
|
||||
BaseType_t higherPriWoken = false;
|
||||
if (_mode == RHModeIdle) // We are now done sending or receiving
|
||||
{
|
||||
if (sendingPacket) // Were we sending?
|
||||
{
|
||||
// We are done sending that packet, release it
|
||||
pool.releaseFromISR(sendingPacket, &higherPriWoken);
|
||||
sendingPacket = NULL;
|
||||
// DEBUG_MSG("Done with send\n");
|
||||
}
|
||||
|
||||
// If we just finished receiving a packet, forward it into a queue
|
||||
if (_rxBufValid)
|
||||
{
|
||||
// We received a packet
|
||||
|
||||
// Skip the 4 headers that are at the beginning of the rxBuf
|
||||
size_t payloadLen = _bufLen - RH_RF95_HEADER_LEN;
|
||||
uint8_t *payload = _buf + RH_RF95_HEADER_LEN;
|
||||
|
||||
// FIXME - throws exception if called in ISR context: frequencyError() - probably the floating point math
|
||||
int32_t freqerr = -1, snr = lastSNR();
|
||||
//DEBUG_MSG("Received packet from mesh src=0x%x,dest=0x%x,id=%d,len=%d rxGood=%d,rxBad=%d,freqErr=%d,snr=%d\n",
|
||||
// srcaddr, destaddr, id, rxlen, rf95.rxGood(), rf95.rxBad(), freqerr, snr);
|
||||
|
||||
MeshPacket *mp = pool.allocZeroed();
|
||||
|
||||
SubPacket *p = &mp->payload;
|
||||
|
||||
mp->from = _rxHeaderFrom;
|
||||
mp->to = _rxHeaderTo;
|
||||
//_rxHeaderId = _buf[2];
|
||||
//_rxHeaderFlags = _buf[3];
|
||||
|
||||
// If we already have an entry in the DB for this nodenum, goahead and hide the snr/freqerr info there.
|
||||
// Note: we can't create it at this point, because it might be a bogus User node allocation. But odds are we will
|
||||
// already have a record we can hide this debugging info in.
|
||||
NodeInfo *info = nodeDB.getNode(mp->from);
|
||||
if (info)
|
||||
{
|
||||
info->snr = snr;
|
||||
info->frequency_error = freqerr;
|
||||
}
|
||||
|
||||
if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p))
|
||||
{
|
||||
pool.releaseFromISR(mp, &higherPriWoken);
|
||||
}
|
||||
else
|
||||
{
|
||||
// parsing was successful, queue for our recipient
|
||||
mp->has_payload = true;
|
||||
|
||||
int res = rxDest.enqueueFromISR(mp, &higherPriWoken); // NOWAIT - fixme, if queue is full, delete older messages
|
||||
assert(res == pdTRUE);
|
||||
}
|
||||
|
||||
clearRxBuf(); // This message accepted and cleared
|
||||
}
|
||||
|
||||
higherPriWoken |= handleIdleISR();
|
||||
}
|
||||
|
||||
// If we call this _IT WILL NOT RETURN_
|
||||
if (higherPriWoken)
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
|
||||
/** The ISR doesn't have any good work to do, give a new assignment.
|
||||
*
|
||||
* Return true if a higher pri task has woken
|
||||
*/
|
||||
bool CustomRF95::handleIdleISR()
|
||||
{
|
||||
BaseType_t higherPriWoken = false;
|
||||
|
||||
// First send any outgoing packets we have ready
|
||||
MeshPacket *txp = txQueue.dequeuePtrFromISR(0);
|
||||
if (txp)
|
||||
startSend(txp);
|
||||
else
|
||||
{
|
||||
// Nothing to send, let's switch back to receive mode
|
||||
setModeRx();
|
||||
}
|
||||
|
||||
return higherPriWoken;
|
||||
}
|
||||
|
||||
/// This routine might be called either from user space or ISR
|
||||
void CustomRF95::startSend(MeshPacket *txp)
|
||||
{
|
||||
assert(!sendingPacket);
|
||||
|
||||
// DEBUG_MSG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", rf95.txGood(), rf95.rxGood(), rf95.rxBad());
|
||||
assert(txp->has_payload);
|
||||
|
||||
size_t numbytes = pb_encode_to_bytes(radiobuf, sizeof(radiobuf), SubPacket_fields, &txp->payload);
|
||||
|
||||
sendingPacket = txp;
|
||||
|
||||
setHeaderTo(txp->to);
|
||||
setHeaderFrom(nodeDB.getNodeNum()); // We must do this before each send, because we might have just changed our nodenum
|
||||
|
||||
// setHeaderId(0);
|
||||
|
||||
assert(numbytes <= 251); // Make sure we don't overflow the tiny max packet size
|
||||
|
||||
// uint32_t start = millis(); // FIXME, store this in the class
|
||||
|
||||
int res = RH_RF95::send(radiobuf, numbytes);
|
||||
assert(res);
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <RH_RF95.h>
|
||||
#include <RHMesh.h>
|
||||
#include "MemoryPool.h"
|
||||
#include "mesh.pb.h"
|
||||
#include "PointerQueue.h"
|
||||
#include "MeshTypes.h"
|
||||
|
||||
#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission
|
||||
|
||||
|
||||
/**
|
||||
* A version of the RF95 driver which is smart enough to manage packets via queues (no polling or blocking in user threads!)
|
||||
*/
|
||||
class CustomRF95 : public RH_RF95
|
||||
{
|
||||
friend class MeshRadio; // for debugging we let that class touch pool
|
||||
|
||||
MemoryPool<MeshPacket> &pool;
|
||||
PointerQueue<MeshPacket> &rxDest;
|
||||
PointerQueue<MeshPacket> txQueue;
|
||||
|
||||
MeshPacket *sendingPacket; // The packet we are currently sending
|
||||
public:
|
||||
/** pool is the pool we will alloc our rx packets from
|
||||
* rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool
|
||||
*/
|
||||
CustomRF95(MemoryPool<MeshPacket> &pool, PointerQueue<MeshPacket> &rxDest);
|
||||
|
||||
/**
|
||||
* Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving)
|
||||
*
|
||||
* This method must be used before putting the CPU into deep or light sleep.
|
||||
*/
|
||||
bool canSleep();
|
||||
|
||||
/// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep.
|
||||
virtual bool sleep();
|
||||
|
||||
/// Send a packet (possibly by enquing in a private fifo). This routine will
|
||||
/// later free() the packet to pool. This routine is not allowed to stall because it is called from
|
||||
/// bluetooth comms code. If the txmit queue is empty it might return an error
|
||||
ErrorCode send(MeshPacket *p);
|
||||
|
||||
bool init();
|
||||
|
||||
protected:
|
||||
// After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as necessary
|
||||
virtual void handleInterrupt();
|
||||
|
||||
private:
|
||||
/// Send a new packet - this low level call can be called from either ISR or userspace
|
||||
void startSend(MeshPacket *txp);
|
||||
|
||||
/// Return true if a higher pri task has woken
|
||||
bool handleIdleISR();
|
||||
};
|
||||
157
src/GPS.cpp
157
src/GPS.cpp
@@ -1,157 +0,0 @@
|
||||
|
||||
#include "GPS.h"
|
||||
#include "time.h"
|
||||
#include <sys/time.h>
|
||||
#include "configuration.h"
|
||||
|
||||
// stuff that really should be in in the instance instead...
|
||||
HardwareSerial _serial_gps(GPS_SERIAL_NUM);
|
||||
uint32_t timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time
|
||||
uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock
|
||||
|
||||
RTC_DATA_ATTR bool timeSetFromGPS; // We only reset our time once per _boot_ after that point just run from the internal clock (even across sleeps)
|
||||
|
||||
GPS gps;
|
||||
bool hasValidLocation; // default to false, until we complete our first read
|
||||
bool wantNewLocation = true;
|
||||
|
||||
GPS::GPS() : PeriodicTask()
|
||||
{
|
||||
}
|
||||
|
||||
void GPS::setup()
|
||||
{
|
||||
readFromRTC();
|
||||
|
||||
#ifdef GPS_RX_PIN
|
||||
_serial_gps.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
|
||||
#endif
|
||||
}
|
||||
|
||||
void GPS::readFromRTC()
|
||||
{
|
||||
struct timeval tv; /* btw settimeofday() is helpfull here too*/
|
||||
|
||||
if (!gettimeofday(&tv, NULL))
|
||||
{
|
||||
uint32_t now = millis();
|
||||
|
||||
DEBUG_MSG("Read RTC time as %ld (cur millis %u) valid=%d\n", tv.tv_sec, now, timeSetFromGPS);
|
||||
timeStartMsec = now;
|
||||
zeroOffsetSecs = tv.tv_sec;
|
||||
}
|
||||
}
|
||||
|
||||
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||
void GPS::perhapsSetRTC(const struct timeval *tv)
|
||||
{
|
||||
if (!timeSetFromGPS)
|
||||
{
|
||||
timeSetFromGPS = true;
|
||||
DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec);
|
||||
settimeofday(tv, NULL);
|
||||
readFromRTC();
|
||||
}
|
||||
}
|
||||
|
||||
#include <time.h>
|
||||
|
||||
// for the time being we need to rapidly read from the serial port to prevent overruns
|
||||
void GPS::loop()
|
||||
{
|
||||
PeriodicTask::loop();
|
||||
}
|
||||
|
||||
uint32_t GPS::getTime()
|
||||
{
|
||||
return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
|
||||
}
|
||||
|
||||
uint32_t GPS::getValidTime()
|
||||
{
|
||||
return timeSetFromGPS ? getTime() : 0;
|
||||
}
|
||||
|
||||
/// Returns true if we think the board can enter deep or light sleep now (we might be trying to get a GPS lock)
|
||||
bool GPS::canSleep()
|
||||
{
|
||||
return !wantNewLocation;
|
||||
}
|
||||
|
||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||
void GPS::prepareSleep()
|
||||
{
|
||||
// discard all rx serial bytes so we don't try to parse them when we come back
|
||||
while (_serial_gps.available())
|
||||
{
|
||||
_serial_gps.read();
|
||||
}
|
||||
|
||||
// make the parser bail on whatever it was parsing
|
||||
encode('\n');
|
||||
}
|
||||
|
||||
void GPS::doTask()
|
||||
{
|
||||
#ifdef GPS_RX_PIN
|
||||
// Consume all characters that have arrived
|
||||
|
||||
while (_serial_gps.available())
|
||||
{
|
||||
encode(_serial_gps.read());
|
||||
// DEBUG_MSG("Got GPS response\n");
|
||||
}
|
||||
|
||||
if (!timeSetFromGPS && time.isValid() && date.isValid())
|
||||
{
|
||||
struct timeval tv;
|
||||
|
||||
DEBUG_MSG("Got time from GPS\n");
|
||||
|
||||
/* Convert to unix time
|
||||
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
||||
*/
|
||||
struct tm t;
|
||||
t.tm_sec = time.second();
|
||||
t.tm_min = time.minute();
|
||||
t.tm_hour = time.hour();
|
||||
t.tm_mday = date.day();
|
||||
t.tm_mon = date.month() - 1;
|
||||
t.tm_year = date.year() - 1900;
|
||||
t.tm_isdst = false;
|
||||
time_t res = mktime(&t);
|
||||
tv.tv_sec = res;
|
||||
tv.tv_usec = 0; // time.centisecond() * (10 / 1000);
|
||||
|
||||
perhapsSetRTC(&tv);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (location.isValid() && location.isUpdated())
|
||||
{ // we only notify if position has changed
|
||||
// DEBUG_MSG("new gps pos\n");
|
||||
hasValidLocation = true;
|
||||
wantNewLocation = false;
|
||||
notifyObservers();
|
||||
}
|
||||
else // we didn't get a location update, go back to sleep and hope the characters show up
|
||||
wantNewLocation = true;
|
||||
|
||||
// Once we have sent a location once we only poll the GPS rarely, otherwise check back every 100ms until we have something over the serial
|
||||
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 100);
|
||||
}
|
||||
|
||||
void GPS::startLock()
|
||||
{
|
||||
DEBUG_MSG("Looking for GPS lock\n");
|
||||
wantNewLocation = true;
|
||||
setPeriod(1);
|
||||
}
|
||||
|
||||
String GPS::getTimeStr()
|
||||
{
|
||||
static char t[12]; // used to sprintf for Serial output
|
||||
|
||||
snprintf(t, sizeof(t), "%02d:%02d:%02d", time.hour(), time.minute(), time.second());
|
||||
return t;
|
||||
}
|
||||
48
src/GPS.h
48
src/GPS.h
@@ -1,48 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <TinyGPS++.h>
|
||||
#include "PeriodicTask.h"
|
||||
#include "Observer.h"
|
||||
#include "sys/time.h"
|
||||
|
||||
/**
|
||||
* A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading)
|
||||
*
|
||||
* When new data is available it will notify observers.
|
||||
*/
|
||||
class GPS : public PeriodicTask, public Observable, public TinyGPSPlus
|
||||
{
|
||||
public:
|
||||
GPS();
|
||||
|
||||
/// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero
|
||||
uint32_t getTime();
|
||||
|
||||
/// Return time since 1970 in secs. If we don't have a GPS lock return zero
|
||||
uint32_t getValidTime();
|
||||
|
||||
String getTimeStr();
|
||||
|
||||
void setup();
|
||||
|
||||
virtual void loop();
|
||||
|
||||
virtual void doTask();
|
||||
|
||||
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||
void perhapsSetRTC(const struct timeval *tv);
|
||||
|
||||
/// Returns true if we think the board can enter deep or light sleep now (we might be trying to get a GPS lock)
|
||||
bool canSleep();
|
||||
|
||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||
void prepareSleep();
|
||||
|
||||
/// Restart our lock attempt - try to get and broadcast a GPS reading ASAP
|
||||
void startLock();
|
||||
|
||||
private:
|
||||
void readFromRTC();
|
||||
};
|
||||
|
||||
extern GPS gps;
|
||||
@@ -1,81 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "PointerQueue.h"
|
||||
|
||||
/**
|
||||
* A pool based allocator
|
||||
*
|
||||
* Eventually this routine will even be safe for ISR use...
|
||||
*/
|
||||
template <class T>
|
||||
class MemoryPool
|
||||
{
|
||||
PointerQueue<T> dead;
|
||||
|
||||
T *buf; // our large raw block of memory
|
||||
|
||||
size_t maxElements;
|
||||
|
||||
public:
|
||||
MemoryPool(size_t _maxElements) : dead(_maxElements), maxElements(_maxElements)
|
||||
{
|
||||
buf = new T[maxElements];
|
||||
|
||||
// prefill dead
|
||||
for (int i = 0; i < maxElements; i++)
|
||||
release(&buf[i]);
|
||||
}
|
||||
|
||||
~MemoryPool()
|
||||
{
|
||||
delete[] buf;
|
||||
}
|
||||
|
||||
/// Return a queable object which has been prefilled with zeros. Panic if no buffer is available
|
||||
T *allocZeroed()
|
||||
{
|
||||
T *p = allocZeroed(0);
|
||||
|
||||
assert(p); // FIXME panic instead
|
||||
return p;
|
||||
}
|
||||
|
||||
/// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you probably don't want this version)
|
||||
T *allocZeroed(TickType_t maxWait)
|
||||
{
|
||||
T *p = dead.dequeuePtr(maxWait);
|
||||
|
||||
if (p)
|
||||
memset(p, 0, sizeof(T));
|
||||
return p;
|
||||
}
|
||||
|
||||
/// Return a queable object which is a copy of some other object
|
||||
T *allocCopy(const T &src, TickType_t maxWait = portMAX_DELAY)
|
||||
{
|
||||
T *p = dead.dequeuePtr(maxWait);
|
||||
|
||||
if (p)
|
||||
*p = src;
|
||||
return p;
|
||||
}
|
||||
|
||||
/// Return a buffer for use by others
|
||||
void release(T *p)
|
||||
{
|
||||
int res = dead.enqueue(p, 0);
|
||||
assert(res == pdTRUE);
|
||||
assert(p >= buf && (p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
|
||||
}
|
||||
|
||||
/// Return a buffer from an ISR, if higherPriWoken is set to true you have some work to do ;-)
|
||||
void releaseFromISR(T *p, BaseType_t *higherPriWoken)
|
||||
{
|
||||
int res = dead.enqueueFromISR(p, higherPriWoken);
|
||||
assert(res == pdTRUE);
|
||||
assert(p >= buf && (p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
|
||||
}
|
||||
};
|
||||
@@ -1,350 +0,0 @@
|
||||
#include "BluetoothUtil.h"
|
||||
#include "MeshBluetoothService.h"
|
||||
#include <esp_gatt_defs.h>
|
||||
#include <BLE2902.h>
|
||||
#include <Arduino.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "mesh.pb.h"
|
||||
#include "MeshService.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "NodeDB.h"
|
||||
#include "configuration.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "CallbackCharacteristic.h"
|
||||
|
||||
// This scratch buffer is used for various bluetooth reads/writes - but it is safe because only one bt operation can be in proccess at once
|
||||
static uint8_t trBytes[_max(_max(_max(_max(ToRadio_size, RadioConfig_size), User_size), MyNodeInfo_size), FromRadio_size)];
|
||||
|
||||
class ProtobufCharacteristic : public CallbackCharacteristic
|
||||
{
|
||||
const pb_msgdesc_t *fields;
|
||||
void *my_struct;
|
||||
|
||||
public:
|
||||
ProtobufCharacteristic(const char *uuid, uint32_t btprops, const pb_msgdesc_t *_fields, void *_my_struct)
|
||||
: CallbackCharacteristic(uuid, btprops),
|
||||
fields(_fields),
|
||||
my_struct(_my_struct)
|
||||
{
|
||||
setCallbacks(this);
|
||||
}
|
||||
|
||||
void onRead(BLECharacteristic *c)
|
||||
{
|
||||
BLEKeepAliveCallbacks::onRead(c);
|
||||
size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), fields, my_struct);
|
||||
DEBUG_MSG("pbread from %s returns %d bytes\n", c->getUUID().toString().c_str(), numbytes);
|
||||
c->setValue(trBytes, numbytes);
|
||||
}
|
||||
|
||||
void onWrite(BLECharacteristic *c)
|
||||
{
|
||||
BLEKeepAliveCallbacks::onWrite(c);
|
||||
writeToDest(c, my_struct);
|
||||
}
|
||||
|
||||
protected:
|
||||
/// like onWrite, but we provide an different destination to write to, for use by subclasses that
|
||||
/// want to optionally ignore parts of writes.
|
||||
/// returns true for success
|
||||
bool writeToDest(BLECharacteristic *c, void *dest)
|
||||
{
|
||||
// dumpCharacteristic(pCharacteristic);
|
||||
std::string src = c->getValue();
|
||||
DEBUG_MSG("pbwrite to %s of %d bytes\n", c->getUUID().toString().c_str(), src.length());
|
||||
return pb_decode_from_bytes((const uint8_t *)src.c_str(), src.length(), fields, dest);
|
||||
}
|
||||
};
|
||||
|
||||
class NodeInfoCharacteristic : public BLECharacteristic, public BLEKeepAliveCallbacks
|
||||
{
|
||||
public:
|
||||
NodeInfoCharacteristic()
|
||||
: BLECharacteristic("d31e02e0-c8ab-4d3f-9cc9-0b8466bdabe8", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ)
|
||||
{
|
||||
setCallbacks(this);
|
||||
}
|
||||
|
||||
void onRead(BLECharacteristic *c)
|
||||
{
|
||||
BLEKeepAliveCallbacks::onRead(c);
|
||||
|
||||
const NodeInfo *info = nodeDB.readNextInfo();
|
||||
|
||||
if (info)
|
||||
{
|
||||
DEBUG_MSG("Sending nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", info->num, info->position.time, info->user.id, info->user.long_name);
|
||||
size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), NodeInfo_fields, info);
|
||||
c->setValue(trBytes, numbytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
c->setValue(trBytes, 0); // Send an empty response
|
||||
DEBUG_MSG("Done sending nodeinfos\n");
|
||||
}
|
||||
}
|
||||
|
||||
void onWrite(BLECharacteristic *c)
|
||||
{
|
||||
BLEKeepAliveCallbacks::onWrite(c);
|
||||
DEBUG_MSG("Reset nodeinfo read pointer\n");
|
||||
nodeDB.resetReadPointer();
|
||||
}
|
||||
};
|
||||
|
||||
// wrap our protobuf version with something that forces the service to reload the config
|
||||
class RadioCharacteristic : public ProtobufCharacteristic
|
||||
{
|
||||
public:
|
||||
RadioCharacteristic()
|
||||
: ProtobufCharacteristic("b56786c8-839a-44a1-b98e-a1724c4a0262", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ, RadioConfig_fields, &radioConfig)
|
||||
{
|
||||
}
|
||||
|
||||
void onWrite(BLECharacteristic *c)
|
||||
{
|
||||
ProtobufCharacteristic::onWrite(c);
|
||||
service.reloadConfig();
|
||||
}
|
||||
};
|
||||
|
||||
// wrap our protobuf version with something that forces the service to reload the owner
|
||||
class OwnerCharacteristic : public ProtobufCharacteristic
|
||||
{
|
||||
public:
|
||||
OwnerCharacteristic()
|
||||
: ProtobufCharacteristic("6ff1d8b6-e2de-41e3-8c0b-8fa384f64eb6", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ, User_fields, &owner)
|
||||
{
|
||||
}
|
||||
|
||||
void onWrite(BLECharacteristic *c)
|
||||
{
|
||||
BLEKeepAliveCallbacks::onWrite(c); // NOTE: We do not call the standard ProtobufCharacteristic superclass, because we want custom write behavior
|
||||
|
||||
static User o; // if the phone doesn't set ID we are careful to keep ours, we also always keep our macaddr
|
||||
if (writeToDest(c, &o))
|
||||
{
|
||||
int changed = 0;
|
||||
|
||||
if (*o.long_name)
|
||||
{
|
||||
changed |= strcmp(owner.long_name, o.long_name);
|
||||
strcpy(owner.long_name, o.long_name);
|
||||
}
|
||||
if (*o.short_name)
|
||||
{
|
||||
changed |= strcmp(owner.short_name, o.short_name);
|
||||
strcpy(owner.short_name, o.short_name);
|
||||
}
|
||||
if (*o.id)
|
||||
{
|
||||
changed |= strcmp(owner.id, o.id);
|
||||
strcpy(owner.id, o.id);
|
||||
}
|
||||
|
||||
if (changed) // If nothing really changed, don't broadcast on the network or write to flash
|
||||
service.reloadOwner();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class ToRadioCharacteristic : public CallbackCharacteristic
|
||||
{
|
||||
public:
|
||||
ToRadioCharacteristic()
|
||||
: CallbackCharacteristic("f75c76d2-129e-4dad-a1dd-7866124401e7", BLECharacteristic::PROPERTY_WRITE)
|
||||
{
|
||||
}
|
||||
|
||||
void onWrite(BLECharacteristic *c)
|
||||
{
|
||||
BLEKeepAliveCallbacks::onWrite(c);
|
||||
DEBUG_MSG("Got on write\n");
|
||||
|
||||
service.handleToRadio(c->getValue());
|
||||
}
|
||||
};
|
||||
|
||||
class FromRadioCharacteristic : public CallbackCharacteristic
|
||||
{
|
||||
public:
|
||||
FromRadioCharacteristic()
|
||||
: CallbackCharacteristic("8ba2bcc2-ee02-4a55-a531-c525c5e454d5", BLECharacteristic::PROPERTY_READ)
|
||||
{
|
||||
}
|
||||
|
||||
void onRead(BLECharacteristic *c)
|
||||
{
|
||||
BLEKeepAliveCallbacks::onRead(c);
|
||||
MeshPacket *mp = service.getForPhone();
|
||||
|
||||
// Someone is going to read our value as soon as this callback returns. So fill it with the next message in the queue
|
||||
// or make empty if the queue is empty
|
||||
if (!mp)
|
||||
{
|
||||
DEBUG_MSG("toPhone queue is empty\n");
|
||||
c->setValue((uint8_t *)"", 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
static FromRadio fRadio;
|
||||
|
||||
// Encapsulate as a ToRadio packet
|
||||
memset(&fRadio, 0, sizeof(fRadio));
|
||||
fRadio.which_variant = FromRadio_packet_tag;
|
||||
fRadio.variant.packet = *mp;
|
||||
|
||||
service.releaseToPool(mp); // we just copied the bytes, so don't need this buffer anymore
|
||||
|
||||
size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), FromRadio_fields, &fRadio);
|
||||
DEBUG_MSG("delivering toPhone packet to phone %d bytes\n", numbytes);
|
||||
c->setValue(trBytes, numbytes);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class FromNumCharacteristic : public CallbackCharacteristic
|
||||
{
|
||||
public:
|
||||
FromNumCharacteristic()
|
||||
: CallbackCharacteristic("ed9da18c-a800-4f66-a670-aa7547e34453",
|
||||
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY)
|
||||
{
|
||||
}
|
||||
|
||||
void onRead(BLECharacteristic *c)
|
||||
{
|
||||
BLEKeepAliveCallbacks::onRead(c);
|
||||
DEBUG_MSG("FIXME implement fromnum read\n");
|
||||
}
|
||||
};
|
||||
|
||||
FromNumCharacteristic *meshFromNumCharacteristic;
|
||||
|
||||
/**
|
||||
* Tell any bluetooth clients that the number of rx packets has changed
|
||||
*/
|
||||
void bluetoothNotifyFromNum(uint32_t newValue)
|
||||
{
|
||||
if (meshFromNumCharacteristic)
|
||||
{
|
||||
// if bt not running ignore
|
||||
meshFromNumCharacteristic->setValue(newValue);
|
||||
meshFromNumCharacteristic->notify();
|
||||
}
|
||||
}
|
||||
|
||||
BLEService *meshService;
|
||||
|
||||
/*
|
||||
MeshBluetoothService UUID 6ba1b218-15a8-461f-9fa8-5dcae273eafd
|
||||
|
||||
FIXME - notify vs indication for fromradio output. Using notify for now, not sure if that is best
|
||||
FIXME - in the esp32 mesh managment code, occasionally mirror the current net db to flash, so that if we reboot we still have a good guess of users who are out there.
|
||||
FIXME - make sure this protocol is guaranteed robust and won't drop packets
|
||||
|
||||
"According to the BLE specification the notification length can be max ATT_MTU - 3. The 3 bytes subtracted is the 3-byte header(OP-code (operation, 1 byte) and the attribute handle (2 bytes)).
|
||||
In BLE 4.1 the ATT_MTU is 23 bytes (20 bytes for payload), but in BLE 4.2 the ATT_MTU can be negotiated up to 247 bytes."
|
||||
|
||||
MAXPACKET is 256? look into what the lora lib uses. FIXME
|
||||
|
||||
Characteristics:
|
||||
UUID
|
||||
properties
|
||||
description
|
||||
|
||||
8ba2bcc2-ee02-4a55-a531-c525c5e454d5
|
||||
read
|
||||
fromradio - contains a newly received packet destined towards the phone (up to MAXPACKET bytes? per packet).
|
||||
After reading the esp32 will put the next packet in this mailbox. If the FIFO is empty it will put an empty packet in this
|
||||
mailbox.
|
||||
|
||||
f75c76d2-129e-4dad-a1dd-7866124401e7
|
||||
write
|
||||
toradio - write ToRadio protobufs to this charstic to send them (up to MAXPACKET len)
|
||||
|
||||
ed9da18c-a800-4f66-a670-aa7547e34453
|
||||
read|notify|write
|
||||
fromnum - the current packet # in the message waiting inside fromradio, if the phone sees this notify it should read messages
|
||||
until it catches up with this number.
|
||||
The phone can write to this register to go backwards up to FIXME packets, to handle the rare case of a fromradio packet was dropped after the esp32
|
||||
callback was called, but before it arrives at the phone. If the phone writes to this register the esp32 will discard older packets and put the next packet >= fromnum in fromradio.
|
||||
When the esp32 advances fromnum, it will delay doing the notify by 100ms, in the hopes that the notify will never actally need to be sent if the phone is already pulling from fromradio.
|
||||
Note: that if the phone ever sees this number decrease, it means the esp32 has rebooted.
|
||||
|
||||
meshMyNodeCharacteristic("ea9f3f82-8dc4-4733-9452-1f6da28892a2", BLECharacteristic::PROPERTY_READ)
|
||||
mynode - read this to access a MyNodeInfo protobuf
|
||||
|
||||
meshNodeInfoCharacteristic("d31e02e0-c8ab-4d3f-9cc9-0b8466bdabe8", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ),
|
||||
nodeinfo - read this to get a series of node infos (ending with a null empty record), write to this to restart the read statemachine that returns all the node infos
|
||||
|
||||
meshRadioCharacteristic("b56786c8-839a-44a1-b98e-a1724c4a0262", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ),
|
||||
radio - read/write this to access a RadioConfig protobuf
|
||||
|
||||
meshOwnerCharacteristic("6ff1d8b6-e2de-41e3-8c0b-8fa384f64eb6", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ)
|
||||
owner - read/write this to access a User protobuf
|
||||
|
||||
Re: queue management
|
||||
Not all messages are kept in the fromradio queue (filtered based on SubPacket):
|
||||
* only the most recent Position and User messages for a particular node are kept
|
||||
* all Data SubPackets are kept
|
||||
* No WantNodeNum / DenyNodeNum messages are kept
|
||||
A variable keepAllPackets, if set to true will suppress this behavior and instead keep everything for forwarding to the phone (for debugging)
|
||||
|
||||
*/
|
||||
BLEService *createMeshBluetoothService(BLEServer *server)
|
||||
{
|
||||
// Create the BLE Service, we need more than the default of 15 handles
|
||||
BLEService *service = server->createService(BLEUUID("6ba1b218-15a8-461f-9fa8-5dcae273eafd"), 25, 0);
|
||||
|
||||
assert(!meshFromNumCharacteristic);
|
||||
meshFromNumCharacteristic = new FromNumCharacteristic;
|
||||
|
||||
addWithDesc(service, meshFromNumCharacteristic, "fromRadio");
|
||||
addWithDesc(service, new ToRadioCharacteristic, "toRadio");
|
||||
addWithDesc(service, new FromRadioCharacteristic, "fromNum");
|
||||
|
||||
addWithDesc(service, new ProtobufCharacteristic("ea9f3f82-8dc4-4733-9452-1f6da28892a2", BLECharacteristic::PROPERTY_READ, MyNodeInfo_fields, &myNodeInfo), "myNode");
|
||||
addWithDesc(service, new RadioCharacteristic, "radio");
|
||||
addWithDesc(service, new OwnerCharacteristic, "owner");
|
||||
addWithDesc(service, new NodeInfoCharacteristic, "nodeinfo");
|
||||
|
||||
meshFromNumCharacteristic->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification
|
||||
|
||||
service->start();
|
||||
|
||||
// We only add to advertisting once, because the ESP32 arduino code is dumb and that object never dies
|
||||
static bool firstTime = true;
|
||||
if (firstTime)
|
||||
{
|
||||
firstTime = false;
|
||||
server->getAdvertising()->addServiceUUID(service->getUUID());
|
||||
}
|
||||
|
||||
DEBUG_MSG("*** Mesh service:\n");
|
||||
service->dump();
|
||||
|
||||
meshService = service;
|
||||
return service;
|
||||
}
|
||||
|
||||
void destroyMeshBluetoothService()
|
||||
{
|
||||
assert(meshService);
|
||||
delete meshService;
|
||||
|
||||
meshFromNumCharacteristic = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Super skanky FIXME - when we start a software update we force the mesh service to shutdown.
|
||||
* If the sw update fails, the user will have to manually reset the board to get things running again.
|
||||
*/
|
||||
void stopMeshBluetoothService()
|
||||
{
|
||||
if (meshService)
|
||||
meshService->stop();
|
||||
|
||||
meshFromNumCharacteristic = NULL; // don't try to notify anymore
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
#include <SPI.h>
|
||||
#include "RH_RF95.h"
|
||||
#include <RHMesh.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pb_encode.h>
|
||||
#include <pb_decode.h>
|
||||
#include "MeshRadio.h"
|
||||
#include "configuration.h"
|
||||
#include "NodeDB.h"
|
||||
|
||||
#define DEFAULT_CHANNEL_NUM 3 // we randomly pick one
|
||||
|
||||
/// 16 bytes of random PSK for our _public_ default channel that all devices power up on
|
||||
static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf};
|
||||
|
||||
/**
|
||||
* ## LoRaWAN for North America
|
||||
|
||||
LoRaWAN defines 64, 125 kHz channels from 902.3 to 914.9 MHz increments.
|
||||
|
||||
The maximum output power for North America is +30 dBM.
|
||||
|
||||
The band is from 902 to 928 MHz. It mentions channel number and its respective channel frequency. All the 13 channels are separated by 2.16 MHz with respect to the adjacent channels.
|
||||
Channel zero starts at 903.08 MHz center frequency.
|
||||
*/
|
||||
|
||||
/// Sometimes while debugging it is useful to set this false, to disable rf95 accesses
|
||||
bool useHardware = true;
|
||||
|
||||
MeshRadio::MeshRadio(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &_rxDest)
|
||||
: rf95(_pool, _rxDest),
|
||||
manager(rf95)
|
||||
{
|
||||
myNodeInfo.num_channels = NUM_CHANNELS;
|
||||
|
||||
//radioConfig.modem_config = RadioConfig_ModemConfig_Bw125Cr45Sf128; // medium range and fast
|
||||
//channelSettings.modem_config = ChannelSettings_ModemConfig_Bw500Cr45Sf128; // short range and fast, but wide bandwidth so incompatible radios can talk together
|
||||
channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range
|
||||
|
||||
channelSettings.tx_power = 23;
|
||||
channelSettings.channel_num = DEFAULT_CHANNEL_NUM;
|
||||
memcpy(&channelSettings.psk, &defaultpsk, sizeof(channelSettings.psk));
|
||||
strcpy(channelSettings.name, "Default");
|
||||
// Can't print strings this early - serial not setup yet
|
||||
// DEBUG_MSG("Set meshradio defaults name=%s\n", channelSettings.name);
|
||||
}
|
||||
|
||||
bool MeshRadio::init()
|
||||
{
|
||||
if (!useHardware)
|
||||
return true;
|
||||
|
||||
DEBUG_MSG("Starting meshradio init...\n");
|
||||
|
||||
#ifdef RESET_GPIO
|
||||
pinMode(RESET_GPIO, OUTPUT); // Deassert reset
|
||||
digitalWrite(RESET_GPIO, HIGH);
|
||||
|
||||
// pulse reset
|
||||
digitalWrite(RESET_GPIO, LOW);
|
||||
delay(10);
|
||||
digitalWrite(RESET_GPIO, HIGH);
|
||||
delay(10);
|
||||
#endif
|
||||
|
||||
manager.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor time.
|
||||
|
||||
if (!manager.init())
|
||||
{
|
||||
DEBUG_MSG("LoRa radio init failed\n");
|
||||
DEBUG_MSG("Uncomment '#define SERIAL_DEBUG' in RH_RF95.cpp for detailed debug info\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// not needed - defaults on
|
||||
// rf95.setPayloadCRC(true);
|
||||
|
||||
reloadConfig();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MeshRadio::reloadConfig()
|
||||
{
|
||||
rf95.setModeIdle(); // Need to be idle before doing init
|
||||
|
||||
// Set up default configuration
|
||||
// No Sync Words in LORA mode.
|
||||
rf95.setModemConfig((RH_RF95::ModemConfigChoice)channelSettings.modem_config); // Radio default
|
||||
// setModemConfig(Bw125Cr48Sf4096); // slow and reliable?
|
||||
// rf95.setPreambleLength(8); // Default is 8
|
||||
|
||||
assert(channelSettings.channel_num < NUM_CHANNELS); // If the phone tries to tell us to use an illegal channel then panic
|
||||
|
||||
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
|
||||
float center_freq = CH0 + CH_SPACING * channelSettings.channel_num;
|
||||
if (!rf95.setFrequency(center_freq))
|
||||
{
|
||||
DEBUG_MSG("setFrequency failed\n");
|
||||
assert(0); // fixme panic
|
||||
}
|
||||
|
||||
// Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
|
||||
|
||||
// The default transmitter power is 13dBm, using PA_BOOST.
|
||||
// If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then
|
||||
// you can set transmitter powers from 5 to 23 dBm:
|
||||
// FIXME - can we do this? It seems to be in the Heltec board.
|
||||
rf95.setTxPower(channelSettings.tx_power, false);
|
||||
|
||||
DEBUG_MSG("Set radio: name=%s. config=%u, ch=%d, txpower=%d\n", channelSettings.name, channelSettings.modem_config, channelSettings.channel_num, channelSettings.tx_power);
|
||||
|
||||
// Done with init tell radio to start receiving
|
||||
rf95.setModeRx();
|
||||
}
|
||||
|
||||
ErrorCode MeshRadio::send(MeshPacket *p)
|
||||
{
|
||||
if (useHardware)
|
||||
return rf95.send(p);
|
||||
else
|
||||
{
|
||||
rf95.pool.release(p);
|
||||
return ERRNO_OK;
|
||||
}
|
||||
}
|
||||
|
||||
void MeshRadio::loop()
|
||||
{
|
||||
// Currently does nothing, since we do it all in ISRs now
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CustomRF95.h"
|
||||
#include <RHMesh.h>
|
||||
#include "MemoryPool.h"
|
||||
#include "mesh.pb.h"
|
||||
#include "PointerQueue.h"
|
||||
#include "MeshTypes.h"
|
||||
#include "configuration.h"
|
||||
|
||||
// US channel settings
|
||||
#define CH0_US 903.08f // MHz
|
||||
#define CH_SPACING_US 2.16f // MHz
|
||||
#define NUM_CHANNELS_US 13
|
||||
|
||||
// EU channel settings
|
||||
#define CH0_EU 865.2f // MHz
|
||||
#define CH_SPACING_EU 0.3f // MHz
|
||||
#define NUM_CHANNELS_EU 10
|
||||
|
||||
// CN channel settings
|
||||
#define CH0_CN 470.0f // MHz
|
||||
#define CH_SPACING_CN 2.0f // MHz FIXME, this is just a guess for 470-510
|
||||
#define NUM_CHANNELS_CN 20
|
||||
|
||||
// CN channel settings
|
||||
#define CH0_JP 920.0f // MHz
|
||||
#define CH_SPACING_JP 0.5f // MHz FIXME, this is just a guess for 920-925
|
||||
#define NUM_CHANNELS_JP 10
|
||||
|
||||
// FIXME add defs for other regions and use them here
|
||||
#ifdef HW_VERSION_US
|
||||
#define CH0 CH0_US
|
||||
#define CH_SPACING CH_SPACING_US
|
||||
#define NUM_CHANNELS NUM_CHANNELS_US
|
||||
#elif defined(HW_VERSION_EU)
|
||||
#define CH0 CH0_EU
|
||||
#define CH_SPACING CH_SPACING_EU
|
||||
#define NUM_CHANNELS NUM_CHANNELS_EU
|
||||
#elif defined(HW_VERSION_CN)
|
||||
#define CH0 CH0_CN
|
||||
#define CH_SPACING CH_SPACING_CN
|
||||
#define NUM_CHANNELS NUM_CHANNELS_CN
|
||||
#elif defined(HW_VERSION_JP)
|
||||
#define CH0 CH0_JP
|
||||
#define CH_SPACING CH_SPACING_JP
|
||||
#define NUM_CHANNELS NUM_CHANNELS_JP
|
||||
#else
|
||||
#error "HW_VERSION not set"
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* A raw low level interface to our mesh. Only understands nodenums and bytes (not protobufs or node ids)
|
||||
*/
|
||||
class MeshRadio {
|
||||
public:
|
||||
CustomRF95 rf95; // the raw radio interface - for now I'm leaving public - because this class is shrinking to be almost nothing
|
||||
|
||||
/** pool is the pool we will alloc our rx packets from
|
||||
* rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool
|
||||
*/
|
||||
MeshRadio(MemoryPool<MeshPacket> &pool, PointerQueue<MeshPacket> &rxDest);
|
||||
|
||||
bool init();
|
||||
|
||||
/// Send a packet (possibly by enquing in a private fifo). This routine will
|
||||
/// later free() the packet to pool. This routine is not allowed to stall because it is called from
|
||||
/// bluetooth comms code. If the txmit queue is empty it might return an error
|
||||
ErrorCode send(MeshPacket *p);
|
||||
|
||||
/// Do loop callback operations (we currently FIXME poll the receive mailbox here)
|
||||
/// for received packets it will call the rx handler
|
||||
void loop();
|
||||
|
||||
/// The radioConfig object just changed, call this to force the hw to change to the new settings
|
||||
void reloadConfig();
|
||||
|
||||
private:
|
||||
|
||||
// RHDatagram manager;
|
||||
// RHReliableDatagram manager; // don't use mesh yet
|
||||
RHMesh manager;
|
||||
// MeshRXHandler rxHandler;
|
||||
|
||||
/// low level send, might block for mutiple seconds
|
||||
ErrorCode sendTo(NodeNum dest, const uint8_t *buf, size_t len);
|
||||
|
||||
/// enqueue a received packet in rxDest
|
||||
void handleReceive(MeshPacket *p);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,355 +0,0 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "MeshService.h"
|
||||
#include "MeshBluetoothService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "GPS.h"
|
||||
#include "screen.h"
|
||||
#include "Periodic.h"
|
||||
|
||||
/*
|
||||
receivedPacketQueue - this is a queue of messages we've received from the mesh, which we are keeping to deliver to the phone.
|
||||
It is implemented with a FreeRTos queue (wrapped with a little RTQueue class) of pointers to MeshPacket protobufs (which were alloced with new).
|
||||
After a packet ptr is removed from the queue and processed it should be deleted. (eventually we should move sent packets into a 'sentToPhone' queue
|
||||
of packets we can delete just as soon as we are sure the phone has acked those packets - when the phone writes to FromNum)
|
||||
|
||||
mesh - an instance of Mesh class. Which manages the interface to the mesh radio library, reception of packets from other nodes, arbitrating to select
|
||||
a node number and keeping the current nodedb.
|
||||
|
||||
*/
|
||||
|
||||
/* Broadcast when a newly powered mesh node wants to find a node num it can use
|
||||
|
||||
The algoritm is as follows:
|
||||
* when a node starts up, it broadcasts their user and the normal flow is for all other nodes to reply with their User as well (so the new node can build its node db)
|
||||
* If a node ever receives a User (not just the first broadcast) message where the sender node number equals our node number, that indicates a collision has occurred and the following steps should happen:
|
||||
|
||||
If the receiving node (that was already in the mesh)'s macaddr is LOWER than the new User who just tried to sign in: it gets to keep its nodenum. We send a broadcast message
|
||||
of OUR User (we use a broadcast so that the other node can receive our message, considering we have the same id - it also serves to let observers correct their nodedb) - this case is rare so it should be okay.
|
||||
|
||||
If any node receives a User where the macaddr is GTE than their local macaddr, they have been vetoed and should pick a new random nodenum (filtering against whatever it knows about the nodedb) and
|
||||
rebroadcast their User.
|
||||
|
||||
FIXME in the initial proof of concept we just skip the entire want/deny flow and just hand pick node numbers at first.
|
||||
*/
|
||||
|
||||
MeshService service;
|
||||
|
||||
// I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX
|
||||
#define MAX_PACKETS (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + MAX_TX_QUEUE + 2) // max number of packets which can be in flight (either queued from reception or queued for sending)
|
||||
|
||||
#define MAX_RX_FROMRADIO 4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big
|
||||
|
||||
MeshService::MeshService()
|
||||
: packetPool(MAX_PACKETS),
|
||||
toPhoneQueue(MAX_RX_TOPHONE),
|
||||
fromRadioQueue(MAX_RX_FROMRADIO),
|
||||
fromNum(0),
|
||||
radio(packetPool, fromRadioQueue)
|
||||
{
|
||||
// assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro
|
||||
}
|
||||
|
||||
void MeshService::init()
|
||||
{
|
||||
nodeDB.init();
|
||||
|
||||
if (!radio.init())
|
||||
DEBUG_MSG("radio init failed\n");
|
||||
|
||||
gps.addObserver(this);
|
||||
|
||||
// No need to call this here, our periodic task will fire quite soon
|
||||
// sendOwnerPeriod();
|
||||
}
|
||||
|
||||
void MeshService::sendOurOwner(NodeNum dest)
|
||||
{
|
||||
MeshPacket *p = allocForSending();
|
||||
p->to = dest;
|
||||
p->payload.which_variant = SubPacket_user_tag;
|
||||
User &u = p->payload.variant.user;
|
||||
u = owner;
|
||||
DEBUG_MSG("sending owner %s/%s/%s\n", u.id, u.long_name, u.short_name);
|
||||
|
||||
sendToMesh(p);
|
||||
}
|
||||
|
||||
/// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all
|
||||
MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp)
|
||||
{
|
||||
bool wasBroadcast = mp->to == NODENUM_BROADCAST;
|
||||
bool isCollision = mp->from == myNodeInfo.my_node_num;
|
||||
|
||||
// we win if we have a lower macaddr
|
||||
bool weWin = memcmp(&owner.macaddr, &mp->payload.variant.user.macaddr, sizeof(owner.macaddr)) < 0;
|
||||
|
||||
if (isCollision)
|
||||
{
|
||||
if (weWin)
|
||||
{
|
||||
DEBUG_MSG("NOTE! Received a nodenum collision and we are vetoing\n");
|
||||
|
||||
packetPool.release(mp); // discard it
|
||||
mp = NULL;
|
||||
|
||||
sendOurOwner(); // send our owner as a _broadcast_ because that other guy is mistakenly using our nodenum
|
||||
}
|
||||
else
|
||||
{
|
||||
// we lost, we need to try for a new nodenum!
|
||||
DEBUG_MSG("NOTE! Received a nodenum collision we lost, so picking a new nodenum\n");
|
||||
nodeDB.updateFrom(*mp); // update the DB early - before trying to repick (so we don't select the same node number again)
|
||||
nodeDB.pickNewNodeNum();
|
||||
sendOurOwner(); // broadcast our new attempt at a node number
|
||||
}
|
||||
}
|
||||
else if (wasBroadcast)
|
||||
{
|
||||
// If we haven't yet abandoned the packet and it was a broadcast, reply (just to them) with our User record so they can build their DB
|
||||
|
||||
// Someone just sent us a User, reply with our Owner
|
||||
DEBUG_MSG("Received broadcast Owner from 0x%x, replying with our owner\n", mp->from);
|
||||
|
||||
sendOurOwner(mp->from);
|
||||
|
||||
String lcd = String("Joined: ") + mp->payload.variant.user.long_name + "\n";
|
||||
screen_print(lcd.c_str());
|
||||
}
|
||||
|
||||
return mp;
|
||||
}
|
||||
|
||||
void MeshService::handleIncomingPosition(MeshPacket *mp)
|
||||
{
|
||||
if (mp->has_payload && mp->payload.which_variant == SubPacket_position_tag)
|
||||
{
|
||||
DEBUG_MSG("handled incoming position time=%u\n", mp->payload.variant.position.time);
|
||||
|
||||
if (mp->payload.variant.position.time)
|
||||
{
|
||||
struct timeval tv;
|
||||
uint32_t secs = mp->payload.variant.position.time;
|
||||
|
||||
tv.tv_sec = secs;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
gps.perhapsSetRTC(&tv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MeshService::handleFromRadio(MeshPacket *mp)
|
||||
{
|
||||
mp->rx_time = gps.getValidTime(); // store the arrival timestamp for the phone
|
||||
|
||||
// If it is a position packet, perhaps set our clock (if we don't have a GPS of our own, otherwise wait for that to work)
|
||||
if(!myNodeInfo.has_gps)
|
||||
handleIncomingPosition(mp);
|
||||
|
||||
if (mp->has_payload && mp->payload.which_variant == SubPacket_user_tag)
|
||||
{
|
||||
mp = handleFromRadioUser(mp);
|
||||
}
|
||||
|
||||
// If we veto a received User packet, we don't put it into the DB or forward it to the phone (to prevent confusing it)
|
||||
if (mp)
|
||||
{
|
||||
DEBUG_MSG("Forwarding to phone, from=0x%x, rx_time=%u\n", mp->from, mp->rx_time);
|
||||
nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
|
||||
|
||||
fromNum++;
|
||||
|
||||
if (toPhoneQueue.numFree() == 0)
|
||||
{
|
||||
DEBUG_MSG("NOTE: tophone queue is full, discarding oldest\n");
|
||||
MeshPacket *d = toPhoneQueue.dequeuePtr(0);
|
||||
if (d)
|
||||
releaseToPool(d);
|
||||
}
|
||||
assert(toPhoneQueue.enqueue(mp, 0) == pdTRUE); // FIXME, instead of failing for full queue, delete the oldest mssages
|
||||
|
||||
if (mp->payload.want_response)
|
||||
sendNetworkPing(mp->from);
|
||||
}
|
||||
else
|
||||
DEBUG_MSG("Dropping vetoed User message\n");
|
||||
}
|
||||
|
||||
void MeshService::handleFromRadio()
|
||||
{
|
||||
MeshPacket *mp;
|
||||
uint32_t oldFromNum = fromNum;
|
||||
while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL)
|
||||
{
|
||||
handleFromRadio(mp);
|
||||
}
|
||||
if (oldFromNum != fromNum) // We don't want to generate extra notifies for multiple new packets
|
||||
bluetoothNotifyFromNum(fromNum);
|
||||
}
|
||||
|
||||
uint32_t sendOwnerCb()
|
||||
{
|
||||
service.sendOurOwner();
|
||||
|
||||
return radioConfig.preferences.send_owner_interval * radioConfig.preferences.position_broadcast_secs * 1000;
|
||||
}
|
||||
|
||||
Periodic sendOwnerPeriod(sendOwnerCb);
|
||||
|
||||
/// Do idle processing (mostly processing messages which have been queued from the radio)
|
||||
void MeshService::loop()
|
||||
{
|
||||
radio.loop(); // FIXME, possibly move radio interaction to own thread
|
||||
|
||||
handleFromRadio();
|
||||
|
||||
// occasionally send our owner info
|
||||
sendOwnerPeriod.loop();
|
||||
}
|
||||
|
||||
/// The radioConfig object just changed, call this to force the hw to change to the new settings
|
||||
void MeshService::reloadConfig()
|
||||
{
|
||||
// If we can successfully set this radio to these settings, save them to disk
|
||||
radio.reloadConfig();
|
||||
nodeDB.saveToDisk();
|
||||
}
|
||||
|
||||
/// Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh)
|
||||
void MeshService::handleToRadio(std::string s)
|
||||
{
|
||||
static ToRadio r; // this is a static scratch object, any data must be copied elsewhere before returning
|
||||
|
||||
if (pb_decode_from_bytes((const uint8_t *)s.c_str(), s.length(), ToRadio_fields, &r))
|
||||
{
|
||||
switch (r.which_variant)
|
||||
{
|
||||
case ToRadio_packet_tag:
|
||||
{
|
||||
// If our phone is sending a position, see if we can use it to set our RTC
|
||||
handleIncomingPosition(&r.variant.packet); // If it is a position packet, perhaps set our clock
|
||||
|
||||
r.variant.packet.rx_time = gps.getValidTime(); // Record the time the packet arrived from the phone (so we update our nodedb for the local node)
|
||||
|
||||
// Send the packet into the mesh
|
||||
sendToMesh(packetPool.allocCopy(r.variant.packet));
|
||||
|
||||
bool loopback = false; // if true send any packet the phone sends back itself (for testing)
|
||||
if (loopback)
|
||||
{
|
||||
MeshPacket *mp = packetPool.allocCopy(r.variant.packet);
|
||||
handleFromRadio(mp);
|
||||
bluetoothNotifyFromNum(fromNum); // tell the phone a new packet arrived
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
DEBUG_MSG("Error: unexpected ToRadio variant\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MeshService::sendToMesh(MeshPacket *p)
|
||||
{
|
||||
nodeDB.updateFrom(*p); // update our local DB for this packet (because phone might have sent position packets etc...)
|
||||
|
||||
// Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other nodes shouldn't trust it anyways)
|
||||
// Note: for now, we allow a device with a local GPS to include the time, so that gpsless devices can get time.
|
||||
if (p->has_payload && p->payload.which_variant == SubPacket_position_tag)
|
||||
{
|
||||
if (!myNodeInfo.has_gps)
|
||||
p->payload.variant.position.time = 0;
|
||||
else
|
||||
DEBUG_MSG("Providing time to mesh %u\n", p->payload.variant.position.time);
|
||||
}
|
||||
|
||||
// If the phone sent a packet just to us, don't send it out into the network
|
||||
if (p->to == nodeDB.getNodeNum())
|
||||
DEBUG_MSG("Dropping locally processed message\n");
|
||||
else
|
||||
{
|
||||
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
|
||||
if (radio.send(p) != ERRNO_OK)
|
||||
DEBUG_MSG("Dropped packet because send queue was full!\n");
|
||||
}
|
||||
}
|
||||
|
||||
MeshPacket *MeshService::allocForSending()
|
||||
{
|
||||
MeshPacket *p = packetPool.allocZeroed();
|
||||
|
||||
p->has_payload = true;
|
||||
p->from = nodeDB.getNodeNum();
|
||||
p->to = NODENUM_BROADCAST;
|
||||
p->rx_time = gps.getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void MeshService::sendNetworkPing(NodeNum dest)
|
||||
{
|
||||
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
|
||||
assert(node);
|
||||
|
||||
if (node->has_position)
|
||||
sendOurPosition(dest);
|
||||
else
|
||||
sendOurOwner(dest);
|
||||
}
|
||||
|
||||
void MeshService::sendOurPosition(NodeNum dest)
|
||||
{
|
||||
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
|
||||
assert(node);
|
||||
assert(node->has_position);
|
||||
|
||||
// Update our local node info with our position (even if we don't decide to update anyone else)
|
||||
MeshPacket *p = allocForSending();
|
||||
p->to = dest;
|
||||
p->payload.which_variant = SubPacket_position_tag;
|
||||
p->payload.variant.position = node->position;
|
||||
p->payload.variant.position.time = gps.getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid.
|
||||
sendToMesh(p);
|
||||
}
|
||||
|
||||
void MeshService::onGPSChanged()
|
||||
{
|
||||
// Update our local node info with our position (even if we don't decide to update anyone else)
|
||||
MeshPacket *p = allocForSending();
|
||||
p->payload.which_variant = SubPacket_position_tag;
|
||||
Position &pos = p->payload.variant.position;
|
||||
if (gps.altitude.isValid())
|
||||
pos.altitude = gps.altitude.meters();
|
||||
pos.latitude = gps.location.lat();
|
||||
pos.longitude = gps.location.lng();
|
||||
pos.time = gps.getValidTime();
|
||||
|
||||
// We limit our GPS broadcasts to a max rate
|
||||
static uint32_t lastGpsSend;
|
||||
uint32_t now = millis();
|
||||
if (lastGpsSend == 0 || now - lastGpsSend > radioConfig.preferences.position_broadcast_secs * 1000)
|
||||
{
|
||||
lastGpsSend = now;
|
||||
DEBUG_MSG("Sending position to mesh\n");
|
||||
|
||||
sendToMesh(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We don't need to send this packet to anyone else, but it still serves as a nice uniform way to update our local state
|
||||
nodeDB.updateFrom(*p);
|
||||
|
||||
releaseToPool(p);
|
||||
}
|
||||
}
|
||||
|
||||
void MeshService::onNotify(Observable *o)
|
||||
{
|
||||
DEBUG_MSG("got gps notify\n");
|
||||
onGPSChanged();
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// low level types
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
typedef uint8_t NodeNum;
|
||||
|
||||
#define NODENUM_BROADCAST 255
|
||||
#define ERRNO_OK 0
|
||||
#define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER
|
||||
|
||||
typedef int ErrorCode;
|
||||
340
src/NodeDB.cpp
340
src/NodeDB.cpp
@@ -1,340 +0,0 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "FS.h"
|
||||
#include "SPIFFS.h"
|
||||
|
||||
#include <pb_encode.h>
|
||||
#include <pb_decode.h>
|
||||
#include "configuration.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "NodeDB.h"
|
||||
#include "GPS.h"
|
||||
#include "PowerFSM.h"
|
||||
|
||||
NodeDB nodeDB;
|
||||
|
||||
// we have plenty of ram so statically alloc this tempbuf (for now)
|
||||
DeviceState devicestate;
|
||||
MyNodeInfo &myNodeInfo = devicestate.my_node;
|
||||
RadioConfig &radioConfig = devicestate.radio;
|
||||
ChannelSettings &channelSettings = radioConfig.channel_settings;
|
||||
|
||||
#define FS SPIFFS
|
||||
|
||||
/**
|
||||
*
|
||||
* Normally userids are unique and start with +country code to look like Signal phone numbers.
|
||||
* But there are some special ids used when we haven't yet been configured by a user. In that case
|
||||
* we use !macaddr (no colons).
|
||||
*/
|
||||
User &owner = devicestate.owner;
|
||||
|
||||
static uint8_t ourMacAddr[6];
|
||||
|
||||
NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_count)
|
||||
{
|
||||
}
|
||||
|
||||
void NodeDB::init()
|
||||
{
|
||||
|
||||
// init our devicestate with valid flags so protobuf writing/reading will work
|
||||
devicestate.has_my_node = true;
|
||||
devicestate.has_radio = true;
|
||||
devicestate.has_owner = true;
|
||||
devicestate.has_radio = true;
|
||||
devicestate.radio.has_channel_settings = true;
|
||||
devicestate.radio.has_preferences = true;
|
||||
devicestate.node_db_count = 0;
|
||||
devicestate.receive_queue_count = 0;
|
||||
|
||||
radioConfig.preferences.send_owner_interval = 4; // per sw-design.md
|
||||
radioConfig.preferences.position_broadcast_secs = 15 * 60;
|
||||
radioConfig.preferences.wait_bluetooth_secs = 120;
|
||||
radioConfig.preferences.screen_on_secs = 30;
|
||||
radioConfig.preferences.mesh_sds_timeout_secs = 60 * 60;
|
||||
radioConfig.preferences.phone_sds_timeout_sec = 60 * 60;
|
||||
radioConfig.preferences.sds_secs = 60 * 60;
|
||||
radioConfig.preferences.ls_secs = 60 * 60;
|
||||
radioConfig.preferences.phone_timeout_secs = 15 * 60;
|
||||
|
||||
#ifdef GPS_RX_PIN
|
||||
// some hardware defaults to have a built in GPS
|
||||
myNodeInfo.has_gps = true;
|
||||
#endif
|
||||
|
||||
// Init our blank owner info to reasonable defaults
|
||||
esp_efuse_mac_get_default(ourMacAddr);
|
||||
sprintf(owner.id, "!%02x%02x%02x%02x%02x%02x", ourMacAddr[0],
|
||||
ourMacAddr[1], ourMacAddr[2], ourMacAddr[3], ourMacAddr[4], ourMacAddr[5]);
|
||||
memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr));
|
||||
|
||||
// make each node start with ad different random seed (but okay that the sequence is the same each boot)
|
||||
randomSeed((ourMacAddr[2] << 24L) | (ourMacAddr[3] << 16L) | (ourMacAddr[4] << 8L) | ourMacAddr[5]);
|
||||
|
||||
sprintf(owner.long_name, "Unknown %02x%02x", ourMacAddr[4], ourMacAddr[5]);
|
||||
sprintf(owner.short_name, "?%02X", ourMacAddr[5]);
|
||||
|
||||
// Crummy guess at our nodenum
|
||||
pickNewNodeNum();
|
||||
|
||||
// Include our owner in the node db under our nodenum
|
||||
NodeInfo *info = getOrCreateNode(getNodeNum());
|
||||
info->user = owner;
|
||||
info->has_user = true;
|
||||
|
||||
if (!FS.begin(true)) // FIXME - do this in main?
|
||||
{
|
||||
DEBUG_MSG("ERROR SPIFFS Mount Failed\n");
|
||||
// FIXME - report failure to phone
|
||||
}
|
||||
|
||||
// saveToDisk();
|
||||
loadFromDisk();
|
||||
|
||||
DEBUG_MSG("NODENUM=0x%x, dbsize=%d\n", myNodeInfo.my_node_num, *numNodes);
|
||||
}
|
||||
|
||||
// We reserve a few nodenums for future use
|
||||
#define NUM_RESERVED 4
|
||||
|
||||
/**
|
||||
* get our starting (provisional) nodenum from flash.
|
||||
*/
|
||||
void NodeDB::pickNewNodeNum()
|
||||
{
|
||||
// FIXME not the right way to guess node numes
|
||||
uint8_t r = ourMacAddr[5];
|
||||
if (r == 0xff || r < NUM_RESERVED)
|
||||
r = NUM_RESERVED; // don't pick a reserved node number
|
||||
|
||||
NodeInfo *found;
|
||||
while ((found = getNode(r)) && memcmp(found->user.macaddr, owner.macaddr, sizeof(owner.macaddr)))
|
||||
{
|
||||
NodeNum n = random(NUM_RESERVED, NODENUM_BROADCAST); // try a new random choice
|
||||
DEBUG_MSG("NOTE! Our desired nodenum 0x%x is in use, so trying for 0x%x\n", r, n);
|
||||
r = n;
|
||||
}
|
||||
|
||||
myNodeInfo.my_node_num = r;
|
||||
}
|
||||
|
||||
const char *preffile = "/db.proto";
|
||||
const char *preftmp = "/db.proto.tmp";
|
||||
|
||||
void NodeDB::loadFromDisk()
|
||||
{
|
||||
static DeviceState scratch;
|
||||
|
||||
File f = FS.open(preffile);
|
||||
if (f)
|
||||
{
|
||||
DEBUG_MSG("Loading saved preferences\n");
|
||||
pb_istream_t stream = {&readcb, &f, DeviceState_size};
|
||||
|
||||
//DEBUG_MSG("Preload channel name=%s\n", channelSettings.name);
|
||||
|
||||
memset(&scratch, 0, sizeof(scratch));
|
||||
if (!pb_decode(&stream, DeviceState_fields, &scratch))
|
||||
{
|
||||
DEBUG_MSG("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream));
|
||||
// FIXME - report failure to phone
|
||||
}
|
||||
else
|
||||
{
|
||||
if (scratch.version < DeviceState_Version_Minimum)
|
||||
DEBUG_MSG("Warn: devicestate is old, discarding\n");
|
||||
else
|
||||
{
|
||||
devicestate = scratch;
|
||||
}
|
||||
|
||||
//DEBUG_MSG("Postload channel name=%s\n", channelSettings.name);
|
||||
}
|
||||
|
||||
f.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_MSG("No saved preferences found\n");
|
||||
}
|
||||
}
|
||||
|
||||
void NodeDB::saveToDisk()
|
||||
{
|
||||
File f = FS.open(preftmp, "w");
|
||||
if (f)
|
||||
{
|
||||
DEBUG_MSG("Writing preferences\n");
|
||||
|
||||
pb_ostream_t stream = {&writecb, &f, SIZE_MAX, 0};
|
||||
|
||||
//DEBUG_MSG("Presave channel name=%s\n", channelSettings.name);
|
||||
|
||||
devicestate.version = DeviceState_Version_Current;
|
||||
if (!pb_encode(&stream, DeviceState_fields, &devicestate))
|
||||
{
|
||||
DEBUG_MSG("Error: can't write protobuf %s\n", PB_GET_ERROR(&stream));
|
||||
// FIXME - report failure to phone
|
||||
}
|
||||
|
||||
f.close();
|
||||
|
||||
// brief window of risk here ;-)
|
||||
if (!FS.remove(preffile))
|
||||
DEBUG_MSG("Warning: Can't remove old pref file\n");
|
||||
if (!FS.rename(preftmp, preffile))
|
||||
DEBUG_MSG("Error: can't rename new pref file\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_MSG("ERROR: can't write prefs\n"); // FIXME report to app
|
||||
}
|
||||
}
|
||||
|
||||
const NodeInfo *NodeDB::readNextInfo()
|
||||
{
|
||||
if (readPointer < *numNodes)
|
||||
return &nodes[readPointer++];
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/// Given a node, return how many seconds in the past (vs now) that we last heard from it
|
||||
uint32_t sinceLastSeen(const NodeInfo *n)
|
||||
{
|
||||
uint32_t now = gps.getTime();
|
||||
|
||||
uint32_t last_seen = n->position.time;
|
||||
int delta = (int)(now - last_seen);
|
||||
if (delta < 0) // our clock must be slightly off still - not set from GPS yet
|
||||
delta = 0;
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
#define NUM_ONLINE_SECS (60 * 2) // 2 hrs to consider someone offline
|
||||
|
||||
size_t NodeDB::getNumOnlineNodes()
|
||||
{
|
||||
size_t numseen = 0;
|
||||
|
||||
// FIXME this implementation is kinda expensive
|
||||
for (int i = 0; i < *numNodes; i++)
|
||||
if (sinceLastSeen(&nodes[i]) < NUM_ONLINE_SECS)
|
||||
numseen++;
|
||||
|
||||
return numseen;
|
||||
}
|
||||
|
||||
/// given a subpacket sniffed from the network, update our DB state
|
||||
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
|
||||
void NodeDB::updateFrom(const MeshPacket &mp)
|
||||
{
|
||||
if (mp.has_payload)
|
||||
{
|
||||
const SubPacket &p = mp.payload;
|
||||
DEBUG_MSG("Update DB node 0x%x for variant %d, rx_time=%u\n", mp.from, p.which_variant, mp.rx_time);
|
||||
|
||||
int oldNumNodes = *numNodes;
|
||||
NodeInfo *info = getOrCreateNode(mp.from);
|
||||
|
||||
if (oldNumNodes != *numNodes)
|
||||
updateGUI = true; // we just created a nodeinfo
|
||||
|
||||
if (mp.rx_time)
|
||||
{ // if the packet has a valid timestamp use it to update our last_seen
|
||||
info->has_position = true; // at least the time is valid
|
||||
info->position.time = mp.rx_time;
|
||||
}
|
||||
|
||||
switch (p.which_variant)
|
||||
{
|
||||
case SubPacket_position_tag:
|
||||
{
|
||||
// we carefully preserve the old time, because we always trust our local timestamps more
|
||||
uint32_t oldtime = info->position.time;
|
||||
info->position = p.variant.position;
|
||||
info->position.time = oldtime;
|
||||
info->has_position = true;
|
||||
updateGUIforNode = info;
|
||||
break;
|
||||
}
|
||||
|
||||
case SubPacket_data_tag:
|
||||
{
|
||||
// Keep a copy of the most recent text message.
|
||||
if (p.variant.data.typ == Data_Type_CLEAR_TEXT)
|
||||
{
|
||||
DEBUG_MSG("Received text msg from=0%0x, msg=%.*s\n", mp.from, p.variant.data.payload.size, p.variant.data.payload.bytes);
|
||||
if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum())
|
||||
{
|
||||
// We only store/display messages destined for us.
|
||||
devicestate.rx_text_message = mp;
|
||||
devicestate.has_rx_text_message = true;
|
||||
updateTextMessage = true;
|
||||
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SubPacket_user_tag:
|
||||
{
|
||||
DEBUG_MSG("old user %s/%s/%s\n", info->user.id, info->user.long_name, info->user.short_name);
|
||||
|
||||
bool changed = memcmp(&info->user, &p.variant.user, sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay
|
||||
|
||||
info->user = p.variant.user;
|
||||
DEBUG_MSG("updating changed=%d user %s/%s/%s\n", changed, info->user.id, info->user.long_name, info->user.short_name);
|
||||
info->has_user = true;
|
||||
|
||||
if (changed)
|
||||
{
|
||||
updateGUIforNode = info;
|
||||
powerFSM.trigger(EVENT_NODEDB_UPDATED);
|
||||
|
||||
// Not really needed - we will save anyways when we go to sleep
|
||||
// We just changed something important about the user, store our DB
|
||||
// saveToDisk();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break; // Ignore other packet types
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Find a node in our DB, return null for missing
|
||||
/// NOTE: This function might be called from an ISR
|
||||
NodeInfo *NodeDB::getNode(NodeNum n)
|
||||
{
|
||||
for (int i = 0; i < *numNodes; i++)
|
||||
if (nodes[i].num == n)
|
||||
return &nodes[i];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/// Find a node in our DB, create an empty NodeInfo if missing
|
||||
NodeInfo *NodeDB::getOrCreateNode(NodeNum n)
|
||||
{
|
||||
NodeInfo *info = getNode(n);
|
||||
|
||||
if (!info)
|
||||
{
|
||||
// add the node
|
||||
assert(*numNodes < MAX_NUM_NODES);
|
||||
info = &nodes[(*numNodes)++];
|
||||
|
||||
// everything is missing except the nodenum
|
||||
memset(info, 0, sizeof(*info));
|
||||
info->num = n;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
49
src/OSTimer.cpp
Normal file
49
src/OSTimer.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "OSTimer.h"
|
||||
#include "configuration.h"
|
||||
|
||||
/**
|
||||
* Schedule a callback to run. The callback must _not_ block, though it is called from regular thread level (not ISR)
|
||||
*
|
||||
* NOTE! xTimerPend... seems to ignore the time passed in on ESP32 and on NRF52
|
||||
* The reason this didn't work is bcause xTimerPednFunctCall really isn't a timer function at all - it just means run the callback
|
||||
* from the timer thread the next time you have spare cycles.
|
||||
*
|
||||
* @return true if successful, false if the timer fifo is too full.
|
||||
|
||||
bool scheduleOSCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec)
|
||||
{
|
||||
return xTimerPendFunctionCall(callback, param1, param2, pdMS_TO_TICKS(delayMsec));
|
||||
} */
|
||||
|
||||
#ifndef NO_ESP32
|
||||
|
||||
// Super skanky quick hack to use hardware timers of the ESP32
|
||||
static hw_timer_t *timer;
|
||||
static PendableFunction tCallback;
|
||||
static void *tParam1;
|
||||
static uint32_t tParam2;
|
||||
|
||||
static void IRAM_ATTR onTimer()
|
||||
{
|
||||
(*tCallback)(tParam1, tParam2);
|
||||
}
|
||||
|
||||
bool scheduleHWCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec)
|
||||
{
|
||||
if (!timer) {
|
||||
timer = timerBegin(0, 80, true); // one usec per tick (main clock is 80MhZ on ESP32)
|
||||
assert(timer);
|
||||
timerAttachInterrupt(timer, &onTimer, true);
|
||||
}
|
||||
|
||||
tCallback = callback;
|
||||
tParam1 = param1;
|
||||
tParam2 = param2;
|
||||
|
||||
timerAlarmWrite(timer, delayMsec * 1000L, false); // Do not reload, we want it to be a single shot timer
|
||||
timerRestart(timer);
|
||||
timerAlarmEnable(timer);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
8
src/OSTimer.h
Normal file
8
src/OSTimer.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
typedef void (*PendableFunction)(void *pvParameter1, uint32_t ulParameter2);
|
||||
|
||||
/// Uses a hardware timer, but calls the handler in _interrupt_ context
|
||||
bool scheduleHWCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec);
|
||||
@@ -1,13 +1,2 @@
|
||||
#include "Observer.h"
|
||||
|
||||
Observer::~Observer()
|
||||
{
|
||||
if (observed)
|
||||
observed->removeObserver(this);
|
||||
observed = NULL;
|
||||
}
|
||||
|
||||
void Observer::observe(Observable *o)
|
||||
{
|
||||
o->addObserver(this);
|
||||
}
|
||||
107
src/Observer.h
107
src/Observer.h
@@ -1,49 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <list>
|
||||
|
||||
class Observable;
|
||||
template <class T> class Observable;
|
||||
|
||||
class Observer
|
||||
/**
|
||||
* An observer which can be mixed in as a baseclass. Implement onNotify as a method in your class.
|
||||
*/
|
||||
template <class T> class Observer
|
||||
{
|
||||
Observable *observed;
|
||||
|
||||
public:
|
||||
Observer() : observed(NULL) {}
|
||||
Observable<T> *observed = NULL;
|
||||
|
||||
public:
|
||||
virtual ~Observer();
|
||||
|
||||
void observe(Observable *o);
|
||||
void observe(Observable<T> *o);
|
||||
|
||||
private:
|
||||
friend class Observable;
|
||||
private:
|
||||
friend class Observable<T>;
|
||||
|
||||
virtual void onNotify(Observable *o) = 0;
|
||||
protected:
|
||||
/**
|
||||
* returns 0 if other observers should continue to be called
|
||||
* returns !0 if the observe calls should be aborted and this result code returned for notifyObservers
|
||||
**/
|
||||
virtual int onNotify(T arg) = 0;
|
||||
};
|
||||
|
||||
class Observable
|
||||
/**
|
||||
* An observer that calls an arbitrary method
|
||||
*/
|
||||
template <class Callback, class T> class CallbackObserver : public Observer<T>
|
||||
{
|
||||
std::list<Observer *> observers;
|
||||
typedef int (Callback::*ObserverCallback)(T arg);
|
||||
|
||||
public:
|
||||
void notifyObservers()
|
||||
{
|
||||
for (std::list<Observer *>::const_iterator iterator = observers.begin(); iterator != observers.end(); ++iterator)
|
||||
{
|
||||
(*iterator)->onNotify(this);
|
||||
}
|
||||
}
|
||||
Callback *objPtr;
|
||||
ObserverCallback method;
|
||||
|
||||
void addObserver(Observer *o)
|
||||
{
|
||||
observers.push_back(o);
|
||||
}
|
||||
public:
|
||||
CallbackObserver(Callback *_objPtr, ObserverCallback _method) : objPtr(_objPtr), method(_method) {}
|
||||
|
||||
void removeObserver(Observer *o)
|
||||
{
|
||||
observers.remove(o);
|
||||
}
|
||||
protected:
|
||||
virtual int onNotify(T arg) { return (objPtr->*method)(arg); }
|
||||
};
|
||||
|
||||
/**
|
||||
* An observable class that will notify observers anytime notifyObservers is called. Argument type T can be any type, but for
|
||||
* performance reasons a pointer or word sized object is recommended.
|
||||
*/
|
||||
template <class T> class Observable
|
||||
{
|
||||
std::list<Observer<T> *> observers;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Tell all observers about a change, observers can process arg as they wish
|
||||
*
|
||||
* returns !0 if an observer chose to abort processing by returning this code
|
||||
*/
|
||||
int notifyObservers(T arg)
|
||||
{
|
||||
for (typename std::list<Observer<T> *>::const_iterator iterator = observers.begin(); iterator != observers.end();
|
||||
++iterator) {
|
||||
int result = (*iterator)->onNotify(arg);
|
||||
if (result != 0)
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Observer<T>;
|
||||
|
||||
// Not called directly, instead call observer.observe
|
||||
void addObserver(Observer<T> *o) { observers.push_back(o); }
|
||||
|
||||
void removeObserver(Observer<T> *o) { observers.remove(o); }
|
||||
};
|
||||
|
||||
template <class T> Observer<T>::~Observer()
|
||||
{
|
||||
if (observed)
|
||||
observed->removeObserver(this);
|
||||
observed = NULL;
|
||||
}
|
||||
|
||||
template <class T> void Observer<T>::observe(Observable<T> *o)
|
||||
{
|
||||
// We can only watch one thing at a time
|
||||
assert(!observed);
|
||||
|
||||
observed = o;
|
||||
o->addObserver(this);
|
||||
}
|
||||
@@ -1,22 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "PeriodicTask.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
/**
|
||||
* Periodically invoke a callback.
|
||||
*
|
||||
*
|
||||
* This just provides C style callback conventions rather than a virtual function - FIXME, remove?
|
||||
*/
|
||||
class Periodic : public PeriodicTask
|
||||
{
|
||||
uint32_t (*callback)();
|
||||
uint32_t (*callback)();
|
||||
|
||||
public:
|
||||
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
|
||||
Periodic(uint32_t (*_callback)()) : callback(_callback) {}
|
||||
public:
|
||||
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
|
||||
Periodic(uint32_t (*_callback)()) : callback(_callback) {}
|
||||
|
||||
protected:
|
||||
|
||||
void doTask();
|
||||
protected:
|
||||
void doTask();
|
||||
};
|
||||
|
||||
@@ -1,23 +1,43 @@
|
||||
#include "PeriodicTask.h"
|
||||
#include "Periodic.h"
|
||||
PeriodicScheduler periodicScheduler;
|
||||
|
||||
PeriodicTask::PeriodicTask(uint32_t initialPeriod) : period(initialPeriod)
|
||||
PeriodicTask::PeriodicTask(uint32_t initialPeriod) : period(initialPeriod) {}
|
||||
|
||||
void PeriodicTask::setup()
|
||||
{
|
||||
periodicScheduler.schedule(this);
|
||||
}
|
||||
|
||||
/// call this from loop
|
||||
void PeriodicTask::loop()
|
||||
void PeriodicScheduler::loop()
|
||||
{
|
||||
meshtastic::LockGuard lg(&lock);
|
||||
|
||||
uint32_t now = millis();
|
||||
if (period && (now - lastMsec) >= period)
|
||||
{
|
||||
lastMsec = now;
|
||||
doTask();
|
||||
for (auto t : tasks) {
|
||||
if (t->period && (now - t->lastMsec) >= t->period) {
|
||||
|
||||
t->doTask();
|
||||
t->lastMsec = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PeriodicScheduler::schedule(PeriodicTask *t)
|
||||
{
|
||||
meshtastic::LockGuard lg(&lock);
|
||||
tasks.insert(t);
|
||||
}
|
||||
|
||||
void PeriodicScheduler::unschedule(PeriodicTask *t)
|
||||
{
|
||||
meshtastic::LockGuard lg(&lock);
|
||||
tasks.erase(t);
|
||||
}
|
||||
|
||||
void Periodic::doTask()
|
||||
{
|
||||
uint32_t p = callback();
|
||||
setPeriod(p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include "lock.h"
|
||||
#include <Arduino.h>
|
||||
#include "configuration.h"
|
||||
#include <cstdint>
|
||||
#include <unordered_set>
|
||||
|
||||
class PeriodicTask;
|
||||
|
||||
/**
|
||||
* Runs all PeriodicTasks in the system.
|
||||
*
|
||||
* Currently called from main loop() but eventually should be its own thread blocked on a freertos timer.
|
||||
*/
|
||||
class PeriodicScheduler
|
||||
{
|
||||
friend class PeriodicTask;
|
||||
|
||||
/**
|
||||
* This really should be some form of heap, and when the period gets changed on a task it should get
|
||||
* rescheduled in that heap. Currently it is just a dumb array and everytime we run loop() we check
|
||||
* _every_ tasks. If it was a heap we'd only have to check the first task.
|
||||
*/
|
||||
std::unordered_set<PeriodicTask *> tasks;
|
||||
|
||||
// Protects the above variables.
|
||||
meshtastic::Lock lock;
|
||||
|
||||
public:
|
||||
/// Run any next tasks which are due for execution
|
||||
void loop();
|
||||
|
||||
private:
|
||||
void schedule(PeriodicTask *t);
|
||||
void unschedule(PeriodicTask *t);
|
||||
};
|
||||
|
||||
extern PeriodicScheduler periodicScheduler;
|
||||
|
||||
/**
|
||||
* A base class for tasks that want their doTask() method invoked periodically
|
||||
*
|
||||
*
|
||||
* FIXME: currently just syntatic sugar for polling in loop (you must call .loop), but eventually
|
||||
* generalize with the freertos scheduler so we can save lots of power by having everything either in
|
||||
* something like this or triggered off of an irq.
|
||||
*/
|
||||
class PeriodicTask
|
||||
{
|
||||
friend class PeriodicScheduler;
|
||||
|
||||
uint32_t lastMsec = 0;
|
||||
uint32_t period = 1; // call soon after creation
|
||||
|
||||
public:
|
||||
uint32_t periodMsec;
|
||||
|
||||
virtual ~PeriodicTask() {}
|
||||
public:
|
||||
virtual ~PeriodicTask() { periodicScheduler.unschedule(this); }
|
||||
|
||||
/**
|
||||
* Constructor (will schedule with the global PeriodicScheduler)
|
||||
*/
|
||||
PeriodicTask(uint32_t initialPeriod = 1);
|
||||
|
||||
/// call this from loop
|
||||
virtual void loop();
|
||||
/** MUST be be called once at startup (but after threading is running - i.e. not from a constructor)
|
||||
*/
|
||||
void setup();
|
||||
|
||||
/// Set a new period in msecs (can be called from doTask or elsewhere and the scheduler will cope)
|
||||
void setPeriod(uint32_t p) { period = p; }
|
||||
/**
|
||||
* 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 = millis(); // reset starting from now
|
||||
period = p;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t getPeriod() const { return period; }
|
||||
|
||||
/**
|
||||
* Syntatic sugar for suspending tasks
|
||||
*/
|
||||
void disable() { setPeriod(0); }
|
||||
|
||||
protected:
|
||||
virtual void doTask() = 0;
|
||||
};
|
||||
|
||||
168
src/PowerFSM.cpp
168
src/PowerFSM.cpp
@@ -1,12 +1,13 @@
|
||||
|
||||
#include "sleep.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "GPS.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "configuration.h"
|
||||
#include "screen.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "GPS.h"
|
||||
#include "main.h"
|
||||
#include "screen.h"
|
||||
#include "sleep.h"
|
||||
#include "target_specific.h"
|
||||
|
||||
static void sdsEnter()
|
||||
{
|
||||
@@ -23,53 +24,88 @@ static void sdsEnter()
|
||||
doDeepSleep(radioConfig.preferences.sds_secs * 1000LL);
|
||||
}
|
||||
|
||||
#include "error.h"
|
||||
|
||||
static uint32_t secsSlept;
|
||||
|
||||
static void lsEnter()
|
||||
{
|
||||
while (!service.radio.rf95.canSleep())
|
||||
delay(10); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives)
|
||||
DEBUG_MSG("lsEnter begin, ls_secs=%u\n", radioConfig.preferences.ls_secs);
|
||||
screen.setOn(false);
|
||||
secsSlept = 0; // How long have we been sleeping this time
|
||||
|
||||
gps.prepareSleep(); // abandon in-process parsing
|
||||
|
||||
if(!isUSBPowered) // FIXME - temp hack until we can put gps in sleep mode, if we have AC when we go to sleep then leave GPS on
|
||||
setGPSPower(false); // kill GPS power
|
||||
DEBUG_MSG("lsEnter end\n");
|
||||
}
|
||||
|
||||
static void lsIdle()
|
||||
{
|
||||
uint32_t secsSlept = 0;
|
||||
// DEBUG_MSG("lsIdle begin ls_secs=%u\n", radioConfig.preferences.ls_secs);
|
||||
|
||||
#ifndef NO_ESP32
|
||||
esp_sleep_source_t wakeCause = ESP_SLEEP_WAKEUP_UNDEFINED;
|
||||
|
||||
while (secsSlept < radioConfig.preferences.ls_secs)
|
||||
{
|
||||
// Do we have more sleeping to do?
|
||||
if (secsSlept < radioConfig.preferences.ls_secs) {
|
||||
// Briefly come out of sleep long enough to blink the led once every few seconds
|
||||
uint32_t sleepTime = 5;
|
||||
uint32_t sleepTime = 30;
|
||||
|
||||
setLed(false); // Never leave led on while in light sleep
|
||||
wakeCause = doLightSleep(sleepTime * 1000LL);
|
||||
if (wakeCause != ESP_SLEEP_WAKEUP_TIMER)
|
||||
break;
|
||||
// If some other service would stall sleep, don't let sleep happen yet
|
||||
if (doPreflightSleep()) {
|
||||
setLed(false); // Never leave led on while in light sleep
|
||||
wakeCause = doLightSleep(sleepTime * 1000LL);
|
||||
|
||||
setLed(true); // briefly turn on led
|
||||
doLightSleep(1);
|
||||
if (wakeCause != ESP_SLEEP_WAKEUP_TIMER)
|
||||
break;
|
||||
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) {
|
||||
// Normal case: timer expired, we should just go back to sleep ASAP
|
||||
|
||||
secsSlept += sleepTime;
|
||||
setLed(true); // briefly turn on led
|
||||
wakeCause = doLightSleep(1); // leave led on for 1ms
|
||||
|
||||
secsSlept += sleepTime;
|
||||
// DEBUG_MSG("sleeping, flash led!\n");
|
||||
}
|
||||
if (wakeCause == 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)
|
||||
// uint64_t status = esp_sleep_get_ext1_wakeup_status();
|
||||
DEBUG_MSG("wakeCause %d\n", wakeCause);
|
||||
|
||||
#ifdef BUTTON_PIN
|
||||
bool pressed = !digitalRead(BUTTON_PIN);
|
||||
#else
|
||||
bool pressed = false;
|
||||
#endif
|
||||
if (pressed) // If we woke because of press, instead generate a PRESS event.
|
||||
{
|
||||
powerFSM.trigger(EVENT_PRESS);
|
||||
} else {
|
||||
// Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc)
|
||||
powerFSM.trigger(EVENT_WAKE_TIMER);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Someone says we can't sleep now, so just save some power by sleeping the CPU for 100ms or so
|
||||
delay(100);
|
||||
}
|
||||
} else {
|
||||
// Time to stop sleeping!
|
||||
setLed(false);
|
||||
DEBUG_MSG("reached ls_secs, servicing loop()\n");
|
||||
powerFSM.trigger(EVENT_WAKE_TIMER);
|
||||
}
|
||||
setLed(false);
|
||||
|
||||
// Regardless of why we woke (for now) just transition to NB (and that state will handle stuff like IRQs etc)
|
||||
powerFSM.trigger(EVENT_WAKE_TIMER);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void lsExit()
|
||||
{
|
||||
setGPSPower(true); // restore GPS power
|
||||
gps.startLock();
|
||||
// setGPSPower(true); // restore GPS power
|
||||
gps->startLock();
|
||||
}
|
||||
|
||||
static void nbEnter()
|
||||
{
|
||||
screen.setOn(false);
|
||||
setBluetoothEnable(false);
|
||||
|
||||
// FIXME - check if we already have packets for phone and immediately trigger EVENT_PACKETS_FOR_PHONE
|
||||
@@ -77,52 +113,75 @@ static void nbEnter()
|
||||
|
||||
static void darkEnter()
|
||||
{
|
||||
setBluetoothEnable(true);
|
||||
screen.setOn(false);
|
||||
}
|
||||
|
||||
static void serialEnter()
|
||||
{
|
||||
setBluetoothEnable(false);
|
||||
screen.setOn(true);
|
||||
}
|
||||
|
||||
static void onEnter()
|
||||
{
|
||||
screen.setOn(true);
|
||||
setBluetoothEnable(true);
|
||||
|
||||
static uint32_t lastPingMs;
|
||||
|
||||
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 onExit()
|
||||
{
|
||||
screen.setOn(false);
|
||||
}
|
||||
static void wakeForPing()
|
||||
{
|
||||
}
|
||||
static void wakeForPing() {}
|
||||
|
||||
static void screenPress()
|
||||
{
|
||||
screen.onPress();
|
||||
}
|
||||
|
||||
static void bootEnter() {}
|
||||
|
||||
State stateSDS(sdsEnter, NULL, NULL, "SDS");
|
||||
State stateLS(lsEnter, lsIdle, lsExit, "LS");
|
||||
State stateNB(nbEnter, NULL, NULL, "NB");
|
||||
State stateDARK(darkEnter, NULL, NULL, "DARK");
|
||||
State stateON(onEnter, NULL, onExit, "ON");
|
||||
Fsm powerFSM(&stateDARK);
|
||||
State stateSERIAL(serialEnter, NULL, NULL, "SERIAL");
|
||||
State stateBOOT(bootEnter, NULL, NULL, "BOOT");
|
||||
State stateON(onEnter, NULL, NULL, "ON");
|
||||
Fsm powerFSM(&stateBOOT);
|
||||
|
||||
void PowerFSM_setup()
|
||||
{
|
||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_BOOT, NULL, "Boot");
|
||||
powerFSM.add_timed_transition(&stateBOOT, &stateON, 3 * 1000, NULL, "boot timeout");
|
||||
|
||||
powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WAKE_TIMER, wakeForPing, "Wake timer");
|
||||
|
||||
// Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB and then it handles things
|
||||
// powerFSM.add_transition(&stateLS, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet");
|
||||
// Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB and then it
|
||||
// handles things powerFSM.add_transition(&stateLS, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet");
|
||||
|
||||
powerFSM.add_transition(&stateNB, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet, resetting win wake");
|
||||
|
||||
// Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB and then it handles things
|
||||
// powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL, "Press");
|
||||
|
||||
// Handle press events - note: we ignore button presses when in API mode
|
||||
powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL, "Press");
|
||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_PRESS, NULL, "Press");
|
||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_PRESS, NULL, "Press");
|
||||
powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, screenPress, "Press"); // reenter On to restart our timers
|
||||
|
||||
// Handle critically low power battery by forcing deep sleep
|
||||
powerFSM.add_transition(&stateBOOT, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat");
|
||||
powerFSM.add_transition(&stateLS, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat");
|
||||
powerFSM.add_transition(&stateNB, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat");
|
||||
powerFSM.add_transition(&stateDARK, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat");
|
||||
powerFSM.add_transition(&stateON, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat");
|
||||
powerFSM.add_transition(&stateSERIAL, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat");
|
||||
|
||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
|
||||
powerFSM.add_transition(&stateON, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
|
||||
|
||||
@@ -135,6 +194,13 @@ void PowerFSM_setup()
|
||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||
powerFSM.add_transition(&stateON, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); // restarts the sleep timer
|
||||
|
||||
powerFSM.add_transition(&stateLS, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||
powerFSM.add_transition(&stateNB, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||
powerFSM.add_transition(&stateDARK, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||
powerFSM.add_transition(&stateON, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||
|
||||
powerFSM.add_transition(&stateSERIAL, &stateNB, EVENT_SERIAL_DISCONNECTED, NULL, "serial disconnect");
|
||||
|
||||
powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone");
|
||||
|
||||
powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone");
|
||||
@@ -143,9 +209,19 @@ void PowerFSM_setup()
|
||||
|
||||
powerFSM.add_timed_transition(&stateDARK, &stateNB, radioConfig.preferences.phone_timeout_secs * 1000, NULL, "Phone timeout");
|
||||
|
||||
#ifndef NRF52_SERIES
|
||||
// We never enter light-sleep state on NRF52 (because the CPU uses so little power normally)
|
||||
powerFSM.add_timed_transition(&stateNB, &stateLS, radioConfig.preferences.min_wake_secs * 1000, NULL, "Min wake timeout");
|
||||
|
||||
powerFSM.add_timed_transition(&stateDARK, &stateLS, radioConfig.preferences.wait_bluetooth_secs * 1000, NULL, "Bluetooth timeout");
|
||||
powerFSM.add_timed_transition(&stateDARK, &stateLS, radioConfig.preferences.wait_bluetooth_secs * 1000, NULL,
|
||||
"Bluetooth timeout");
|
||||
#endif
|
||||
|
||||
powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.mesh_sds_timeout_secs * 1000, NULL,
|
||||
"mesh timeout");
|
||||
// removing for now, because some users don't even have phones
|
||||
// powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.phone_sds_timeout_sec * 1000, NULL, "phone
|
||||
// timeout");
|
||||
|
||||
powerFSM.run_machine(); // run one interation of the state machine, so we run our on enter tasks for the initial DARK state
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include "Fsm.h"
|
||||
#include <Fsm.h>
|
||||
|
||||
// See sw-design.md for documentation
|
||||
|
||||
@@ -9,11 +9,14 @@
|
||||
#define EVENT_RECEIVED_PACKET 3
|
||||
#define EVENT_PACKET_FOR_PHONE 4
|
||||
#define EVENT_RECEIVED_TEXT_MSG 5
|
||||
#define EVENT_BOOT 6
|
||||
// #define EVENT_BOOT 6 // now done with a timed transition
|
||||
#define EVENT_BLUETOOTH_PAIR 7
|
||||
#define EVENT_NODEDB_UPDATED 8 // NodeDB has a big enough change that we think you should turn on the screen
|
||||
#define EVENT_NODEDB_UPDATED 8 // NodeDB has a big enough change that we think you should turn on the screen
|
||||
#define EVENT_CONTACT_FROM_PHONE 9 // the phone just talked to us over bluetooth
|
||||
#define EVENT_LOW_BATTERY 10 // Battery is critically low, go to sleep
|
||||
#define EVENT_SERIAL_CONNECTED 11
|
||||
#define EVENT_SERIAL_DISCONNECTED 12
|
||||
|
||||
extern Fsm powerFSM;
|
||||
|
||||
void PowerFSM_setup();
|
||||
void PowerFSM_setup();
|
||||
|
||||
13
src/RedirectablePrint.cpp
Normal file
13
src/RedirectablePrint.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#include "RedirectablePrint.h"
|
||||
#include <assert.h>
|
||||
|
||||
/**
|
||||
* A printer that doesn't go anywhere
|
||||
*/
|
||||
NoopPrint noopPrint;
|
||||
|
||||
void RedirectablePrint::setDestination(Print *_dest)
|
||||
{
|
||||
assert(_dest);
|
||||
dest = _dest;
|
||||
}
|
||||
34
src/RedirectablePrint.h
Normal file
34
src/RedirectablePrint.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <Print.h>
|
||||
|
||||
/**
|
||||
* A Printable that can be switched to squirt its bytes to a different sink.
|
||||
* This class is mostly useful to allow debug printing to be redirected away from Serial
|
||||
* to some other transport if we switch Serial usage (on the fly) to some other purpose.
|
||||
*/
|
||||
class RedirectablePrint : public Print
|
||||
{
|
||||
Print *dest;
|
||||
|
||||
public:
|
||||
RedirectablePrint(Print *_dest) : dest(_dest) {}
|
||||
|
||||
/**
|
||||
* Set a new destination
|
||||
*/
|
||||
void setDestination(Print *dest);
|
||||
|
||||
virtual size_t write(uint8_t c) { return dest->write(c); }
|
||||
};
|
||||
|
||||
class NoopPrint : public Print
|
||||
{
|
||||
public:
|
||||
virtual size_t write(uint8_t c) { return 1; }
|
||||
};
|
||||
|
||||
/**
|
||||
* A printer that doesn't go anywhere
|
||||
*/
|
||||
extern NoopPrint noopPrint;
|
||||
45
src/SerialConsole.cpp
Normal file
45
src/SerialConsole.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "SerialConsole.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "configuration.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#define Port Serial
|
||||
|
||||
SerialConsole console;
|
||||
|
||||
SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port)
|
||||
{
|
||||
canWrite = false; // We don't send packets to our port until it has talked to us first
|
||||
// setDestination(&noopPrint); for testing, try turning off 'all' debug output and see what leaks
|
||||
}
|
||||
|
||||
/// Do late init that can't happen at constructor time
|
||||
void SerialConsole::init()
|
||||
{
|
||||
Port.begin(SERIAL_BAUD);
|
||||
StreamAPI::init();
|
||||
emitRebooted();
|
||||
}
|
||||
|
||||
/**
|
||||
* we override this to notice when we've received a protobuf over the serial
|
||||
* stream. Then we shunt off debug serial output.
|
||||
*/
|
||||
void SerialConsole::handleToRadio(const uint8_t *buf, size_t len)
|
||||
{
|
||||
// Turn off debug serial printing once the API is activated, because other threads could print and corrupt packets
|
||||
setDestination(&noopPrint);
|
||||
canWrite = true;
|
||||
|
||||
StreamAPI::handleToRadio(buf, len);
|
||||
}
|
||||
|
||||
/// Hookable to find out when connection changes
|
||||
void SerialConsole::onConnectionChanged(bool connected)
|
||||
{
|
||||
if (connected) { // To prevent user confusion, turn off bluetooth while using the serial port api
|
||||
powerFSM.trigger(EVENT_SERIAL_CONNECTED);
|
||||
} else {
|
||||
powerFSM.trigger(EVENT_SERIAL_DISCONNECTED);
|
||||
}
|
||||
}
|
||||
35
src/SerialConsole.h
Normal file
35
src/SerialConsole.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "RedirectablePrint.h"
|
||||
#include "StreamAPI.h"
|
||||
/**
|
||||
* Provides both debug printing and, if the client starts sending protobufs to us, switches to send/receive protobufs
|
||||
* (and starts dropping debug printing - FIXME, eventually those prints should be encapsulated in protobufs).
|
||||
*/
|
||||
class SerialConsole : public StreamAPI, public RedirectablePrint
|
||||
{
|
||||
public:
|
||||
SerialConsole();
|
||||
|
||||
/// Do late init that can't happen at constructor time
|
||||
virtual void init();
|
||||
|
||||
/**
|
||||
* we override this to notice when we've received a protobuf over the serial stream. Then we shunt off
|
||||
* debug serial output.
|
||||
*/
|
||||
virtual void handleToRadio(const uint8_t *buf, size_t len);
|
||||
|
||||
virtual size_t write(uint8_t c)
|
||||
{
|
||||
if (c == '\n') // prefix any newlines with carriage return
|
||||
RedirectablePrint::write('\r');
|
||||
return RedirectablePrint::write(c);
|
||||
}
|
||||
|
||||
protected:
|
||||
/// Hookable to find out when connection changes
|
||||
virtual void onConnectionChanged(bool connected);
|
||||
};
|
||||
|
||||
extern SerialConsole console;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user