mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-15 23:32:34 +00:00
Compare commits
566 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea40bd991c | ||
|
|
e19dd46f0f | ||
|
|
532b06c280 | ||
|
|
0cf7aaffff | ||
|
|
e2e1819ef1 | ||
|
|
31b89e2932 | ||
|
|
a021ff7eb8 | ||
|
|
bb5d0fac90 | ||
|
|
df5ed64514 | ||
|
|
9db5f9ff67 | ||
|
|
ca83a78e13 | ||
|
|
13eef9a309 | ||
|
|
2a8ac2c0c6 | ||
|
|
c97342db99 | ||
|
|
d7b2a0ed79 | ||
|
|
af0a1b5db5 | ||
|
|
9cf030d587 | ||
|
|
c04d70d5e5 | ||
|
|
2a47819fd6 | ||
|
|
4516c8f9b5 | ||
|
|
e4fdf26dc7 | ||
|
|
39d14fedc2 | ||
|
|
1da38fc748 | ||
|
|
b5f50efdcd | ||
|
|
046e691d4e | ||
|
|
e72531b090 | ||
|
|
81e320c9cf | ||
|
|
fa8cc74141 | ||
|
|
c7d9ff7cc0 | ||
|
|
8704a9d08f | ||
|
|
c0d27e2ce9 | ||
|
|
84b9028ecb | ||
|
|
4fda7098c0 | ||
|
|
8e8264efb0 | ||
|
|
54e780a6ca | ||
|
|
125eb2b784 | ||
|
|
6ea9cdc83b | ||
|
|
c0711fde69 | ||
|
|
20b8d2c4a5 | ||
|
|
73ae151971 | ||
|
|
f4806c9dd7 | ||
|
|
79532210e8 | ||
|
|
d7f26493a5 | ||
|
|
b9d025dd58 | ||
|
|
f435086a5a | ||
|
|
3dcdf372d7 | ||
|
|
cd84f2867c | ||
|
|
cafe00e463 | ||
|
|
6da4e30215 | ||
|
|
dbf0569e29 | ||
|
|
18220b88b3 | ||
|
|
665da2fb00 | ||
|
|
57ffe6622d | ||
|
|
485fec9649 | ||
|
|
bd85736226 | ||
|
|
4ec8986934 | ||
|
|
b963216764 | ||
|
|
813fd95bc8 | ||
|
|
3598c91c29 | ||
|
|
507cd1dd20 | ||
|
|
e39506824d | ||
|
|
f68a31ab28 | ||
|
|
b1181deb58 | ||
|
|
89b32dd7ee | ||
|
|
c54e87f9a2 | ||
|
|
eee7e1de57 | ||
|
|
3c60df1565 | ||
|
|
a827017bd2 | ||
|
|
95c502c658 | ||
|
|
0f573901d5 | ||
|
|
fdc9bf5783 | ||
|
|
37e0f9a325 | ||
|
|
0c06d8db3c | ||
|
|
0be4bbb369 | ||
|
|
f02ab88393 | ||
|
|
c9d4de8808 | ||
|
|
adb912b665 | ||
|
|
3f5da1e03e | ||
|
|
0a40d920e3 | ||
|
|
39311f1e40 | ||
|
|
9cd24a5646 | ||
|
|
1c0efde315 | ||
|
|
c82905bbdd | ||
|
|
275eace968 | ||
|
|
5688c8b81e | ||
|
|
8b2798abd5 | ||
|
|
6d977923b6 | ||
|
|
52dacaed37 | ||
|
|
7a381eaea1 | ||
|
|
69391e186b | ||
|
|
06f8beaa17 | ||
|
|
3798f4ca5b | ||
|
|
4fd243a6e4 | ||
|
|
d458f673be | ||
|
|
cfcb00b943 | ||
|
|
977e47d109 | ||
|
|
cfeb40f36d | ||
|
|
4fcc3ac1de | ||
|
|
f4afa6931b | ||
|
|
71be71d63d | ||
|
|
de9f7e6c39 | ||
|
|
7c8db2b501 | ||
|
|
cd653f9434 | ||
|
|
74bc05936d | ||
|
|
7aacfd66ef | ||
|
|
3636b87db0 | ||
|
|
d6bd328576 | ||
|
|
0af5b225c4 | ||
|
|
f7dcef39ce | ||
|
|
07042178d2 | ||
|
|
243878f2a0 | ||
|
|
d3f8a76cce | ||
|
|
20131a51a2 | ||
|
|
1c9a369774 | ||
|
|
dcb426f58f | ||
|
|
35bcb5297a | ||
|
|
84e3d7c276 | ||
|
|
9b03f0ac8e | ||
|
|
eb402809e2 | ||
|
|
e9c9e40624 | ||
|
|
01eed97b91 | ||
|
|
94a47dba7d | ||
|
|
bce2c9347b | ||
|
|
da8b1d41c7 | ||
|
|
3ddae5faec | ||
|
|
34faea6100 | ||
|
|
01848a9e5d | ||
|
|
10db80541f | ||
|
|
edd1268f5f | ||
|
|
11c16e8bbc | ||
|
|
7d411351c0 | ||
|
|
df21602c90 | ||
|
|
ce4ccf3cc4 | ||
|
|
a7f93de3ad | ||
|
|
8e8257adf3 | ||
|
|
e627725dfc | ||
|
|
b3ba557b8b | ||
|
|
bd03650140 | ||
|
|
42f51f33a8 | ||
|
|
8295b88d96 | ||
|
|
70313b2660 | ||
|
|
745d3775b4 | ||
|
|
aa176b6593 | ||
|
|
b0e3a7524f | ||
|
|
5ceee50bb5 | ||
|
|
ebdad76fb2 | ||
|
|
925829dc58 | ||
|
|
e04ea853dc | ||
|
|
9587729bb0 | ||
|
|
6ec368bf02 | ||
|
|
d71c7b512f | ||
|
|
349701ac14 | ||
|
|
d424fa5ea8 | ||
|
|
ca6293eefe | ||
|
|
d289e8a86f | ||
|
|
96328526b7 | ||
|
|
279c89dca3 | ||
|
|
a7a52e08d1 | ||
|
|
f6336855d0 | ||
|
|
727d8a6456 | ||
|
|
7b80b95381 | ||
|
|
2867f8fd53 | ||
|
|
cdf416cb73 | ||
|
|
7716d62018 | ||
|
|
d5f76b16b9 | ||
|
|
552406b15f | ||
|
|
abb52e5446 | ||
|
|
de37a0c31e | ||
|
|
6e31ba30c7 | ||
|
|
8fe1c518d9 | ||
|
|
b6006fe3d5 | ||
|
|
3e8173c4bd | ||
|
|
d8a15d6324 | ||
|
|
9a3d558f61 | ||
|
|
85ddf3be1b | ||
|
|
3ca42b8f51 | ||
|
|
b75c7ad179 | ||
|
|
44f89c969d | ||
|
|
5595fb38c1 | ||
|
|
c0e0e095c9 | ||
|
|
6c1c0640f2 | ||
|
|
698102371f | ||
|
|
997ed283bf | ||
|
|
9128f7d4b3 | ||
|
|
93d0257be7 | ||
|
|
adc71e7ed2 | ||
|
|
516e18ca80 | ||
|
|
4777e53c23 | ||
|
|
d6912cfd8e | ||
|
|
621306e610 | ||
|
|
0e507e1923 | ||
|
|
15a0b3694d | ||
|
|
6e4cf22cf0 | ||
|
|
58859848a3 | ||
|
|
55f61826bf | ||
|
|
f80d357b77 | ||
|
|
c972197643 | ||
|
|
c06b7b2b48 | ||
|
|
3c69beef94 | ||
|
|
e55c5e10bc | ||
|
|
e321528a6d | ||
|
|
ee897bce6c | ||
|
|
186a52172c | ||
|
|
21570fc24f | ||
|
|
2edc6b363d | ||
|
|
0c74303e9d | ||
|
|
244e597a9f | ||
|
|
15833e1e53 | ||
|
|
73d64d378a | ||
|
|
8d04410f45 | ||
|
|
36d28d2da6 | ||
|
|
4a653ab054 | ||
|
|
651bd71454 | ||
|
|
1e9ebbc476 | ||
|
|
27c16ba185 | ||
|
|
51a8c7118a | ||
|
|
808c4ff5ca | ||
|
|
ded2b86e55 | ||
|
|
9efcdc7c67 | ||
|
|
34e6dbec81 | ||
|
|
62b655ccea | ||
|
|
3c2aac87f7 | ||
|
|
3aba097096 | ||
|
|
f45451ca74 | ||
|
|
c35fec9f20 | ||
|
|
88fa24ce79 | ||
|
|
59577b9d79 | ||
|
|
c349ad62e7 | ||
|
|
d5b57840d9 | ||
|
|
f8a3d143cb | ||
|
|
c717dfc33d | ||
|
|
22d9096c3d | ||
|
|
8080bc608b | ||
|
|
c4b9d60afa | ||
|
|
dda5568e2c | ||
|
|
2d8e00e2a0 | ||
|
|
901ff6bb1e | ||
|
|
7312c56d6c | ||
|
|
031c58e21c | ||
|
|
35b1cfcc42 | ||
|
|
e545778154 | ||
|
|
6fd2bc5f83 | ||
|
|
9a587b2743 | ||
|
|
bacc1b1dad | ||
|
|
575b69c541 | ||
|
|
73092b4b40 | ||
|
|
877dc824a9 | ||
|
|
89c76dca11 | ||
|
|
2253ea1b41 | ||
|
|
b732a13d6c | ||
|
|
8e0c224813 | ||
|
|
5a96dc0083 | ||
|
|
181db06b0c | ||
|
|
47ccfb6106 | ||
|
|
5f97740ab7 | ||
|
|
90d6878bbb | ||
|
|
5c70f36aa5 | ||
|
|
09cc0a85db | ||
|
|
a47fcdacb5 | ||
|
|
ef0891ae5d | ||
|
|
a8d7700295 | ||
|
|
412916ba7c | ||
|
|
616290edcc | ||
|
|
9ed19892e2 | ||
|
|
88cf60ad9d | ||
|
|
7f59e76c72 | ||
|
|
dcb9125b32 | ||
|
|
2743b9d310 | ||
|
|
2f779bfd37 | ||
|
|
db2193b526 | ||
|
|
7205e9a5b4 | ||
|
|
1ca83509dd | ||
|
|
7135a12300 | ||
|
|
fae9ea8b3b | ||
|
|
b96ee7be72 | ||
|
|
9e449bebf9 | ||
|
|
e32202e4f8 | ||
|
|
ca99b6b3b7 | ||
|
|
2eb2e9142f | ||
|
|
15e1a3870c | ||
|
|
5bdc7216b3 | ||
|
|
cc127f7dad | ||
|
|
be38a58a62 | ||
|
|
5930f8270d | ||
|
|
c9f2318e78 | ||
|
|
5cdc2f5142 | ||
|
|
53d773b81f | ||
|
|
85b2ba7ce9 | ||
|
|
474e0e7158 | ||
|
|
99a8c80c44 | ||
|
|
03a9d7da5e | ||
|
|
6975848f45 | ||
|
|
0cdc1fc959 | ||
|
|
e80c79edbe | ||
|
|
651d045afe | ||
|
|
86952c5456 | ||
|
|
46781357df | ||
|
|
bb9abf2dca | ||
|
|
5249608dce | ||
|
|
ee8f4de5ab | ||
|
|
17297db2b1 | ||
|
|
ad8bcba5ef | ||
|
|
138cebbf03 | ||
|
|
9f9573d2eb | ||
|
|
e10b82c118 | ||
|
|
d82aaaa806 | ||
|
|
c0d94ae4ab | ||
|
|
02ce12607c | ||
|
|
bdeba54c50 | ||
|
|
26c9585c9d | ||
|
|
696255c1f7 | ||
|
|
d857f8ba6d | ||
|
|
5852caa61c | ||
|
|
e82752c777 | ||
|
|
3eae2c6286 | ||
|
|
1e5d0b25ad | ||
|
|
c8423400ea | ||
|
|
af88a34f75 | ||
|
|
b9f1ce70cb | ||
|
|
c361c1fab7 | ||
|
|
091e953ed4 | ||
|
|
9ab02119f5 | ||
|
|
2d4849e0d0 | ||
|
|
16f897d27c | ||
|
|
4cbf0a0730 | ||
|
|
99c8df8e7d | ||
|
|
8e2e4f7e6a | ||
|
|
45d72bd51b | ||
|
|
781ed3eafd | ||
|
|
c66a0a37d8 | ||
|
|
92f2007207 | ||
|
|
1f7b537d2d | ||
|
|
cabeacfa94 | ||
|
|
df8b3ebbc7 | ||
|
|
b1c30f0650 | ||
|
|
194028f9fc | ||
|
|
f49c8f4c43 | ||
|
|
b3b4c2c1c3 | ||
|
|
a0076eb394 | ||
|
|
a6a4fec4b9 | ||
|
|
2d4657c8d4 | ||
|
|
32b8e4f20a | ||
|
|
3753fef298 | ||
|
|
a4bb1937c1 | ||
|
|
4bd22dd5db | ||
|
|
79a24c200e | ||
|
|
90060e84c0 | ||
|
|
8f5a1f19d3 | ||
|
|
3e0dc44210 | ||
|
|
91b99bd584 | ||
|
|
b6e21bcbcd | ||
|
|
ae7d3ee5ed | ||
|
|
f1179bd3ea | ||
|
|
9b24cc6dd6 | ||
|
|
20c5b98b2d | ||
|
|
d3cb9bdd4a | ||
|
|
a70cda6fe4 | ||
|
|
7737123d0f | ||
|
|
5138aff4b2 | ||
|
|
0b0d293a66 | ||
|
|
ddab4a0235 | ||
|
|
50615540ce | ||
|
|
f5e42b2533 | ||
|
|
9e9913101f | ||
|
|
b1289b632a | ||
|
|
c427c8abf9 | ||
|
|
9170dc7738 | ||
|
|
cc36e3a9a6 | ||
|
|
7d4c77abfd | ||
|
|
1ba91ec27f | ||
|
|
575c5b2193 | ||
|
|
3473a1e323 | ||
|
|
11a00e2977 | ||
|
|
817c99e09c | ||
|
|
9801a62d2d | ||
|
|
2bd40b7053 | ||
|
|
dccc15946b | ||
|
|
3ab9d2a50e | ||
|
|
776a978ea0 | ||
|
|
f60922af34 | ||
|
|
cb34fd5eb9 | ||
|
|
19d81347f2 | ||
|
|
d7d13d637c | ||
|
|
df75cefeeb | ||
|
|
66f9dbec45 | ||
|
|
4679dd7c4d | ||
|
|
a02979d564 | ||
|
|
f2698bbf91 | ||
|
|
d045139945 | ||
|
|
2c9c5991a0 | ||
|
|
1b365fa0aa | ||
|
|
71d1d4d8fa | ||
|
|
64df994a32 | ||
|
|
49a19e26d5 | ||
|
|
ef37f955c3 | ||
|
|
ac50b9544b | ||
|
|
ccc1600bc9 | ||
|
|
7c220f8a39 | ||
|
|
1839f8f7ca | ||
|
|
48c461c50c | ||
|
|
f346b4f0f2 | ||
|
|
5aab4f5c95 | ||
|
|
d407db5ee1 | ||
|
|
93afc71e2e | ||
|
|
67e657f10f | ||
|
|
619a48085a | ||
|
|
68937d52fe | ||
|
|
e33657eb75 | ||
|
|
21751da5a2 | ||
|
|
c2e8ac7173 | ||
|
|
825001f313 | ||
|
|
576526576a | ||
|
|
2fd5ce00ce | ||
|
|
4204c494ae | ||
|
|
84beae1001 | ||
|
|
951b4293c4 | ||
|
|
952c216bf7 | ||
|
|
ff4b03b8c1 | ||
|
|
c5903a790b | ||
|
|
bbc36f7b6f | ||
|
|
2f9ef463d8 | ||
|
|
bea00569fd | ||
|
|
d7368d5a51 | ||
|
|
47bbde3c60 | ||
|
|
04942a3570 | ||
|
|
62a8c968e8 | ||
|
|
b9a1cae72d | ||
|
|
6b442784f3 | ||
|
|
cfcb62bd18 | ||
|
|
f698883c02 | ||
|
|
4a5cef886e | ||
|
|
f6ec129288 | ||
|
|
17763034a0 | ||
|
|
4ad562b9f4 | ||
|
|
6b838002d4 | ||
|
|
5e0d53a1e3 | ||
|
|
b1dae3608e | ||
|
|
44aafd5b9c | ||
|
|
7597d5b3fd | ||
|
|
1a8891c33d | ||
|
|
f0eeaf01d4 | ||
|
|
d4e95e95a6 | ||
|
|
0767c8be03 | ||
|
|
18bbf3523e | ||
|
|
b081a6da56 | ||
|
|
a102e49fdb | ||
|
|
c078c08c3e | ||
|
|
91756d1fec | ||
|
|
5981831bc0 | ||
|
|
00eed206cb | ||
|
|
130d55aaaa | ||
|
|
13ef48094d | ||
|
|
529fd5a830 | ||
|
|
baa3d1dae4 | ||
|
|
4dd50df810 | ||
|
|
14c4022c18 | ||
|
|
a5d7bacdbf | ||
|
|
0b3c25f6d9 | ||
|
|
ad7a474a52 | ||
|
|
430186ec53 | ||
|
|
e9279919ae | ||
|
|
227c6fc27e | ||
|
|
a37844d7e5 | ||
|
|
ff20b29c3c | ||
|
|
64c29c4a35 | ||
|
|
d4df3f8a7e | ||
|
|
a16c3af30a | ||
|
|
3061860dab | ||
|
|
2450d98b59 | ||
|
|
a371592ad9 | ||
|
|
f7aaf48ae9 | ||
|
|
1fb604ebc8 | ||
|
|
df2733a3b5 | ||
|
|
8fd3cb1aac | ||
|
|
485c476f17 | ||
|
|
7dd4ce32d2 | ||
|
|
7f12af73d4 | ||
|
|
63113d57b3 | ||
|
|
32850ff39d | ||
|
|
2901f773a4 | ||
|
|
a7c54e4ad7 | ||
|
|
e1f0e11cb8 | ||
|
|
c73ee98739 | ||
|
|
c2a1141dfa | ||
|
|
5b4472ab56 | ||
|
|
3262f732d8 | ||
|
|
cff21ca130 | ||
|
|
81ce04d3da | ||
|
|
f4d2b10840 | ||
|
|
2370cb8aac | ||
|
|
59ec87f5b0 | ||
|
|
0d9481b6ea | ||
|
|
8f0105ccd9 | ||
|
|
05ca3c3d56 | ||
|
|
ba549d8fcd | ||
|
|
b9df2c00fa | ||
|
|
d9dcb33576 | ||
|
|
f698231be7 | ||
|
|
8414f4a6a3 | ||
|
|
8505020be5 | ||
|
|
f3b93d55fb | ||
|
|
9e0731a956 | ||
|
|
2b373048c6 | ||
|
|
22f23bb07d | ||
|
|
b32e3f1269 | ||
|
|
68ddb712f5 | ||
|
|
2fb5cd8c1c | ||
|
|
79aea8231f | ||
|
|
b0837c10c6 | ||
|
|
cd811951b1 | ||
|
|
df2976dad0 | ||
|
|
4ccbe6ff71 | ||
|
|
038ddb887f | ||
|
|
9134faaed1 | ||
|
|
649a120fe0 | ||
|
|
4db0c4a563 | ||
|
|
5f2f3c94b9 | ||
|
|
3b2f5fa5e3 | ||
|
|
97adb598b6 | ||
|
|
152ebf0dff | ||
|
|
5b1511c930 | ||
|
|
7ef2cc8623 | ||
|
|
b41a32c6b6 | ||
|
|
1ebd7b0c3e | ||
|
|
5457541244 | ||
|
|
ca77d48b20 | ||
|
|
965c2bda8d | ||
|
|
02e3438d5e | ||
|
|
02b1ece6ac | ||
|
|
9fdef366f7 | ||
|
|
284816229e | ||
|
|
10008d4eef | ||
|
|
58cfd1317c | ||
|
|
3d3f7869d4 | ||
|
|
ef325289eb | ||
|
|
876d32c9ee | ||
|
|
b9ce75b09c | ||
|
|
48e6a60a07 | ||
|
|
ca48079545 | ||
|
|
76b4be3b87 | ||
|
|
d39cc3d57b | ||
|
|
b17a8d7a6a | ||
|
|
c16acb904e | ||
|
|
5b777219be | ||
|
|
32ea11d2af | ||
|
|
db8faa9faf | ||
|
|
6d178ebc91 | ||
|
|
f75a256631 | ||
|
|
4f659b7563 | ||
|
|
dffcea1f4d | ||
|
|
b4b1b24c84 | ||
|
|
4d7cd0a09d | ||
|
|
fed8e80ae4 | ||
|
|
b38bcffafb | ||
|
|
530432411e | ||
|
|
f30c84012f | ||
|
|
5150d15997 | ||
|
|
7d6dbcfa3f | ||
|
|
26bafb4082 | ||
|
|
8bdbbfbe16 | ||
|
|
0ba4925f75 | ||
|
|
26d50fda9a | ||
|
|
bc22ab7b87 | ||
|
|
d5e3e63d6d | ||
|
|
0b1a78c028 | ||
|
|
b8e1b28958 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -17,5 +17,5 @@ Thumbs.db
|
||||
.cproject
|
||||
.idea/*
|
||||
.vagrant
|
||||
|
||||
nanopb*
|
||||
flash.uf2
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -49,7 +49,8 @@
|
||||
"string_view": "cpp",
|
||||
"cassert": "cpp",
|
||||
"iterator": "cpp",
|
||||
"shared_mutex": "cpp"
|
||||
"shared_mutex": "cpp",
|
||||
"iostream": "cpp"
|
||||
},
|
||||
"cSpell.words": [
|
||||
"Blox",
|
||||
|
||||
12
README.md
12
README.md
@@ -30,11 +30,14 @@ We currently support three models of radios.
|
||||
- [T-Beam V0.7 w/ NEO-6M](https://www.aliexpress.com/item/4000574335430.html) (will work but **you must use the tbeam0.7 firmware ** - but the T-Beam V1.0 or later are better!)
|
||||
- board labels "TTGO T22_V07 20180711"
|
||||
- 3D printable cases
|
||||
- [T-Beam V0](https://www.thingiverse.com/thing:3773717)
|
||||
- [T-Beam V0](https://www.thingiverse.com/thing:3773717) (GPS and LoRa antenna misaligned if GPS placed as pictured)
|
||||
- [T-Beam V1 (SMA-antenna)](https://www.thingiverse.com/thing:3830711)
|
||||
- [T-Beam V1 (IPEX-antenna)](https://www.thingiverse.com/thing:4587297)
|
||||
- [T-Beam V1 (SMA-antenna)](https://www.thingiverse.com/thing:4677388) (Mounting option for larger GPS antenna but LoRa antenna enclosed)
|
||||
- [T-Beam V1 (IPEX-antenna)](https://www.thingiverse.com/thing:4587297) (GPS and LoRa antenna misaligned if GPS placed as pictured)
|
||||
- [T-Beam V1 (IPEX-antenna)](https://www.thingiverse.com/thing:4589651)
|
||||
- [T-Beam V1 (IPEX-antenna)](https://www.thingiverse.com/thing:4619981) (GPS and LoRa antenna misaligned if GPS placed as pictured)
|
||||
- Laser-cut cases
|
||||
- [T-Beam V1](https://www.thingiverse.com/thing:4552771)
|
||||
- [T-Beam V1 (SMA-antenna)](https://www.thingiverse.com/thing:4552771)
|
||||
|
||||
- [TTGO LORA32](https://www.aliexpress.com/item/4000211331316.html) - No GPS
|
||||
- version 2.1
|
||||
@@ -43,7 +46,10 @@ We currently support three models of radios.
|
||||
- [TTGO LORA32 v1](https://www.thingiverse.com/thing:3385109)
|
||||
|
||||
- [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/) - No GPS
|
||||
- [Official Heltec case](https://www.aliexpress.com/item/4001050707951.html)
|
||||
- [3D Printable case](https://www.thingiverse.com/thing:3125854)
|
||||
|
||||
Note: The GPS and LoRa stock antennas should be placed in a way, that the GPS antenna faces the sky and the LoRa antenna radiates 360 degrees horizontally. For better GPS reception you might want to [upgrade the GPS antenna](https://meshtastic.discourse.group/t/the-importance-of-gps-antennas-and-request-to-3d-case-documentation-people/1505) and to properly align the antennas you might want to upgrade to a LoRa antenna that can be adjusted to radiate into the right directions.
|
||||
|
||||
**Make sure to get the frequency for your country**
|
||||
|
||||
|
||||
@@ -2,18 +2,19 @@
|
||||
|
||||
set -e
|
||||
|
||||
source bin/version.sh
|
||||
VERSION=`bin/buildinfo.py`
|
||||
|
||||
COUNTRIES="US EU433 EU865 CN JP ANZ KR"
|
||||
#COUNTRIES=US
|
||||
#COUNTRIES=CN
|
||||
|
||||
BOARDS_ESP32="tlora-v2 tlora-v1 tlora-v2-1-1.6 tbeam heltec tbeam0.7"
|
||||
#BOARDS_ESP32=tbeam
|
||||
|
||||
# FIXME note nrf52840dk build is for some reason only generating a BIN file but not a HEX file nrf52840dk-geeksville is fine
|
||||
BOARDS_NRF52="lora-relay-v1"
|
||||
BOARDS="$BOARDS_ESP32 $BOARDS_NRF52"
|
||||
#BOARDS=tbeam
|
||||
|
||||
NUM_JOBS=2 || true
|
||||
|
||||
OUTDIR=release/latest
|
||||
|
||||
@@ -22,22 +23,61 @@ ARCHIVEDIR=release/archive
|
||||
|
||||
rm -f $OUTDIR/firmware*
|
||||
|
||||
mkdir -p $OUTDIR/bins $OUTDIR/elfs
|
||||
rm -f $OUTDIR/bins/*
|
||||
mkdir -p $OUTDIR/bins
|
||||
rm -r $OUTDIR/bins/*
|
||||
mkdir -p $OUTDIR/bins/universal $OUTDIR/elfs/universal
|
||||
|
||||
# build the named environment and copy the bins to the release directory
|
||||
function do_build {
|
||||
echo "Building for $BOARD with $PLATFORMIO_BUILD_FLAGS"
|
||||
function do_build() {
|
||||
BOARD=$1
|
||||
COUNTRY=$2
|
||||
isNrf=$3
|
||||
|
||||
echo "Building $COUNTRY for $BOARD with $PLATFORMIO_BUILD_FLAGS"
|
||||
rm -f .pio/build/$BOARD/firmware.*
|
||||
|
||||
# The shell vars the build tool expects to find
|
||||
export HW_VERSION="1.0-$COUNTRY"
|
||||
export APP_VERSION=$VERSION
|
||||
export COUNTRY
|
||||
|
||||
pio run --jobs 4 --environment $BOARD # -v
|
||||
# Are we building a universal/regionless rom?
|
||||
if [ "x$COUNTRY" != "x" ]
|
||||
then
|
||||
export HW_VERSION="1.0-$COUNTRY"
|
||||
export COUNTRY
|
||||
basename=firmware-$BOARD-$COUNTRY-$VERSION
|
||||
else
|
||||
export HW_VERSION="1.0"
|
||||
unset COUNTRY
|
||||
basename=universal/firmware-$BOARD-$VERSION
|
||||
fi
|
||||
|
||||
pio run --jobs $NUM_JOBS --environment $BOARD # -v
|
||||
SRCELF=.pio/build/$BOARD/firmware.elf
|
||||
cp $SRCELF $OUTDIR/elfs/firmware-$BOARD-$COUNTRY-$VERSION.elf
|
||||
cp $SRCELF $OUTDIR/elfs/$basename.elf
|
||||
|
||||
if [ "$isNrf" = "false" ]
|
||||
then
|
||||
echo "Copying ESP32 bin file"
|
||||
SRCBIN=.pio/build/$BOARD/firmware.bin
|
||||
cp $SRCBIN $OUTDIR/bins/$basename.bin
|
||||
else
|
||||
echo "Generating NRF52 uf2 file"
|
||||
SRCHEX=.pio/build/$BOARD/firmware.hex
|
||||
bin/uf2conv.py $SRCHEX -c -o $OUTDIR/bins/$basename.uf2 -f 0xADA52840
|
||||
fi
|
||||
}
|
||||
|
||||
function do_boards() {
|
||||
declare boards=$1
|
||||
declare isNrf=$2
|
||||
for board in $boards; do
|
||||
for country in $COUNTRIES; do
|
||||
do_build $board $country "$isNrf"
|
||||
done
|
||||
|
||||
# Build universal
|
||||
do_build $board "" "$isNrf"
|
||||
done
|
||||
}
|
||||
|
||||
# Make sure our submodules are current
|
||||
@@ -46,26 +86,20 @@ git submodule update
|
||||
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
|
||||
platformio lib update
|
||||
|
||||
for COUNTRY in $COUNTRIES; do
|
||||
for BOARD in $BOARDS; do
|
||||
do_build $BOARD
|
||||
done
|
||||
do_boards "$BOARDS_ESP32" "false"
|
||||
do_boards "$BOARDS_NRF52" "true"
|
||||
|
||||
echo "Copying ESP32 bin files"
|
||||
for BOARD in $BOARDS_ESP32; do
|
||||
SRCBIN=.pio/build/$BOARD/firmware.bin
|
||||
cp $SRCBIN $OUTDIR/bins/firmware-$BOARD-$COUNTRY-$VERSION.bin
|
||||
done
|
||||
|
||||
echo "Generating NRF52 uf2 files"
|
||||
for BOARD in $BOARDS_NRF52; do
|
||||
SRCHEX=.pio/build/$BOARD/firmware.hex
|
||||
bin/uf2conv.py $SRCHEX -c -o $OUTDIR/bins/firmware-$BOARD-$COUNTRY-$VERSION.uf2 -f 0xADA52840
|
||||
done
|
||||
done
|
||||
echo "Building SPIFFS for ESP32 targets"
|
||||
pio run --environment tbeam -t buildfs
|
||||
cp .pio/build/tbeam/spiffs.bin $OUTDIR/bins/universal/spiffs-$VERSION.bin
|
||||
|
||||
# keep the bins in archive also
|
||||
cp $OUTDIR/bins/firmware* $OUTDIR/elfs/firmware* $ARCHIVEDIR
|
||||
cp $OUTDIR/bins/firmware* $OUTDIR/bins/universal/spiffs* $OUTDIR/elfs/firmware* $OUTDIR/bins/universal/firmware* $OUTDIR/elfs/universal/firmware* $ARCHIVEDIR
|
||||
|
||||
echo Updating android bins $OUTDIR/forandroid
|
||||
rm -rf $OUTDIR/forandroid
|
||||
mkdir -p $OUTDIR/forandroid
|
||||
cp -a $OUTDIR/bins/universal/*.bin $OUTDIR/forandroid/
|
||||
|
||||
cat >$OUTDIR/curfirmwareversion.xml <<XML
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
@@ -79,7 +113,8 @@ Generated by bin/buildall.sh -->
|
||||
</resources>
|
||||
XML
|
||||
|
||||
echo Generating $ARCHIVEDIR/firmware-$VERSION.zip
|
||||
rm -f $ARCHIVEDIR/firmware-$VERSION.zip
|
||||
zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/bins/firmware-*-$VERSION.* images/system-info.bin bin/device-install.sh bin/device-update.sh
|
||||
zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $ARCHIVEDIR/spiffs-$VERSION.bin $OUTDIR/bins/firmware-*-$VERSION.* images/system-info.bin bin/device-install.sh bin/device-update.sh
|
||||
|
||||
echo BUILT ALL
|
||||
|
||||
11
bin/buildinfo.py
Executable file
11
bin/buildinfo.py
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
import configparser
|
||||
|
||||
config = configparser.RawConfigParser()
|
||||
config.read('version.properties')
|
||||
|
||||
version = dict(config.items('VERSION'))
|
||||
|
||||
verStr = "{}.{}.{}".format(version["major"], version["minor"], version["build"])
|
||||
|
||||
print(f"{verStr}")
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# Usage info
|
||||
show_help() {
|
||||
cat << EOF
|
||||
@@ -36,6 +38,7 @@ if [ -f "${FILENAME}" ]; then
|
||||
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
|
||||
esptool.py --baud 921600 erase_flash
|
||||
esptool.py --baud 921600 write_flash 0x1000 system-info.bin
|
||||
esptool.py --baud 921600 write_flash 0x00390000 spiffs-*.bin
|
||||
esptool.py --baud 921600 write_flash 0x10000 ${FILENAME}
|
||||
else
|
||||
echo "Invalid file: ${FILENAME}"
|
||||
|
||||
@@ -2,7 +2,20 @@
|
||||
|
||||
set -e
|
||||
|
||||
# dependencies
|
||||
# apt install srecord
|
||||
|
||||
BOOTDIR=/home/kevinh/development/meshtastic/Adafruit_nRF52_Bootloader
|
||||
BOARD=othernet_ppr1
|
||||
BOOTVER=0.3.2
|
||||
BOOTNUM=128
|
||||
BOOTSHA=gc01b9ea
|
||||
SDCODE=s113
|
||||
SDVER=7.2.0
|
||||
PROJ=ppr1
|
||||
|
||||
# FIXME for nRF52840 use 0xff000, for nRF52833 use 0x7f000
|
||||
BOOTSET=0x7f000
|
||||
|
||||
nrfjprog --eraseall -f nrf52
|
||||
|
||||
@@ -11,12 +24,12 @@ nrfjprog --eraseall -f nrf52
|
||||
# first 4 bytes should be 0x01 to indicate valid app image
|
||||
# second 4 bytes should be 0x00 to indicate no CRC required for image
|
||||
echo "01 00 00 00 00 00 00 00" | xxd -r -p - >/tmp/bootconf.bin
|
||||
srec_cat /tmp/bootconf.bin -binary -offset 0xff000 -output /tmp/bootconf.hex -intel
|
||||
srec_cat /tmp/bootconf.bin -binary -offset $BOOTSET -output /tmp/bootconf.hex -intel
|
||||
|
||||
echo Generating merged hex file
|
||||
mergehex -m $BOOTDIR/_build/build-ttgo_eink/ttgo_eink_bootloader-0.3.2-124-g69bd8eb-dirty_s140_6.1.1.hex .pio/build/eink/firmware.hex /tmp/bootconf.hex -o ttgo_eink_full.hex
|
||||
echo Generating merged hex file from .pio/build/$PROJ/firmware.hex
|
||||
mergehex -o ${BOARD}_full.hex -m $BOOTDIR/_build/build-$BOARD/${BOARD}_bootloader-$BOOTVER-$BOOTNUM-$BOOTSHA-dirty_${SDCODE}_$SDVER.hex .pio/build/$PROJ/firmware.hex /tmp/bootconf.hex
|
||||
|
||||
echo Telling bootloader app region is valid and telling CPU to run
|
||||
nrfjprog --program ttgo_eink_full.hex -f nrf52 --reset
|
||||
nrfjprog --program ${BOARD}_full.hex -f nrf52 --reset
|
||||
|
||||
# nrfjprog --readuicr /tmp/uicr.hex; objdump -s /tmp/uicr.hex | less
|
||||
|
||||
22
bin/install-eink.sh
Executable file
22
bin/install-eink.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
# You probably don't want to use this script, it programs a custom bootloader build onto a nrf52 board
|
||||
|
||||
set -e
|
||||
|
||||
BOOTDIR=/home/kevinh/development/meshtastic/Adafruit_nRF52_Bootloader
|
||||
|
||||
nrfjprog --eraseall -f nrf52
|
||||
|
||||
# this generates an intel hex file that can be programmed into a NRF52 to tell the adafruit bootloader that the current app image is valid
|
||||
# Bootloader settings are at BOOTLOADER_SETTINGS (rw) : ORIGIN = 0xFF000, LENGTH = 0x1000
|
||||
# first 4 bytes should be 0x01 to indicate valid app image
|
||||
# second 4 bytes should be 0x00 to indicate no CRC required for image
|
||||
echo "01 00 00 00 00 00 00 00" | xxd -r -p - >/tmp/bootconf.bin
|
||||
srec_cat /tmp/bootconf.bin -binary -offset 0xff000 -output /tmp/bootconf.hex -intel
|
||||
|
||||
echo Generating merged hex file
|
||||
mergehex -m $BOOTDIR/_build/build-ttgo_eink/ttgo_eink_bootloader-0.3.2-125-gf38f8f4-dirty_s140_6.1.1.hex .pio/build/eink/firmware.hex /tmp/bootconf.hex -o ttgo_eink_full.hex
|
||||
|
||||
echo Telling bootloader app region is valid and telling CPU to run
|
||||
nrfjprog --program ttgo_eink_full.hex -f nrf52 --reset
|
||||
|
||||
# nrfjprog --readuicr /tmp/uicr.hex; objdump -s /tmp/uicr.hex | less
|
||||
@@ -1,3 +1,3 @@
|
||||
|
||||
|
||||
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52840_XXAA
|
||||
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52832_XXAA
|
||||
|
||||
3
bin/nrf52833-gdbserver.sh
Executable file
3
bin/nrf52833-gdbserver.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52833_XXAA
|
||||
@@ -1,3 +1,3 @@
|
||||
|
||||
|
||||
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52832_XXAA
|
||||
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52840_XXAA
|
||||
|
||||
16
bin/platformio-custom.py
Normal file
16
bin/platformio-custom.py
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
Import("projenv")
|
||||
|
||||
import configparser
|
||||
prefsLoc = projenv["PROJECT_DIR"] + "/version.properties"
|
||||
config = configparser.RawConfigParser()
|
||||
config.read(prefsLoc)
|
||||
version = dict(config.items('VERSION'))
|
||||
verStr = "{}.{}.{}".format(version["major"], version["minor"], version["build"])
|
||||
|
||||
print("Using meshtastic platform-custom.py, firmare version " + verStr)
|
||||
|
||||
# General options that are passed to the C and C++ compilers
|
||||
projenv.Append(CCFLAGS=[
|
||||
"-DAPP_VERSION=" + verStr
|
||||
])
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
set -e
|
||||
|
||||
source bin/version.sh
|
||||
VERSION=`bin/buildinfo.py`
|
||||
|
||||
esptool.py --baud 921600 write_flash 0x10000 release/latest/bins/firmware-tbeam-US-$VERSION.bin
|
||||
|
||||
6
bin/program-release-universal.sh
Executable file
6
bin/program-release-universal.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
|
||||
set -e
|
||||
|
||||
source bin/version.sh
|
||||
|
||||
esptool.py --baud 921600 write_flash 0x10000 release/latest/bins/universal/firmware-tbeam-$VERSION.bin
|
||||
@@ -1,6 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.1"
|
||||
set -e
|
||||
|
||||
echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.4 to be located in the"
|
||||
echo "meshtastic-device root directory if the following step fails, you should download the correct"
|
||||
echo "prebuilt binaries for your computer into nanopb-0.4.4"
|
||||
|
||||
# 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
|
||||
../nanopb-0.4.4/generator-bin/protoc --nanopb_out=-v:../src/mesh/generated -I=../proto *.proto
|
||||
|
||||
echo "Regenerating protobuf documentation - if you see an error message"
|
||||
echo "you can ignore it unless doing a new protobuf release to github."
|
||||
bin/regen-docs.sh
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
echo "Converting to uf2 for NRF52 Adafruit bootloader"
|
||||
bin/uf2conv.py .pio/build/lora-relay-v1/firmware.hex -f 0xADA52840
|
||||
bin/uf2conv.py .pio/build/lora-relay-v2/firmware.hex -f 0xADA52840
|
||||
# cp flash.uf2 /media/kevinh/FTH*BOOT/
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
|
||||
|
||||
export VERSION=1.1.4
|
||||
2
bin/view-map.sh
Executable file
2
bin/view-map.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
echo using amap tool to display memory map
|
||||
amap .pio/build/output.map
|
||||
@@ -5,7 +5,7 @@
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_LORA_RELAY_V1 -DNRF52840_XXAA",
|
||||
"extra_flags": "-DARDUINO_NRF52840_TTGO_EINK -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
[
|
||||
|
||||
46
boards/lora-relay-v2.json
Normal file
46
boards/lora-relay-v2.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_LORA_RELAY_V2 -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [["0x239A", "0x4406"]],
|
||||
"usb_product": "LORA_RELAY",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "lora_relay_v2",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52840.svd"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Meshtastic Lora Relay V1 (Adafruit BSP)",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"require_upload_port": true,
|
||||
"speed": 115200,
|
||||
"protocol": "jlink",
|
||||
"protocols": ["jlink", "nrfjprog", "stlink"]
|
||||
},
|
||||
"url": "https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay",
|
||||
"vendor": "BigCorvus"
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
"ldscript": "nrf52840_s113_v7.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
@@ -16,9 +16,9 @@
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_flags": "-DS113",
|
||||
"sd_name": "s113",
|
||||
"sd_version": "7.2.0",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
|
||||
46
boards/ppr1.json
Normal file
46
boards/ppr1.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52833_s113_v7.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52833_PPR -DNRF52833_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [["0x239A", "0x4406"]],
|
||||
"usb_product": "PPR",
|
||||
"mcu": "nrf52833",
|
||||
"variant": "ppr",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS113",
|
||||
"sd_name": "s113",
|
||||
"sd_version": "7.2.0",
|
||||
"sd_fwid": "0x00b6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52833_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52833.svd"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Meshtastic PPR1 (Adafruit BSP)",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"require_upload_port": true,
|
||||
"speed": 115200,
|
||||
"protocol": "jlink",
|
||||
"protocols": ["jlink", "nrfjprog", "stlink"]
|
||||
},
|
||||
"url": "https://meshtastic.org/",
|
||||
"vendor": "Othernet"
|
||||
}
|
||||
43
data/static/basic.js
Normal file
43
data/static/basic.js
Normal file
@@ -0,0 +1,43 @@
|
||||
var meshtasticClient;
|
||||
var connectionOne;
|
||||
|
||||
|
||||
// Important: the connect action must be called from a user interaction (e.g. button press), otherwise the browsers won't allow the connect
|
||||
function connect() {
|
||||
|
||||
// Create new connection
|
||||
var httpconn = new meshtasticjs.IHTTPConnection();
|
||||
|
||||
// Set connection params
|
||||
let sslActive;
|
||||
if (window.location.protocol === 'https:') {
|
||||
sslActive = true;
|
||||
} else {
|
||||
sslActive = false;
|
||||
}
|
||||
let deviceIp = window.location.hostname; // Your devices IP here
|
||||
|
||||
|
||||
// Add event listeners that get called when a new packet is received / state of device changes
|
||||
httpconn.addEventListener('fromRadio', function (packet) { console.log(packet) });
|
||||
|
||||
// Connect to the device async, then send a text message
|
||||
httpconn.connect(deviceIp, sslActive)
|
||||
.then(result => {
|
||||
|
||||
alert('device has been configured')
|
||||
// This gets called when the connection has been established
|
||||
// -> send a message over the mesh network. If no recipient node is provided, it gets sent as a broadcast
|
||||
return httpconn.sendText('meshtastic is awesome');
|
||||
|
||||
})
|
||||
.then(result => {
|
||||
|
||||
// This gets called when the message has been sucessfully sent
|
||||
console.log('Message sent!');
|
||||
})
|
||||
|
||||
.catch(error => { console.log(error); });
|
||||
|
||||
}
|
||||
|
||||
18
data/static/index.html
Normal file
18
data/static/index.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<html class="no-js" lang="">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title></title>
|
||||
|
||||
<script src="/static/meshtastic.js"></script>
|
||||
<script src="/static/basic.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<button id="connect_button" onclick="connect()">Connect to Meshtastic device</button>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
data/static/meshtastic.js.gz
Normal file
BIN
data/static/meshtastic.js.gz
Normal file
Binary file not shown.
@@ -39,6 +39,10 @@ This software is 100% open source and developed by a group of hobbyist experimen
|
||||
|
||||
For an detailed walk-through aimed at beginners, we recommend [meshtastic.letstalkthis.com](https://meshtastic.letstalkthis.com/).
|
||||
|
||||
### Related Groups
|
||||
|
||||
Telegram group for **Italy**-based users [t.me/meshtastic_italia](http://t.me/meshtastic_italia) (Italian language, unofficial).
|
||||
|
||||
# Updates
|
||||
|
||||
Note: Updates are happening almost daily, only major updates are listed below. For more details see our forum.
|
||||
|
||||
BIN
docs/hardware/LoRa Design Guide.pdf
Normal file
BIN
docs/hardware/LoRa Design Guide.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/pinetab/PineTab LoRa schematic.pdf
Normal file
BIN
docs/hardware/pinetab/PineTab LoRa schematic.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/pinetab/SX1302/DS_SX1302_V1.0.pdf
Normal file
BIN
docs/hardware/pinetab/SX1302/DS_SX1302_V1.0.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/pinetab/SX1302/M-GW1302S 用户手册(2)(1)(1).pdf
Normal file
BIN
docs/hardware/pinetab/SX1302/M-GW1302S 用户手册(2)(1)(1).pdf
Normal file
Binary file not shown.
BIN
docs/hardware/pinetab/SX1302/M-GW1302S(射频版)硬件设计手册_V1.1.pdf
Normal file
BIN
docs/hardware/pinetab/SX1302/M-GW1302S(射频版)硬件设计手册_V1.1.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/pinetab/SX1302/M-GW1302(透传版)_硬件设计手册.pdf
Normal file
BIN
docs/hardware/pinetab/SX1302/M-GW1302(透传版)_硬件设计手册.pdf
Normal file
Binary file not shown.
990
docs/hardware/pinetab/ch341h_datasheet.pdf
Normal file
990
docs/hardware/pinetab/ch341h_datasheet.pdf
Normal file
File diff suppressed because one or more lines are too long
BIN
docs/hardware/pinetab/nfnfaceblhkepdph.jpg
Normal file
BIN
docs/hardware/pinetab/nfnfaceblhkepdph.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 272 KiB |
BIN
docs/hardware/pinetab/nhndndegjjflkibg.jpg
Normal file
BIN
docs/hardware/pinetab/nhndndegjjflkibg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 417 KiB |
@@ -2,13 +2,13 @@
|
||||
|
||||
We use the same channel maps as LoRaWAN (though this is not LoRaWAN).
|
||||
|
||||

|
||||

|
||||
|
||||
See [this site](https://www.rfwireless-world.com/Tutorials/LoRa-channels-list.html) for more information.
|
||||
|
||||
## LoRaWAN Europe Frequency Band
|
||||
|
||||
The maximum power allowed is +14dBM.
|
||||
The maximum power allowed is +14dBm ERP (Effective Radiated Power, see [this site](https://en.wikipedia.org/wiki/Effective_radiated_power) for more information).
|
||||
|
||||
### 433 MHz
|
||||
|
||||
@@ -24,7 +24,7 @@ Channel zero starts at 865.20 MHz
|
||||
|
||||
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 maximum output power for North America is +30 dBm ERP.
|
||||
|
||||
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.
|
||||
Channel zero starts at 903.08 MHz center frequency.
|
||||
|
||||
@@ -2,31 +2,96 @@
|
||||
|
||||
You probably don't care about this section - skip to the next one.
|
||||
|
||||
Threading tasks:
|
||||
For app cleanup:
|
||||
|
||||
- Use https://github.com/ivanseidel/ArduinoThread? rather than full coroutines
|
||||
- clean up main loop()
|
||||
- check that we are mostly asleep, show which thread is causing us to wake
|
||||
-
|
||||
- use tickless idle on nrf52, and sleep X msec or until an interrupt occurs or the cooperative scheduling changes. https://devzone.nordicsemi.com/f/nordic-q-a/12363/nrf52-freertos-power-consumption-tickless-idle
|
||||
- BAD IDEA: use vTaskDelay and https://www.freertos.org/xTaskAbortDelay.html if scheduling changes. (define INCLUDE_xTaskAbortDelay on ESP32 and NRF52 - seems impossible to find?)
|
||||
- GOOD IDEA: use xSemaphoreTake to take a semaphore using a timeout. Expect semaphore to not be set, but set it to indicate scheduling has changed.
|
||||
* use structured logging to kep logs in ram. Also send logs as packets to api clients
|
||||
* DONE writeup nice python options docs (common cases, link to protobuf docs)
|
||||
* have android app link to user manual
|
||||
* DONE only do wantReplies once per packet type, if we change network settings force it again
|
||||
* update positions and nodeinfos based on packets we just merely witness on the mesh. via isPromsciousPort bool, remove sniffing
|
||||
* DONE make device build always have a valid version
|
||||
* DONE do fixed position bug https://github.com/meshtastic/Meshtastic-device/issues/536
|
||||
* DONE check build guide
|
||||
* DONE write devapi user guide
|
||||
* DONE update android code: https://developer.android.com/topic/libraries/view-binding/migration
|
||||
* DONE test GPIO watch
|
||||
* DONE set --set-chan-fast, --set-chan-default
|
||||
* writeup docs on gpio
|
||||
* DONE make python ping command
|
||||
* DONE make hello world example service
|
||||
* DONE have python tool check max packet size before sending to device
|
||||
* DONE if request was sent reliably, send reply reliably
|
||||
* DONE require a recent python api to talk to these new device loads
|
||||
* DONE require a recent android app to talk to these new device loads
|
||||
* DONE fix handleIncomingPosition
|
||||
* DONE move want_replies handling into plugins
|
||||
* DONE on android for received positions handle either old or new positions / user messages
|
||||
* DONE on android side send old or new positions as needed / user messages
|
||||
* DONE test python side handle new position/user messages
|
||||
* DONE make a gpio example. --gpiowrb 4 1, --gpiord 0x444, --gpiowatch 0x3ff
|
||||
* DONE fix position sending to use new plugin
|
||||
* DONE Add SinglePortNumPlugin - as the new most useful baseclass
|
||||
* DONE move positions into regular data packets (use new app framework)
|
||||
* DONE move user info into regular data packets (use new app framework)
|
||||
* DONE test that positions, text messages and user info still work
|
||||
* DONE test that position, text messages and user info work properly with new android app and old device code
|
||||
* DONE do UDP tunnel
|
||||
* DONE fix the RTC drift bug
|
||||
* move python ping functionality into device, reply with rxsnr info
|
||||
* use channels for gpio security https://github.com/meshtastic/Meshtastic-device/issues/104
|
||||
* MeshPackets for sending should be reference counted so that API clients would have the option of checking sent status (would allow removing the nasty 30 sec timer in gpio watch sending)
|
||||
|
||||
Nimble tasks:
|
||||
For high speed/lots of devices/short range tasks:
|
||||
|
||||
- readerror.txt stress test bug
|
||||
- started RPA long test, jul 22 6pm
|
||||
- implement nimble software update api
|
||||
- update to latest bins, test OTA again (measure times) and then checkin bins
|
||||
- do alpha release
|
||||
- When guessing numhops for sending: if I've heard from many local (0 hop neighbors) decrease hopcount by 2 rather than 1.
|
||||
This should nicely help 'router' nodes do the right thing when long range, or if there are many local nodes for short range.
|
||||
- fix timeouts/delays to be based on packet length at current radio settings
|
||||
|
||||
* update protocol description per cyclomies email thread
|
||||
* update faq with antennas https://meshtastic.discourse.group/t/range-test-ideas-requested/738/2
|
||||
* update faq on recommended android version and phones
|
||||
* add help link inside the app, reference a page on the wiki
|
||||
* turn on amazon reviews support
|
||||
* add a tablet layout (with map next to messages) in the android app
|
||||
|
||||
# Old docs to merge
|
||||
|
||||
MESH RADIO PROTOCOL
|
||||
|
||||
Old TODO notes on the mesh radio protocol, merge into real docs someday...
|
||||
|
||||
for each named group we have a pre-shared key known by all group members and
|
||||
wrapped around the device. you can only be in one group at a time (FIXME?!) To
|
||||
join the group we read a qr code with the preshared key and ParamsCodeEnum. that
|
||||
gets sent via bluetooth to the device. ParamsCodeEnum maps to a set of various
|
||||
radio params (regulatory region, center freq, SF, bandwidth, bitrate, power
|
||||
etc...) so all members of the mesh can have their radios set the same way.
|
||||
|
||||
once in that group, we can talk between 254 node numbers.
|
||||
to get our node number (and announce our presence in the channel) we pick a
|
||||
random node number and broadcast as that node with WANT-NODENUM(my globally
|
||||
unique name). If anyone on the channel has seen someone _else_ using that name
|
||||
within the last 24 hrs(?) they reply with DENY-NODENUM. Note: we might receive
|
||||
multiple denies. Note: this allows others to speak up for some other node that
|
||||
might be saving battery right now. Any time we hear from another node (for any
|
||||
message type), we add that node number to the unpickable list. To dramatically
|
||||
decrease the odds a node number we request is already used by someone. If no one
|
||||
denies within TBD seconds, we assume that we have that node number. As long as
|
||||
we keep talking to folks at least once every 24 hrs, others should remember we
|
||||
have it.
|
||||
|
||||
Once we have a node number we can broadcast POSITION-UPDATE(my globally unique
|
||||
name, lat, lon, alt, amt battery remaining). All receivers will use this to a)
|
||||
update the mapping of who is at what node nums, b) the time of last rx, c)
|
||||
position. If we haven't heard from that node in a while we reply to that node
|
||||
(only) with our current POSITION_UPDATE state - so that node (presumably just
|
||||
rejoined the network) can build a map of all participants.
|
||||
|
||||
We will periodically broadcast POSITION-UPDATE as needed based on distance moved
|
||||
or a periodic minimum heartbeat.
|
||||
|
||||
If user wants to send a text they can SEND_TEXT(dest user, short text message).
|
||||
Dest user is a node number, or 0xff for broadcast.
|
||||
|
||||
# Medium priority
|
||||
|
||||
Items to complete before 1.0.
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
# Build instructions
|
||||
|
||||
This project uses the simple PlatformIO build system. PlatformIO is an extension to Microsoft VSCode.
|
||||
This project uses the simple PlatformIO build system. PlatformIO is an extension to Microsoft VSCode. Workflows from building from the GUI or from the commandline are listed below.
|
||||
|
||||
If you encounter any problems, please post a question in [our forum](meshtastic.discourse.group). And when you learn a fix, update these instructions for the next person (i.e. edit this file and send in a [pull-request](https://opensource.com/article/19/7/create-pull-request-github) which we will eagerly merge).
|
||||
|
||||
## GUI
|
||||
|
||||
1. Purchase a suitable [radio](https://github.com/meshtastic/Meshtastic-device/wiki/Hardware-Information).
|
||||
2. Install [Python](https://www.python.org/downloads/).
|
||||
3. Install [Git](https://git-scm.com/downloads).
|
||||
@@ -19,6 +22,7 @@ This project uses the simple PlatformIO build system. PlatformIO is an extension
|
||||
Note - To get a clean build you may have to delete the auto-generated file `./.vscode/c_cpp_properties.json`, close and re-open Visual Studio and WAIT until the file is auto-generated before compiling again.
|
||||
|
||||
## Command Line
|
||||
|
||||
1. Purchase a suitable [radio](https://github.com/meshtastic/Meshtastic-device/wiki/Hardware-Information).
|
||||
2. Install [PlatformIO](https://platformio.org/platformio-ide)
|
||||
3. Download this git repo and cd into it:
|
||||
@@ -35,9 +39,20 @@ cd Meshtastic-device
|
||||
|
||||
## Decoding stack traces
|
||||
|
||||
### Option 1
|
||||
|
||||
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).
|
||||
|
||||
### Option 2
|
||||
|
||||
You can run the exception decoder to monitor the serial output and decode backtraces in real time.
|
||||
|
||||
1. From within PlatformIO, open a new terminal.
|
||||
2. At the the terminal, enter:
|
||||
`pio device monitor --port /dev/cu.SLAB_USBtoUART -f esp32_exception_decoder`
|
||||
Replace the value of port with the location of your serial port.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# Device API
|
||||
# Bluetooth/serial/TCP protocol API
|
||||
|
||||
(This document describes the protocol for external API clients using our devices. If you are interested in running your own code on the device itself, see the [on-device](plugin-api.md) documentation instead)
|
||||
|
||||
The Device API is design to have only a simple stream of ToRadio and FromRadio packets and all 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.
|
||||
|
||||
@@ -42,7 +44,6 @@ Expected sequence for initial download:
|
||||
- Read a RadioConfig from "radio" - used to get the channel and radio settings
|
||||
- Read a User from "user" - to get the username for this node
|
||||
- Read a MyNodeInfo from "mynode" to get information about this local device
|
||||
- Write an empty record to "nodeinfo" to restart the nodeinfo reading state machine
|
||||
- Read a series of NodeInfo packets to build the phone's copy of the current NodeDB for the mesh
|
||||
- Read a endConfig packet that indicates that the entire state you need has been sent.
|
||||
- Read a series of MeshPackets until it returns empty to get any messages that arrived for this node while the phone was away
|
||||
@@ -112,6 +113,7 @@ Characteristics
|
||||
| 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 |
|
||||
| 5e134862-7411-4424-ac4a-210937432c67 | write | sets the region for programming, currently only 0 (app) or 100 (spiffs) are defined, if not set app is assumed |
|
||||
| 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 | |
|
||||
|
||||
@@ -10,7 +10,7 @@ you'll automatically get our fixed libraries.
|
||||
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
|
||||
kevinh@kevin-server:~/development/meshtastic/esp32-arduino-lib-builder\$ python /home/kevinh/development/meshtastic/
|
||||
cp -a out/tools/sdk/* components/arduino/tools/sdk
|
||||
cp -ar components/arduino/* ~/.platformio/packages/framework-arduinoespressif32
|
||||
|
||||
@@ -21,3 +21,9 @@ you'll automatically get our fixed libraries.
|
||||
cp -ar out/tools/sdk/* ~/.platformio/packages/framework-arduinoespressif32/tools/sdk
|
||||
|
||||
```
|
||||
|
||||
How to flash new bootloader
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
192
docs/software/mqtt.md
Normal file
192
docs/software/mqtt.md
Normal file
@@ -0,0 +1,192 @@
|
||||
|
||||
# Table of Contents
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Abstract](#abstract)
|
||||
- [Short term goals](#short-term-goals)
|
||||
- [Long term goals](#long-term-goals)
|
||||
- [Multiple Channel support / Security](#multiple-channel-support--security)
|
||||
- [On device API](#on-device-api)
|
||||
- [MQTT transport](#mqtt-transport)
|
||||
- [Topics](#topics)
|
||||
- [Service Envelope](#service-envelope)
|
||||
- [NODEID](#nodeid)
|
||||
- [USERID](#userid)
|
||||
- [CHANNELID](#channelid)
|
||||
- [Gateway nodes](#gateway-nodes)
|
||||
- [Optional web services](#optional-web-services)
|
||||
- [Public MQTT broker service](#public-mqtt-broker-service)
|
||||
- [Riot.im messaging bridge](#riotim-messaging-bridge)
|
||||
- [Deprecated concepts](#deprecated-concepts)
|
||||
- [MESHID (deprecated)](#meshid-deprecated)
|
||||
- [DESTCLASS (deprecated)](#destclass-deprecated)
|
||||
- [DESTID (deprecated)](#destid-deprecated)
|
||||
- [Rejected idea: RAW UDP](#rejected-idea-raw-udp)
|
||||
- [Development plan](#development-plan)
|
||||
- [Work items](#work-items)
|
||||
- [Enhancements in following releases](#enhancements-in-following-releases)
|
||||
|
||||
## Abstract
|
||||
|
||||
This is a mini-doc/RFC sketching out a development plan to satisfy a number of 1.1 goals.
|
||||
|
||||
- [MQTT](https://opensource.com/article/18/6/mqtt) internet accessible API. Issue #[369](https://github.com/meshtastic/Meshtastic-device/issues/169)
|
||||
- An open API to easily run custom mini-apps on the devices
|
||||
- A text messaging bridge when a node in the mesh can gateway to the internet. Issue #[353](https://github.com/meshtastic/Meshtastic-device/issues/353) and this nicely documented [android issue](https://github.com/meshtastic/Meshtastic-Android/issues/2).
|
||||
- An easy way to let desktop app developers remotely control GPIOs. Issue #[182](https://github.com/meshtastic/Meshtastic-device/issues/182)
|
||||
- Remote attribute access (to change settings of distant nodes). Issue #182
|
||||
|
||||
## Short term goals
|
||||
|
||||
- We want a clean API for novice developers to write mini "apps" that run **on the device** with the existing messaging/location "apps".
|
||||
- We want the ability to have a gateway web service, so that if any node in the mesh can connect to the internet (via its connected phone app or directly) then that node will provide bidirectional messaging between nodes and the internet.
|
||||
- We want an easy way for novice developers to remotely read and control GPIOs (because this is an often requested use case), without those developers having to write any device code.
|
||||
- We want a way to gateway text messaging between our current private meshes and the broader internet (when that mesh is able to connect to the internet)
|
||||
- We want a way to remotely set any device/channel parameter on a node. This is particularly important for administering physically inaccessible router nodes. Ideally this mechanism would also be used for administering the local node (so one common mechanism for both cases).
|
||||
- This work should be independent of our current (semi-custom) LoRa transport, so that in the future we can swap out that transport if we wish (to QMesh or Reticulum?)
|
||||
- Our networks are (usually) very slow and low bandwidth, so the messaging must be very airtime efficient.
|
||||
|
||||
## Long term goals
|
||||
|
||||
- Store and forward messaging should be supported, so apps can send messages that might be delivered to their destination in **hours** or **days** if a node/mesh was partitioned.
|
||||
|
||||
## Multiple Channel support / Security
|
||||
|
||||
Mini-apps API can bind to particular channels. They will only see messages sent on that channel.
|
||||
|
||||
During the 1.0 timeframe only one channel was supported per node. Starting in the 1.1 tree we will do things like "remote admin operations / channel settings etc..." are on the "Control" channel and only especially trusted users should have the keys to access that channel.
|
||||
|
||||
FIXME - explain this more, talk about how useful for users and security domains.
|
||||
- add channels as security
|
||||
|
||||
## On device API
|
||||
|
||||
For information on the related on-device API see [here](device-api.md).
|
||||
|
||||
## MQTT transport
|
||||
|
||||
Any gateway-device will contact the MQTT broker.
|
||||
|
||||
### Topics
|
||||
|
||||
The "mesh/crypt/CHANNELID/NODEID/PORTID" [topic](https://www.hivemq.com/blog/mqtt-essentials-part-5-mqtt-topics-best-practices/) will be used for messages sent from/to a mesh.
|
||||
|
||||
Gateway nodes will foward any MeshPacket from a local mesh channel with uplink_enabled. The packet (encapsulated in a ServiceEnvelope) will remain encrypted with the key for the specified channel.
|
||||
|
||||
For any channels in the local node with downlink_enabled, the gateway node will forward packets from MQTT to the local mesh. It will do this by subscribing to mesh/crypt/CHANNELID/# and forwarding relevant packets.
|
||||
|
||||
If the channelid 'well known'/public it could be decrypted by a web service (if the web service was provided with the associated channel key), in which case it will be decrypted by a web service and appear at "mesh/clear/CHANNELID/NODEID/PORTID". Note: This is not in the initial deliverable.
|
||||
|
||||
FIXME, discuss how text message global mirroring could scale (or not)
|
||||
FIXME, possibly don't global mirror text messages - instead rely on matrix/riot?
|
||||
FIXME, discuss possible attacks by griefers and how they can be prvented
|
||||
|
||||
#### Service Envelope
|
||||
|
||||
The payload published on mesh/... will always be wrapped in a [ServiceEnvelope protobuf](https://github.com/meshtastic/Meshtastic-protobufs/blob/master/docs/docs.md#.ServiceEnvelope).
|
||||
|
||||
ServiceEnvelope will include the message, and full information about arrival time, who forwarded it, source channel, source mesh id, etc...
|
||||
|
||||
#### NODEID
|
||||
|
||||
The unique ID for a node. A hex string that starts with a ! symbol.
|
||||
|
||||
#### USERID
|
||||
|
||||
A user ID string. This string is either a user ID if known or a nodeid to simply deliver the message to whoever the local user is of a particular device (i.e. person who might see the screen). FIXME, see what riot.im uses and perhaps use that convention? Or use the signal +phone number convention? Or the email addr?
|
||||
|
||||
#### CHANNELID
|
||||
|
||||
FIXME, figure out how channelids work
|
||||
|
||||
### Gateway nodes
|
||||
|
||||
Any meshtastic node that has a direct connection to the internet (either via a helper app or installed wifi/4G/satellite hardware) can function as a "Gateway node".
|
||||
|
||||
Gateway nodes (via code running in the phone) will contain two tables to whitelist particular traffic to either be delivered toward the internet, or down toward the mesh. Users that are developing custom apps will be able to customize these filters/subscriptions.
|
||||
|
||||
Since multiple gateway nodes might be connected to a single mesh, it is possible that duplicate messages will be published on any particular topic. Therefore subscribers to these topics should
|
||||
deduplicate if needed by using the packet ID of each message.
|
||||
|
||||
### Optional web services
|
||||
|
||||
#### Public MQTT broker service
|
||||
|
||||
An existing public [MQTT broker](https://mosquitto.org/) will be the default for this service, but clients can use any MQTT broker they choose.
|
||||
|
||||
FIXME - figure out how to avoid impersonation (because we are initially using a public mqtt server with no special security options). FIXME, include some ideas on this in the ServiceEnvelope documentation.
|
||||
|
||||
#### Riot.im messaging bridge
|
||||
|
||||
@Geeksville will run a riot.im bridge that talks to the public MQTT broker and sends/receives into the riot.im network.
|
||||
|
||||
There is apparently [already](https://github.com/derEisele/tuple) a riot.im [bridge](https://matrix.org/bridges/) for MQTT. That will possibly need to be customized a bit. But by doing this, we should be able to let random riot.im users send/receive messages to/from any meshtastic device. (FIXME ponder security). See this [issue](https://github.com/meshtastic/Meshtastic-Android/issues/2#issuecomment-645660990) with discussion with the dev.
|
||||
|
||||
### Deprecated concepts
|
||||
|
||||
You can ignore these for now...
|
||||
|
||||
#### MESHID (deprecated)
|
||||
|
||||
Earlier drafts of this document included the concept of a MESHID. That concept has been removed for now, but might be useful in the future. The old idea is listed below:
|
||||
|
||||
A unique ID for this mesh. There will be some sort of key exchange process so that the mesh ID can not be impersonated by other meshes.
|
||||
|
||||
#### DESTCLASS (deprecated)
|
||||
|
||||
Earlier drafts of this document included the concept of a DESTCLASS. That concept has been removed for now, but might be useful in the future. The old idea is listed below:
|
||||
|
||||
The type of DESTID this message should be delivered to. A short one letter sequence:
|
||||
|
||||
| Symbol | Description |
|
||||
| ------ | ------------------------------------------------------------- |
|
||||
| R | riot.im |
|
||||
| L | local mesh node ID or ^all |
|
||||
| A | an application specific message, ID will be an APP ID |
|
||||
| S | SMS gateway, DESTID is a phone number to reach via Twilio.com |
|
||||
| E | Emergency message, see bug #fixme for more context |
|
||||
|
||||
#### DESTID (deprecated)
|
||||
|
||||
Earlier drafts of this document included the concept of a DESTCLASS. That concept has been removed for now, but might be useful in the future. The old idea is listed below:
|
||||
|
||||
Can be...
|
||||
|
||||
- an internet username: kevinh@geeksville.com
|
||||
- ^ALL for anyone
|
||||
- An app ID (to allow apps out in the web to receive arbitrary binary data from nodes or simply other apps using meshtastic as a transport). They would connect to the MQTT broker and subscribe to their topic
|
||||
|
||||
## Rejected idea: RAW UDP
|
||||
|
||||
A number of commenters have requested/proposed using UDP for the transport. We've considered this option and decided to use MQTT instead for the following reasons:
|
||||
|
||||
- Most UDP uses cases would need to have a server anyways so that nodes can reach each other from anywhere (i.e. if most gateways will be behind some form of NAT which would need to be tunnelled)
|
||||
- Raw UDP is dropped **very** agressively by many cellular providers. MQTT from the gateway to a broker can be done over a TCP connection for this reason.
|
||||
- MQTT provides a nice/documented/standard security model to build upon
|
||||
- MQTT is fairly wire efficient with multiple broker implementations/providers and numerous client libraries for any language. The actual implementation of MQTT is quite simple.
|
||||
|
||||
## Development plan
|
||||
|
||||
Given the previous problem/goals statement, here's the initial thoughts on the work items required. As this idea becomes a bit more fully baked we should add details
|
||||
on how this will be implemented and guesses at approximate work items.
|
||||
|
||||
### Work items
|
||||
|
||||
- Change nodeIDs to be base64 instead of eight hex digits.
|
||||
- DONE Refactor the position features into a position "mini-app". Use only the new public on-device API to implement this app.
|
||||
- DONE Refactor the on device texting features into a messaging "mini-app". (Similar to the position mini-app)
|
||||
- Add new multi channel concept
|
||||
- Send new channels to python client
|
||||
- Let python client add channels
|
||||
- Add portion of channelid to the raw lora packet header
|
||||
- Confirm that we can now forward encrypted packets without decrypting at each node
|
||||
- Use a channel named "remotehw" to secure the GPIO service. If that channel is not found, don't even start the service. Document this as the standard method for securing services.
|
||||
- Add first cut of the "gateway node" code (i.e. MQTT broker client) to the python API (very little code needed for this component)
|
||||
- Confirm that texting works to/from the internet
|
||||
- Confirm that positions are optionally sent to the internet
|
||||
- Add the first cut of the "gateway node" code to the android app (very little code needed for this component)
|
||||
|
||||
### Enhancements in following releases
|
||||
|
||||
The initial gateway will be added to the python tool. But the gateway implementation is designed to be fairly trivial/dumb. After the initial release the actual gateway code can be ported to also run inside of the android app. In fact, we could have ESP32 based nodes include a built-in "gateway node" implementation.
|
||||
|
||||
Store and forward could be added so that nodes on the mesh could deliver messages (i.e. text messages) on an "as possible" basis. This would allow things like "hiker sends a message to friend - mesh can not currently reach friend - eventually (days later) mesh can somehow reach friend, message gets delivered"
|
||||
@@ -5,7 +5,27 @@
|
||||
|
||||
## RAK815
|
||||
|
||||
TODO:
|
||||
### PPR1 TODO
|
||||
|
||||
* V_BK for the GPS should probably be supplied from something always on
|
||||
|
||||
* use S113 soft device 7.2.0
|
||||
* properly test charge controller config and read battery/charge status
|
||||
* fix bluetooth
|
||||
* fix LCD max contrast (currently too high, needs to be about 40?)
|
||||
* save brightness settings in flash
|
||||
* make ST7567Wire driver less ugly, move OLED stuff into a common class treee
|
||||
* add LCD power save mode for lcd per page 31 of datasheet
|
||||
* add LCD power off sequence per datasheet to lcd driver
|
||||
* leave LCD screen on most of the time (because it needs little power)
|
||||
|
||||
### general nrf52 TODO:
|
||||
|
||||
- turn off transitions on eink screens
|
||||
- change update interval on eink from 1/sec frames to one frame every 5 mins
|
||||
- enter SDS state at correct time (to protect battery or loss of phone contact)
|
||||
- show screen on eink when we enter SDS state (with app info and say sleeping)
|
||||
- require button press to pair
|
||||
|
||||
- shrink soft device RAM usage
|
||||
- get nrf52832 working again (currently OOM)
|
||||
|
||||
@@ -2,18 +2,25 @@
|
||||
|
||||
These are **preliminary** notes on support for Meshtastic in the Pinetab.
|
||||
|
||||
A RF95 is connected via a CS341 USB-SPI chip.
|
||||
A RF95 is connected via a CH341 USB-SPI chip.
|
||||
|
||||
Pin assignments:
|
||||
CS0 from RF95 goes to CS0 on CS341
|
||||
DIO0 from RF95 goes to INT on CS341
|
||||
RST from RF95 goes to RST on CS341
|
||||
CS0 from RF95 goes to CS0 on CH341
|
||||
DIO0 from RF95 goes to INT on CH341
|
||||
RST from RF95 goes to RST on CH341
|
||||
|
||||
This linux driver claims to provide USB-SPI support: https://github.com/gschorcht/spi-ch341-usb
|
||||
Notes here on using that driver: https://www.linuxquestions.org/questions/linux-hardware-18/ch341-usb-to-spi-adaptor-driver-doesn%27t-work-4175622736/
|
||||
|
||||
Or if **absolutely** necessary could bitbang: https://www.cnx-software.com/2018/02/16/wch-ch341-usb-to-serial-chip-gets-linux-driver-to-control-gpios-over-usb/
|
||||
|
||||
## Portduino tasks
|
||||
|
||||
* How to access spi devices via ioctl (spidev): https://www.raspberrypi.org/documentation/hardware/raspberrypi/spi/README.md#:~:text=Troubleshooting-,Overview,bus)%2C%20UARTs%2C%20etc.
|
||||
* access gpio via libgpiod?
|
||||
* Use dkms to distribute driver?
|
||||
* echo 100 > /sys/module/spi_ch341_usb/parameters/poll_period
|
||||
|
||||
## Task list
|
||||
|
||||
* Port meshtastic to build (under platformio) for a poxix target. spec: no screen, no gpios, sim network interface, posix threads, posix semaphores & queues, IO to the console only
|
||||
|
||||
81
docs/software/plugin-api.md
Normal file
81
docs/software/plugin-api.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Plugin-API
|
||||
|
||||
This is a tutorial on how to write small plugins which run on the device. Plugins are bits regular 'arduino' code that can send and receive packets to other nodes/apps/PCs using our mesh.
|
||||
|
||||
## Key concepts
|
||||
|
||||
All plugins should be subclasses of MeshPlugin. By inheriting from this class and creating an instance of your new plugin your plugin will be automatically registered to receive packets.
|
||||
|
||||
Messages are sent to particular port numbers (similar to UDP networking). Your new plugin should eventually pick its own port number (see below), but at first you can simply use PRIVATE_APP (which is the default).
|
||||
|
||||
Packets can be sent/received either as raw binary structures or as [Protobufs](https://developers.google.com/protocol-buffers).
|
||||
|
||||
### Class heirarchy
|
||||
|
||||
The relevant bits of the class heirarchy are as follows
|
||||
|
||||
* [MeshPlugin](/src/mesh/MeshPlugin.h) (in src/mesh/MeshPlugin.h) - you probably don't want to use this baseclass directly
|
||||
* [SinglePortPlugin](/src/mesh/SinglePortPlugin.h) (in src/mesh/SinglePortPlugin.h) - for plugins that send/receive from a single port number (the normal case)
|
||||
* [ProtobufPlugin](/src/mesh/ProtobufPlugin.h) (in src/mesh/ProtobufPlugin.h) - for plugins that send/receive a single particular Protobuf type. Inherit from this if you are using protocol buffers in your plugin.
|
||||
|
||||
You will typically want to inherit from either SinglePortPlugin (if you are just sending/receiving raw bytes) or ProtobufPlugin (if you are sending/receiving protobufs). You'll implement your own handleReceived/handleReceivedProtobuf - probably based on the example code.
|
||||
|
||||
If your plugin needs to perform any operations at startup you can override and implement the setup() method to run your code.
|
||||
|
||||
If you need to send a packet you can call service.sendToMesh with code like this (from the examples):
|
||||
|
||||
```
|
||||
MeshPacket *p = allocReply();
|
||||
p->to = dest;
|
||||
|
||||
service.sendToMesh(p);
|
||||
```
|
||||
|
||||
## Example plugins
|
||||
|
||||
A number of [key services](/src/plugins) are implemented using the plugin API, these plugins are as follows:
|
||||
|
||||
* [TextMessagePlugin](/src/plugins/TextMessagePlugin.h) - receives text messages and displays them on the LCD screen/stores them in the local DB
|
||||
* [NodeInfoPlugin](/src/plugins/NodeInfoPlugin.h) - receives/sends User information to other nodes so that usernames are available in the databases
|
||||
* [RemoteHardwarePlugin](/src/plugins/RemoteHardwarePlugin.h) - a plugin that provides easy remote access to device hardware (for things like turning GPIOs on or off). Intended to be a more extensive example and provide a useful feature of its own. See [remote-hardware](remote-hardware.md) for details.
|
||||
* [ReplyPlugin](/src/plugins/ReplyPlugin.h) - a simple plugin that just replies to any packet it receives (provides a 'ping' service).
|
||||
|
||||
## Getting started
|
||||
|
||||
The easiest way to get started is:
|
||||
|
||||
* [Build and install](build-instructions.md) the standard codebase from github.
|
||||
* Copy [src/plugins/ReplyPlugin.*](/src/plugins/ReplyPlugin.cpp) into src/plugins/YourPlugin.*. Then change the port number from *PortNum_REPLY_APP* to *PortNum_PRIVATE_APP*.
|
||||
* Edit plugins/Plugins.cpp:setupPlugins() to add a call to create an instance of your plugin (see comment at head of that function)
|
||||
* Rebuild with your new messaging goodness and install on the device
|
||||
* Use the [meshtastic commandline tool](https://github.com/meshtastic/Meshtastic-python) to send a packet to your board, for example "*meshtastic --dest 1234 --sendping*", where *1234* is another mesh node to send the ping to.
|
||||
|
||||
## Threading
|
||||
|
||||
It is very common that you would like your plugin to be invoked periodically.
|
||||
We use a crude/basic cooperative threading system to allow this on any of our supported platforms. Simply inherit from OSThread and implement runOnce(). See the OSThread [documentation](/src/concurrency/OSThread.h) for more details. For an example consumer of this API see RemoteHardwarePlugin::runOnce.
|
||||
|
||||
## Sending messages
|
||||
|
||||
If you would like to proactively send messages (rather than just responding to them), just call service.sendToMesh(). For an example of this see [NodeInfoPlugin::sendOurNodeInfo(...)](/src/plugins/NodeInfoPlugin.cpp).
|
||||
|
||||
## Picking a port number
|
||||
|
||||
For any new 'apps' that run on the device or via sister apps on phones/PCs they should pick and use a unique 'portnum' for their application.
|
||||
|
||||
If you are making a new app using meshtastic, please send in a pull request to add your 'portnum' to [the master list](https://github.com/meshtastic/Meshtastic-protobufs/blob/master/portnums.proto). PortNums should be assigned in the following range:
|
||||
|
||||
* **0-63** Core Meshtastic use; do not use for third party apps
|
||||
* **64-127** Registered 3rd party apps. Send in a pull request that adds a new entry to portnums.proto to register your application
|
||||
* **256-511** Use one of these portnums for your private applications that you don't want to register publically
|
||||
* **1024-66559** Are reserved for use by IP tunneling (see *FIXME* for more information)
|
||||
|
||||
All other values are reserved.
|
||||
|
||||
## How to add custom protocol buffers
|
||||
|
||||
If you would like to use protocol buffers to define the structures you send over the mesh (recommended), here's how to do that.
|
||||
|
||||
* Create a new .proto file in the protos directory. You can use the existing [remote_hardware.proto](https://github.com/meshtastic/Meshtastic-protobufs/blob/master/remote_hardware.proto) file as an example.
|
||||
* Run "bin/regen-protos.sh" to regenerate the C code for accessing the protocol buffers. If you don't have the required nanopb tool, follow the instructions printed by the script to get it.
|
||||
* Done! You can now use your new protobuf just like any of the existing protobufs in meshtastic.
|
||||
@@ -50,7 +50,7 @@ From lower to higher power consumption.
|
||||
|
||||
- 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: 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 60 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)
|
||||
|
||||
23
docs/software/remote-hardware-service.md
Normal file
23
docs/software/remote-hardware-service.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Remote Hardware Service
|
||||
|
||||
FIXME - the following are a collection of notes moved from elsewhere. We need to refactor these notes into actual documentation on the remote-hardware/gpio service.
|
||||
|
||||
### 1.7.2. New 'no-code-IOT' mini-app
|
||||
|
||||
Add a new 'remote GPIO/serial port/SPI/I2C access' mini-app. This new standard app would use the MQTT messaging layer to let users (developers that don't need to write device code) do basic (potentially dangerous) operations remotely.
|
||||
|
||||
#### 1.7.2.1. Supported operations in the initial release
|
||||
|
||||
Initially supported features for no-code-IOT.
|
||||
|
||||
- Set any GPIO
|
||||
- Read any GPIO
|
||||
|
||||
#### 1.7.2.2. Supported operations eventually
|
||||
|
||||
General ideas for no-code IOT.
|
||||
|
||||
- Subscribe for notification of GPIO input status change (i.e. when pin goes low, send my app a message)
|
||||
- Write/read N bytes over I2C/SPI bus Y (as one atomic I2C/SPI transaction)
|
||||
- Send N bytes out serial port Z
|
||||
- Subscribe for notification for when regex X matches the bytes that were received on serial port Z
|
||||
@@ -1,9 +1,10 @@
|
||||
This is a mini design doc for developing the meshtastic software.
|
||||
|
||||
* [Build instructions](build-instructions.md)
|
||||
* [On device plugin API](plugin-api.md) - a tutorial on how to write small Plugins which run on the device and can message other nodes.
|
||||
* [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)
|
||||
* [Device API](device-api.md) and porting guide for new clients (iOS, python, etc...)
|
||||
* [External client API](device-api.md) and porting guide for new clients (iOS, python, etc...)
|
||||
* TODO: how to port the device code to a new device.
|
||||
|
||||
@@ -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. */
|
||||
@@ -55,7 +55,7 @@
|
||||
|
||||
/* Version of the nanopb library. Just in case you want to check it in
|
||||
* your own program. */
|
||||
#define NANOPB_VERSION nanopb-0.4.1
|
||||
#define NANOPB_VERSION nanopb-0.4.4
|
||||
|
||||
/* Include all the system headers needed by nanopb. You will need the
|
||||
* definitions of the following:
|
||||
@@ -276,17 +276,18 @@ typedef struct pb_field_iter_s pb_field_iter_t;
|
||||
/* This structure is used in auto-generated constants
|
||||
* to specify struct fields.
|
||||
*/
|
||||
PB_PACKED_STRUCT_START
|
||||
typedef struct pb_msgdesc_s pb_msgdesc_t;
|
||||
struct pb_msgdesc_s {
|
||||
pb_size_t field_count;
|
||||
const uint32_t *field_info;
|
||||
const pb_msgdesc_t * const * submsg_info;
|
||||
const pb_byte_t *default_value;
|
||||
|
||||
bool (*field_callback)(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field);
|
||||
} pb_packed;
|
||||
PB_PACKED_STRUCT_END
|
||||
|
||||
pb_size_t field_count;
|
||||
pb_size_t required_field_count;
|
||||
pb_size_t largest_tag;
|
||||
};
|
||||
|
||||
/* Iterator for message descriptor */
|
||||
struct pb_field_iter_s {
|
||||
@@ -469,137 +470,181 @@ struct pb_extension_s {
|
||||
}; \
|
||||
const pb_msgdesc_t structname ## _msg = \
|
||||
{ \
|
||||
0 msgname ## _FIELDLIST(PB_GEN_FIELD_COUNT, structname), \
|
||||
structname ## _field_info, \
|
||||
structname ## _submsg_info, \
|
||||
msgname ## _DEFAULT, \
|
||||
msgname ## _CALLBACK, \
|
||||
0 msgname ## _FIELDLIST(PB_GEN_FIELD_COUNT, structname), \
|
||||
0 msgname ## _FIELDLIST(PB_GEN_REQ_FIELD_COUNT, structname), \
|
||||
0 msgname ## _FIELDLIST(PB_GEN_LARGEST_TAG, structname), \
|
||||
}; \
|
||||
msgname ## _FIELDLIST(PB_GEN_FIELD_INFO_ASSERT_ ## width, structname)
|
||||
|
||||
#define PB_GEN_FIELD_COUNT(structname, atype, htype, ltype, fieldname, tag) +1
|
||||
#define PB_GEN_REQ_FIELD_COUNT(structname, atype, htype, ltype, fieldname, tag) \
|
||||
+ (PB_HTYPE_ ## htype == PB_HTYPE_REQUIRED)
|
||||
#define PB_GEN_LARGEST_TAG(structname, atype, htype, ltype, fieldname, tag) \
|
||||
* 0 + tag
|
||||
|
||||
/* X-macro for generating the entries in struct_field_info[] array. */
|
||||
#define PB_GEN_FIELD_INFO_1(structname, atype, htype, ltype, fieldname, tag) \
|
||||
PB_GEN_FIELD_INFO(1, structname, atype, htype, ltype, fieldname, tag)
|
||||
PB_FIELDINFO_1(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
|
||||
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
|
||||
|
||||
#define PB_GEN_FIELD_INFO_2(structname, atype, htype, ltype, fieldname, tag) \
|
||||
PB_GEN_FIELD_INFO(2, structname, atype, htype, ltype, fieldname, tag)
|
||||
PB_FIELDINFO_2(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
|
||||
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
|
||||
|
||||
#define PB_GEN_FIELD_INFO_4(structname, atype, htype, ltype, fieldname, tag) \
|
||||
PB_GEN_FIELD_INFO(4, structname, atype, htype, ltype, fieldname, tag)
|
||||
PB_FIELDINFO_4(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
|
||||
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
|
||||
|
||||
#define PB_GEN_FIELD_INFO_8(structname, atype, htype, ltype, fieldname, tag) \
|
||||
PB_GEN_FIELD_INFO(8, structname, atype, htype, ltype, fieldname, tag)
|
||||
PB_FIELDINFO_8(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
|
||||
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
|
||||
|
||||
#define PB_GEN_FIELD_INFO_AUTO(structname, atype, htype, ltype, fieldname, tag) \
|
||||
PB_GEN_FIELD_INFO_AUTO2(PB_FIELDINFO_WIDTH_AUTO(atype, htype, ltype), structname, atype, htype, ltype, fieldname, tag)
|
||||
PB_FIELDINFO_AUTO2(PB_FIELDINFO_WIDTH_AUTO(_PB_ATYPE_ ## atype, _PB_HTYPE_ ## htype, _PB_LTYPE_ ## ltype), \
|
||||
tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
|
||||
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
|
||||
|
||||
#define PB_GEN_FIELD_INFO_AUTO2(width, structname, atype, htype, ltype, fieldname, tag) \
|
||||
PB_GEN_FIELD_INFO(width, structname, atype, htype, ltype, fieldname, tag)
|
||||
#define PB_FIELDINFO_AUTO2(width, tag, type, data_offset, data_size, size_offset, array_size) \
|
||||
PB_FIELDINFO_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size)
|
||||
|
||||
#define PB_GEN_FIELD_INFO(width, structname, atype, htype, ltype, fieldname, tag) \
|
||||
PB_FIELDINFO_ ## width(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
|
||||
PB_DATA_OFFSET_ ## atype(htype, structname, fieldname), \
|
||||
PB_DATA_SIZE_ ## atype(htype, structname, fieldname), \
|
||||
PB_SIZE_OFFSET_ ## atype(htype, structname, fieldname), \
|
||||
PB_ARRAY_SIZE_ ## atype(htype, structname, fieldname))
|
||||
#define PB_FIELDINFO_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) \
|
||||
PB_FIELDINFO_ ## width(tag, type, data_offset, data_size, size_offset, array_size)
|
||||
|
||||
/* X-macro for generating asserts that entries fit in struct_field_info[] array.
|
||||
* The structure of macros here must match the structure above in PB_GEN_FIELD_INFO_x(),
|
||||
* but it is not easily reused because of how macro substitutions work. */
|
||||
#define PB_GEN_FIELD_INFO_ASSERT_1(structname, atype, htype, ltype, fieldname, tag) \
|
||||
PB_GEN_FIELD_INFO_ASSERT(1, structname, atype, htype, ltype, fieldname, tag)
|
||||
PB_FIELDINFO_ASSERT_1(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
|
||||
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
|
||||
|
||||
#define PB_GEN_FIELD_INFO_ASSERT_2(structname, atype, htype, ltype, fieldname, tag) \
|
||||
PB_GEN_FIELD_INFO_ASSERT(2, structname, atype, htype, ltype, fieldname, tag)
|
||||
PB_FIELDINFO_ASSERT_2(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
|
||||
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
|
||||
|
||||
#define PB_GEN_FIELD_INFO_ASSERT_4(structname, atype, htype, ltype, fieldname, tag) \
|
||||
PB_GEN_FIELD_INFO_ASSERT(4, structname, atype, htype, ltype, fieldname, tag)
|
||||
PB_FIELDINFO_ASSERT_4(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
|
||||
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
|
||||
|
||||
#define PB_GEN_FIELD_INFO_ASSERT_8(structname, atype, htype, ltype, fieldname, tag) \
|
||||
PB_GEN_FIELD_INFO_ASSERT(8, structname, atype, htype, ltype, fieldname, tag)
|
||||
PB_FIELDINFO_ASSERT_8(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
|
||||
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
|
||||
|
||||
#define PB_GEN_FIELD_INFO_ASSERT_AUTO(structname, atype, htype, ltype, fieldname, tag) \
|
||||
PB_GEN_FIELD_INFO_ASSERT_AUTO2(PB_FIELDINFO_WIDTH_AUTO(atype, htype, ltype), structname, atype, htype, ltype, fieldname, tag)
|
||||
PB_FIELDINFO_ASSERT_AUTO2(PB_FIELDINFO_WIDTH_AUTO(_PB_ATYPE_ ## atype, _PB_HTYPE_ ## htype, _PB_LTYPE_ ## ltype), \
|
||||
tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
|
||||
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
|
||||
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
|
||||
|
||||
#define PB_GEN_FIELD_INFO_ASSERT_AUTO2(width, structname, atype, htype, ltype, fieldname, tag) \
|
||||
PB_GEN_FIELD_INFO_ASSERT(width, structname, atype, htype, ltype, fieldname, tag)
|
||||
#define PB_FIELDINFO_ASSERT_AUTO2(width, tag, type, data_offset, data_size, size_offset, array_size) \
|
||||
PB_FIELDINFO_ASSERT_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size)
|
||||
|
||||
#define PB_GEN_FIELD_INFO_ASSERT(width, structname, atype, htype, ltype, fieldname, tag) \
|
||||
PB_FIELDINFO_ASSERT_ ## width(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
|
||||
PB_DATA_OFFSET_ ## atype(htype, structname, fieldname), \
|
||||
PB_DATA_SIZE_ ## atype(htype, structname, fieldname), \
|
||||
PB_SIZE_OFFSET_ ## atype(htype, structname, fieldname), \
|
||||
PB_ARRAY_SIZE_ ## atype(htype, structname, fieldname))
|
||||
#define PB_FIELDINFO_ASSERT_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) \
|
||||
PB_FIELDINFO_ASSERT_ ## width(tag, type, data_offset, data_size, size_offset, array_size)
|
||||
|
||||
#define PB_DATA_OFFSET_STATIC(htype, structname, fieldname) PB_DATA_OFFSET_ ## htype(structname, fieldname)
|
||||
#define PB_DATA_OFFSET_POINTER(htype, structname, fieldname) PB_DATA_OFFSET_ ## htype(structname, fieldname)
|
||||
#define PB_DATA_OFFSET_CALLBACK(htype, structname, fieldname) PB_DATA_OFFSET_ ## htype(structname, fieldname)
|
||||
#define PB_DATA_OFFSET_REQUIRED(structname, fieldname) offsetof(structname, fieldname)
|
||||
#define PB_DATA_OFFSET_SINGULAR(structname, fieldname) offsetof(structname, fieldname)
|
||||
#define PB_DATA_OFFSET_ONEOF(structname, fieldname) offsetof(structname, PB_ONEOF_NAME(FULL, fieldname))
|
||||
#define PB_DATA_OFFSET_OPTIONAL(structname, fieldname) offsetof(structname, fieldname)
|
||||
#define PB_DATA_OFFSET_REPEATED(structname, fieldname) offsetof(structname, fieldname)
|
||||
#define PB_DATA_OFFSET_FIXARRAY(structname, fieldname) offsetof(structname, fieldname)
|
||||
#define PB_DATA_OFFSET_STATIC(htype, structname, fieldname) PB_DO ## htype(structname, fieldname)
|
||||
#define PB_DATA_OFFSET_POINTER(htype, structname, fieldname) PB_DO ## htype(structname, fieldname)
|
||||
#define PB_DATA_OFFSET_CALLBACK(htype, structname, fieldname) PB_DO ## htype(structname, fieldname)
|
||||
#define PB_DO_PB_HTYPE_REQUIRED(structname, fieldname) offsetof(structname, fieldname)
|
||||
#define PB_DO_PB_HTYPE_SINGULAR(structname, fieldname) offsetof(structname, fieldname)
|
||||
#define PB_DO_PB_HTYPE_ONEOF(structname, fieldname) offsetof(structname, PB_ONEOF_NAME(FULL, fieldname))
|
||||
#define PB_DO_PB_HTYPE_OPTIONAL(structname, fieldname) offsetof(structname, fieldname)
|
||||
#define PB_DO_PB_HTYPE_REPEATED(structname, fieldname) offsetof(structname, fieldname)
|
||||
#define PB_DO_PB_HTYPE_FIXARRAY(structname, fieldname) offsetof(structname, fieldname)
|
||||
|
||||
#define PB_SIZE_OFFSET_STATIC(htype, structname, fieldname) PB_SIZE_OFFSET_ ## htype(structname, fieldname)
|
||||
#define PB_SIZE_OFFSET_POINTER(htype, structname, fieldname) PB_SIZE_OFFSET_PTR_ ## htype(structname, fieldname)
|
||||
#define PB_SIZE_OFFSET_CALLBACK(htype, structname, fieldname) PB_SIZE_OFFSET_CB_ ## htype(structname, fieldname)
|
||||
#define PB_SIZE_OFFSET_REQUIRED(structname, fieldname) 0
|
||||
#define PB_SIZE_OFFSET_SINGULAR(structname, fieldname) 0
|
||||
#define PB_SIZE_OFFSET_ONEOF(structname, fieldname) PB_SIZE_OFFSET_ONEOF2(structname, PB_ONEOF_NAME(FULL, fieldname), PB_ONEOF_NAME(UNION, fieldname))
|
||||
#define PB_SIZE_OFFSET_ONEOF2(structname, fullname, unionname) PB_SIZE_OFFSET_ONEOF3(structname, fullname, unionname)
|
||||
#define PB_SIZE_OFFSET_ONEOF3(structname, fullname, unionname) pb_delta(structname, fullname, which_ ## unionname)
|
||||
#define PB_SIZE_OFFSET_OPTIONAL(structname, fieldname) pb_delta(structname, fieldname, has_ ## fieldname)
|
||||
#define PB_SIZE_OFFSET_REPEATED(structname, fieldname) pb_delta(structname, fieldname, fieldname ## _count)
|
||||
#define PB_SIZE_OFFSET_FIXARRAY(structname, fieldname) 0
|
||||
#define PB_SIZE_OFFSET_PTR_REQUIRED(structname, fieldname) 0
|
||||
#define PB_SIZE_OFFSET_PTR_SINGULAR(structname, fieldname) 0
|
||||
#define PB_SIZE_OFFSET_PTR_ONEOF(structname, fieldname) PB_SIZE_OFFSET_ONEOF(structname, fieldname)
|
||||
#define PB_SIZE_OFFSET_PTR_OPTIONAL(structname, fieldname) 0
|
||||
#define PB_SIZE_OFFSET_PTR_REPEATED(structname, fieldname) PB_SIZE_OFFSET_REPEATED(structname, fieldname)
|
||||
#define PB_SIZE_OFFSET_PTR_FIXARRAY(structname, fieldname) 0
|
||||
#define PB_SIZE_OFFSET_CB_REQUIRED(structname, fieldname) 0
|
||||
#define PB_SIZE_OFFSET_CB_SINGULAR(structname, fieldname) 0
|
||||
#define PB_SIZE_OFFSET_CB_ONEOF(structname, fieldname) PB_SIZE_OFFSET_ONEOF(structname, fieldname)
|
||||
#define PB_SIZE_OFFSET_CB_OPTIONAL(structname, fieldname) 0
|
||||
#define PB_SIZE_OFFSET_CB_REPEATED(structname, fieldname) 0
|
||||
#define PB_SIZE_OFFSET_CB_FIXARRAY(structname, fieldname) 0
|
||||
#define PB_SIZE_OFFSET_STATIC(htype, structname, fieldname) PB_SO ## htype(structname, fieldname)
|
||||
#define PB_SIZE_OFFSET_POINTER(htype, structname, fieldname) PB_SO_PTR ## htype(structname, fieldname)
|
||||
#define PB_SIZE_OFFSET_CALLBACK(htype, structname, fieldname) PB_SO_CB ## htype(structname, fieldname)
|
||||
#define PB_SO_PB_HTYPE_REQUIRED(structname, fieldname) 0
|
||||
#define PB_SO_PB_HTYPE_SINGULAR(structname, fieldname) 0
|
||||
#define PB_SO_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF2(structname, PB_ONEOF_NAME(FULL, fieldname), PB_ONEOF_NAME(UNION, fieldname))
|
||||
#define PB_SO_PB_HTYPE_ONEOF2(structname, fullname, unionname) PB_SO_PB_HTYPE_ONEOF3(structname, fullname, unionname)
|
||||
#define PB_SO_PB_HTYPE_ONEOF3(structname, fullname, unionname) pb_delta(structname, fullname, which_ ## unionname)
|
||||
#define PB_SO_PB_HTYPE_OPTIONAL(structname, fieldname) pb_delta(structname, fieldname, has_ ## fieldname)
|
||||
#define PB_SO_PB_HTYPE_REPEATED(structname, fieldname) pb_delta(structname, fieldname, fieldname ## _count)
|
||||
#define PB_SO_PB_HTYPE_FIXARRAY(structname, fieldname) 0
|
||||
#define PB_SO_PTR_PB_HTYPE_REQUIRED(structname, fieldname) 0
|
||||
#define PB_SO_PTR_PB_HTYPE_SINGULAR(structname, fieldname) 0
|
||||
#define PB_SO_PTR_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF(structname, fieldname)
|
||||
#define PB_SO_PTR_PB_HTYPE_OPTIONAL(structname, fieldname) 0
|
||||
#define PB_SO_PTR_PB_HTYPE_REPEATED(structname, fieldname) PB_SO_PB_HTYPE_REPEATED(structname, fieldname)
|
||||
#define PB_SO_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) 0
|
||||
#define PB_SO_CB_PB_HTYPE_REQUIRED(structname, fieldname) 0
|
||||
#define PB_SO_CB_PB_HTYPE_SINGULAR(structname, fieldname) 0
|
||||
#define PB_SO_CB_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF(structname, fieldname)
|
||||
#define PB_SO_CB_PB_HTYPE_OPTIONAL(structname, fieldname) 0
|
||||
#define PB_SO_CB_PB_HTYPE_REPEATED(structname, fieldname) 0
|
||||
#define PB_SO_CB_PB_HTYPE_FIXARRAY(structname, fieldname) 0
|
||||
|
||||
#define PB_ARRAY_SIZE_STATIC(htype, structname, fieldname) PB_ARRAY_SIZE_ ## htype(structname, fieldname)
|
||||
#define PB_ARRAY_SIZE_POINTER(htype, structname, fieldname) PB_ARRAY_SIZE_PTR_ ## htype(structname, fieldname)
|
||||
#define PB_ARRAY_SIZE_STATIC(htype, structname, fieldname) PB_AS ## htype(structname, fieldname)
|
||||
#define PB_ARRAY_SIZE_POINTER(htype, structname, fieldname) PB_AS_PTR ## htype(structname, fieldname)
|
||||
#define PB_ARRAY_SIZE_CALLBACK(htype, structname, fieldname) 1
|
||||
#define PB_ARRAY_SIZE_REQUIRED(structname, fieldname) 1
|
||||
#define PB_ARRAY_SIZE_SINGULAR(structname, fieldname) 1
|
||||
#define PB_ARRAY_SIZE_OPTIONAL(structname, fieldname) 1
|
||||
#define PB_ARRAY_SIZE_ONEOF(structname, fieldname) 1
|
||||
#define PB_ARRAY_SIZE_REPEATED(structname, fieldname) pb_arraysize(structname, fieldname)
|
||||
#define PB_ARRAY_SIZE_FIXARRAY(structname, fieldname) pb_arraysize(structname, fieldname)
|
||||
#define PB_ARRAY_SIZE_PTR_REQUIRED(structname, fieldname) 1
|
||||
#define PB_ARRAY_SIZE_PTR_SINGULAR(structname, fieldname) 1
|
||||
#define PB_ARRAY_SIZE_PTR_OPTIONAL(structname, fieldname) 1
|
||||
#define PB_ARRAY_SIZE_PTR_ONEOF(structname, fieldname) 1
|
||||
#define PB_ARRAY_SIZE_PTR_REPEATED(structname, fieldname) 1
|
||||
#define PB_ARRAY_SIZE_PTR_FIXARRAY(structname, fieldname) pb_arraysize(structname, fieldname[0])
|
||||
#define PB_AS_PB_HTYPE_REQUIRED(structname, fieldname) 1
|
||||
#define PB_AS_PB_HTYPE_SINGULAR(structname, fieldname) 1
|
||||
#define PB_AS_PB_HTYPE_OPTIONAL(structname, fieldname) 1
|
||||
#define PB_AS_PB_HTYPE_ONEOF(structname, fieldname) 1
|
||||
#define PB_AS_PB_HTYPE_REPEATED(structname, fieldname) pb_arraysize(structname, fieldname)
|
||||
#define PB_AS_PB_HTYPE_FIXARRAY(structname, fieldname) pb_arraysize(structname, fieldname)
|
||||
#define PB_AS_PTR_PB_HTYPE_REQUIRED(structname, fieldname) 1
|
||||
#define PB_AS_PTR_PB_HTYPE_SINGULAR(structname, fieldname) 1
|
||||
#define PB_AS_PTR_PB_HTYPE_OPTIONAL(structname, fieldname) 1
|
||||
#define PB_AS_PTR_PB_HTYPE_ONEOF(structname, fieldname) 1
|
||||
#define PB_AS_PTR_PB_HTYPE_REPEATED(structname, fieldname) 1
|
||||
#define PB_AS_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) pb_arraysize(structname, fieldname[0])
|
||||
|
||||
#define PB_DATA_SIZE_STATIC(htype, structname, fieldname) PB_DATA_SIZE_ ## htype(structname, fieldname)
|
||||
#define PB_DATA_SIZE_POINTER(htype, structname, fieldname) PB_DATA_SIZE_PTR_ ## htype(structname, fieldname)
|
||||
#define PB_DATA_SIZE_CALLBACK(htype, structname, fieldname) PB_DATA_SIZE_CB_ ## htype(structname, fieldname)
|
||||
#define PB_DATA_SIZE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname)
|
||||
#define PB_DATA_SIZE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname)
|
||||
#define PB_DATA_SIZE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname)
|
||||
#define PB_DATA_SIZE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname))
|
||||
#define PB_DATA_SIZE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname[0])
|
||||
#define PB_DATA_SIZE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname[0])
|
||||
#define PB_DATA_SIZE_PTR_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname[0])
|
||||
#define PB_DATA_SIZE_PTR_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname[0])
|
||||
#define PB_DATA_SIZE_PTR_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname[0])
|
||||
#define PB_DATA_SIZE_PTR_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname)[0])
|
||||
#define PB_DATA_SIZE_PTR_REPEATED(structname, fieldname) pb_membersize(structname, fieldname[0])
|
||||
#define PB_DATA_SIZE_PTR_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname[0][0])
|
||||
#define PB_DATA_SIZE_CB_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname)
|
||||
#define PB_DATA_SIZE_CB_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname)
|
||||
#define PB_DATA_SIZE_CB_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname)
|
||||
#define PB_DATA_SIZE_CB_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname))
|
||||
#define PB_DATA_SIZE_CB_REPEATED(structname, fieldname) pb_membersize(structname, fieldname)
|
||||
#define PB_DATA_SIZE_CB_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname)
|
||||
#define PB_DATA_SIZE_STATIC(htype, structname, fieldname) PB_DS ## htype(structname, fieldname)
|
||||
#define PB_DATA_SIZE_POINTER(htype, structname, fieldname) PB_DS_PTR ## htype(structname, fieldname)
|
||||
#define PB_DATA_SIZE_CALLBACK(htype, structname, fieldname) PB_DS_CB ## htype(structname, fieldname)
|
||||
#define PB_DS_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname)
|
||||
#define PB_DS_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname)
|
||||
#define PB_DS_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname)
|
||||
#define PB_DS_PB_HTYPE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname))
|
||||
#define PB_DS_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname[0])
|
||||
#define PB_DS_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname[0])
|
||||
#define PB_DS_PTR_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname[0])
|
||||
#define PB_DS_PTR_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname[0])
|
||||
#define PB_DS_PTR_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname[0])
|
||||
#define PB_DS_PTR_PB_HTYPE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname)[0])
|
||||
#define PB_DS_PTR_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname[0])
|
||||
#define PB_DS_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname[0][0])
|
||||
#define PB_DS_CB_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname)
|
||||
#define PB_DS_CB_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname)
|
||||
#define PB_DS_CB_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname)
|
||||
#define PB_DS_CB_PB_HTYPE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname))
|
||||
#define PB_DS_CB_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname)
|
||||
#define PB_DS_CB_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname)
|
||||
|
||||
#define PB_ONEOF_NAME(type, tuple) PB_EXPAND(PB_ONEOF_NAME_ ## type tuple)
|
||||
#define PB_ONEOF_NAME_UNION(unionname,membername,fullname) unionname
|
||||
@@ -607,37 +652,37 @@ struct pb_extension_s {
|
||||
#define PB_ONEOF_NAME_FULL(unionname,membername,fullname) fullname
|
||||
|
||||
#define PB_GEN_SUBMSG_INFO(structname, atype, htype, ltype, fieldname, tag) \
|
||||
PB_SUBMSG_INFO_ ## htype(ltype, structname, fieldname)
|
||||
PB_SUBMSG_INFO_ ## htype(_PB_LTYPE_ ## ltype, structname, fieldname)
|
||||
|
||||
#define PB_SUBMSG_INFO_REQUIRED(ltype, structname, fieldname) PB_SUBMSG_INFO_ ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
|
||||
#define PB_SUBMSG_INFO_SINGULAR(ltype, structname, fieldname) PB_SUBMSG_INFO_ ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
|
||||
#define PB_SUBMSG_INFO_OPTIONAL(ltype, structname, fieldname) PB_SUBMSG_INFO_ ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
|
||||
#define PB_SUBMSG_INFO_REQUIRED(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
|
||||
#define PB_SUBMSG_INFO_SINGULAR(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
|
||||
#define PB_SUBMSG_INFO_OPTIONAL(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
|
||||
#define PB_SUBMSG_INFO_ONEOF(ltype, structname, fieldname) PB_SUBMSG_INFO_ONEOF2(ltype, structname, PB_ONEOF_NAME(UNION, fieldname), PB_ONEOF_NAME(MEMBER, fieldname))
|
||||
#define PB_SUBMSG_INFO_ONEOF2(ltype, structname, unionname, membername) PB_SUBMSG_INFO_ONEOF3(ltype, structname, unionname, membername)
|
||||
#define PB_SUBMSG_INFO_ONEOF3(ltype, structname, unionname, membername) PB_SUBMSG_INFO_ ## ltype(structname ## _ ## unionname ## _ ## membername ## _MSGTYPE)
|
||||
#define PB_SUBMSG_INFO_REPEATED(ltype, structname, fieldname) PB_SUBMSG_INFO_ ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
|
||||
#define PB_SUBMSG_INFO_FIXARRAY(ltype, structname, fieldname) PB_SUBMSG_INFO_ ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
|
||||
#define PB_SUBMSG_INFO_BOOL(t)
|
||||
#define PB_SUBMSG_INFO_BYTES(t)
|
||||
#define PB_SUBMSG_INFO_DOUBLE(t)
|
||||
#define PB_SUBMSG_INFO_ENUM(t)
|
||||
#define PB_SUBMSG_INFO_UENUM(t)
|
||||
#define PB_SUBMSG_INFO_FIXED32(t)
|
||||
#define PB_SUBMSG_INFO_FIXED64(t)
|
||||
#define PB_SUBMSG_INFO_FLOAT(t)
|
||||
#define PB_SUBMSG_INFO_INT32(t)
|
||||
#define PB_SUBMSG_INFO_INT64(t)
|
||||
#define PB_SUBMSG_INFO_MESSAGE(t) PB_SUBMSG_DESCRIPTOR(t)
|
||||
#define PB_SUBMSG_INFO_MSG_W_CB(t) PB_SUBMSG_DESCRIPTOR(t)
|
||||
#define PB_SUBMSG_INFO_SFIXED32(t)
|
||||
#define PB_SUBMSG_INFO_SFIXED64(t)
|
||||
#define PB_SUBMSG_INFO_SINT32(t)
|
||||
#define PB_SUBMSG_INFO_SINT64(t)
|
||||
#define PB_SUBMSG_INFO_STRING(t)
|
||||
#define PB_SUBMSG_INFO_UINT32(t)
|
||||
#define PB_SUBMSG_INFO_UINT64(t)
|
||||
#define PB_SUBMSG_INFO_EXTENSION(t)
|
||||
#define PB_SUBMSG_INFO_FIXED_LENGTH_BYTES(t)
|
||||
#define PB_SUBMSG_INFO_ONEOF3(ltype, structname, unionname, membername) PB_SI ## ltype(structname ## _ ## unionname ## _ ## membername ## _MSGTYPE)
|
||||
#define PB_SUBMSG_INFO_REPEATED(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
|
||||
#define PB_SUBMSG_INFO_FIXARRAY(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
|
||||
#define PB_SI_PB_LTYPE_BOOL(t)
|
||||
#define PB_SI_PB_LTYPE_BYTES(t)
|
||||
#define PB_SI_PB_LTYPE_DOUBLE(t)
|
||||
#define PB_SI_PB_LTYPE_ENUM(t)
|
||||
#define PB_SI_PB_LTYPE_UENUM(t)
|
||||
#define PB_SI_PB_LTYPE_FIXED32(t)
|
||||
#define PB_SI_PB_LTYPE_FIXED64(t)
|
||||
#define PB_SI_PB_LTYPE_FLOAT(t)
|
||||
#define PB_SI_PB_LTYPE_INT32(t)
|
||||
#define PB_SI_PB_LTYPE_INT64(t)
|
||||
#define PB_SI_PB_LTYPE_MESSAGE(t) PB_SUBMSG_DESCRIPTOR(t)
|
||||
#define PB_SI_PB_LTYPE_MSG_W_CB(t) PB_SUBMSG_DESCRIPTOR(t)
|
||||
#define PB_SI_PB_LTYPE_SFIXED32(t)
|
||||
#define PB_SI_PB_LTYPE_SFIXED64(t)
|
||||
#define PB_SI_PB_LTYPE_SINT32(t)
|
||||
#define PB_SI_PB_LTYPE_SINT64(t)
|
||||
#define PB_SI_PB_LTYPE_STRING(t)
|
||||
#define PB_SI_PB_LTYPE_UINT32(t)
|
||||
#define PB_SI_PB_LTYPE_UINT64(t)
|
||||
#define PB_SI_PB_LTYPE_EXTENSION(t)
|
||||
#define PB_SI_PB_LTYPE_FIXED_LENGTH_BYTES(t)
|
||||
#define PB_SUBMSG_DESCRIPTOR(t) &(t ## _msg),
|
||||
|
||||
/* The field descriptors use a variable width format, with width of either
|
||||
@@ -726,37 +771,37 @@ struct pb_extension_s {
|
||||
* The generator will give explicit size argument when it knows that a message
|
||||
* structure grows beyond 1-word format limits.
|
||||
*/
|
||||
#define PB_FIELDINFO_WIDTH_AUTO(atype, htype, ltype) PB_FIELDINFO_WIDTH_ ## atype(htype, ltype)
|
||||
#define PB_FIELDINFO_WIDTH_STATIC(htype, ltype) PB_FIELDINFO_WIDTH_ ## htype(ltype)
|
||||
#define PB_FIELDINFO_WIDTH_POINTER(htype, ltype) PB_FIELDINFO_WIDTH_ ## htype(ltype)
|
||||
#define PB_FIELDINFO_WIDTH_CALLBACK(htype, ltype) 2
|
||||
#define PB_FIELDINFO_WIDTH_REQUIRED(ltype) PB_FIELDINFO_WIDTH_ ## ltype
|
||||
#define PB_FIELDINFO_WIDTH_SINGULAR(ltype) PB_FIELDINFO_WIDTH_ ## ltype
|
||||
#define PB_FIELDINFO_WIDTH_OPTIONAL(ltype) PB_FIELDINFO_WIDTH_ ## ltype
|
||||
#define PB_FIELDINFO_WIDTH_ONEOF(ltype) PB_FIELDINFO_WIDTH_ ## ltype
|
||||
#define PB_FIELDINFO_WIDTH_REPEATED(ltype) 2
|
||||
#define PB_FIELDINFO_WIDTH_FIXARRAY(ltype) 2
|
||||
#define PB_FIELDINFO_WIDTH_BOOL 1
|
||||
#define PB_FIELDINFO_WIDTH_BYTES 2
|
||||
#define PB_FIELDINFO_WIDTH_DOUBLE 1
|
||||
#define PB_FIELDINFO_WIDTH_ENUM 1
|
||||
#define PB_FIELDINFO_WIDTH_UENUM 1
|
||||
#define PB_FIELDINFO_WIDTH_FIXED32 1
|
||||
#define PB_FIELDINFO_WIDTH_FIXED64 1
|
||||
#define PB_FIELDINFO_WIDTH_FLOAT 1
|
||||
#define PB_FIELDINFO_WIDTH_INT32 1
|
||||
#define PB_FIELDINFO_WIDTH_INT64 1
|
||||
#define PB_FIELDINFO_WIDTH_MESSAGE 2
|
||||
#define PB_FIELDINFO_WIDTH_MSG_W_CB 2
|
||||
#define PB_FIELDINFO_WIDTH_SFIXED32 1
|
||||
#define PB_FIELDINFO_WIDTH_SFIXED64 1
|
||||
#define PB_FIELDINFO_WIDTH_SINT32 1
|
||||
#define PB_FIELDINFO_WIDTH_SINT64 1
|
||||
#define PB_FIELDINFO_WIDTH_STRING 2
|
||||
#define PB_FIELDINFO_WIDTH_UINT32 1
|
||||
#define PB_FIELDINFO_WIDTH_UINT64 1
|
||||
#define PB_FIELDINFO_WIDTH_EXTENSION 1
|
||||
#define PB_FIELDINFO_WIDTH_FIXED_LENGTH_BYTES 2
|
||||
#define PB_FIELDINFO_WIDTH_AUTO(atype, htype, ltype) PB_FI_WIDTH ## atype(htype, ltype)
|
||||
#define PB_FI_WIDTH_PB_ATYPE_STATIC(htype, ltype) PB_FI_WIDTH ## htype(ltype)
|
||||
#define PB_FI_WIDTH_PB_ATYPE_POINTER(htype, ltype) PB_FI_WIDTH ## htype(ltype)
|
||||
#define PB_FI_WIDTH_PB_ATYPE_CALLBACK(htype, ltype) 2
|
||||
#define PB_FI_WIDTH_PB_HTYPE_REQUIRED(ltype) PB_FI_WIDTH ## ltype
|
||||
#define PB_FI_WIDTH_PB_HTYPE_SINGULAR(ltype) PB_FI_WIDTH ## ltype
|
||||
#define PB_FI_WIDTH_PB_HTYPE_OPTIONAL(ltype) PB_FI_WIDTH ## ltype
|
||||
#define PB_FI_WIDTH_PB_HTYPE_ONEOF(ltype) PB_FI_WIDTH ## ltype
|
||||
#define PB_FI_WIDTH_PB_HTYPE_REPEATED(ltype) 2
|
||||
#define PB_FI_WIDTH_PB_HTYPE_FIXARRAY(ltype) 2
|
||||
#define PB_FI_WIDTH_PB_LTYPE_BOOL 1
|
||||
#define PB_FI_WIDTH_PB_LTYPE_BYTES 2
|
||||
#define PB_FI_WIDTH_PB_LTYPE_DOUBLE 1
|
||||
#define PB_FI_WIDTH_PB_LTYPE_ENUM 1
|
||||
#define PB_FI_WIDTH_PB_LTYPE_UENUM 1
|
||||
#define PB_FI_WIDTH_PB_LTYPE_FIXED32 1
|
||||
#define PB_FI_WIDTH_PB_LTYPE_FIXED64 1
|
||||
#define PB_FI_WIDTH_PB_LTYPE_FLOAT 1
|
||||
#define PB_FI_WIDTH_PB_LTYPE_INT32 1
|
||||
#define PB_FI_WIDTH_PB_LTYPE_INT64 1
|
||||
#define PB_FI_WIDTH_PB_LTYPE_MESSAGE 2
|
||||
#define PB_FI_WIDTH_PB_LTYPE_MSG_W_CB 2
|
||||
#define PB_FI_WIDTH_PB_LTYPE_SFIXED32 1
|
||||
#define PB_FI_WIDTH_PB_LTYPE_SFIXED64 1
|
||||
#define PB_FI_WIDTH_PB_LTYPE_SINT32 1
|
||||
#define PB_FI_WIDTH_PB_LTYPE_SINT64 1
|
||||
#define PB_FI_WIDTH_PB_LTYPE_STRING 2
|
||||
#define PB_FI_WIDTH_PB_LTYPE_UINT32 1
|
||||
#define PB_FI_WIDTH_PB_LTYPE_UINT64 1
|
||||
#define PB_FI_WIDTH_PB_LTYPE_EXTENSION 1
|
||||
#define PB_FI_WIDTH_PB_LTYPE_FIXED_LENGTH_BYTES 2
|
||||
|
||||
/* The mapping from protobuf types to LTYPEs is done using these macros. */
|
||||
#define PB_LTYPE_MAP_BOOL PB_LTYPE_BOOL
|
||||
|
||||
@@ -32,6 +32,10 @@ bool pb_field_iter_next(pb_field_iter_t *iter);
|
||||
* Returns false if no such field exists. */
|
||||
bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag);
|
||||
|
||||
/* Find a field with type PB_LTYPE_EXTENSION, or return false if not found.
|
||||
* There can be only one extension range field per message. */
|
||||
bool pb_field_iter_find_extension(pb_field_iter_t *iter);
|
||||
|
||||
#ifdef PB_VALIDATE_UTF8
|
||||
/* Validate UTF-8 text string */
|
||||
bool pb_validate_utf8(const char *s);
|
||||
|
||||
@@ -113,6 +113,9 @@ bool pb_decode_ex(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_s
|
||||
* pb_decode() returns with an error, the message is already released.
|
||||
*/
|
||||
void pb_release(const pb_msgdesc_t *fields, void *dest_struct);
|
||||
#else
|
||||
/* Allocation is not supported, so release is no-op */
|
||||
#define pb_release(fields, dest_struct) PB_UNUSED(fields); PB_UNUSED(dest_struct);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -121,11 +124,14 @@ void pb_release(const pb_msgdesc_t *fields, void *dest_struct);
|
||||
**************************************/
|
||||
|
||||
/* Create an input stream for reading from a memory buffer.
|
||||
*
|
||||
* msglen should be the actual length of the message, not the full size of
|
||||
* allocated buffer.
|
||||
*
|
||||
* Alternatively, you can use a custom stream that reads directly from e.g.
|
||||
* a file or a network socket.
|
||||
*/
|
||||
pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t bufsize);
|
||||
pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t msglen);
|
||||
|
||||
/* Function to read from a pb_istream_t. You can use this if you need to
|
||||
* read some custom header data, or to read data in field callbacks.
|
||||
|
||||
@@ -132,7 +132,7 @@ bool pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count);
|
||||
* structure. Call this from the callback before writing out field contents. */
|
||||
bool pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_iter_t *field);
|
||||
|
||||
/* Encode field header by manually specifing wire type. You need to use this
|
||||
/* Encode field header by manually specifying wire type. You need to use this
|
||||
* if you want to write out packed arrays from a callback field. */
|
||||
bool pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number);
|
||||
|
||||
|
||||
@@ -9,89 +9,102 @@ static bool load_descriptor_values(pb_field_iter_t *iter)
|
||||
{
|
||||
uint32_t word0;
|
||||
uint32_t data_offset;
|
||||
uint_least8_t format;
|
||||
int_least8_t size_offset;
|
||||
|
||||
if (iter->index >= iter->descriptor->field_count)
|
||||
return false;
|
||||
|
||||
word0 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]);
|
||||
format = word0 & 3;
|
||||
iter->tag = (pb_size_t)((word0 >> 2) & 0x3F);
|
||||
iter->type = (pb_type_t)((word0 >> 8) & 0xFF);
|
||||
|
||||
if (format == 0)
|
||||
switch(word0 & 3)
|
||||
{
|
||||
/* 1-word format */
|
||||
iter->array_size = 1;
|
||||
size_offset = (int_least8_t)((word0 >> 24) & 0x0F);
|
||||
data_offset = (word0 >> 16) & 0xFF;
|
||||
iter->data_size = (pb_size_t)((word0 >> 28) & 0x0F);
|
||||
}
|
||||
else if (format == 1)
|
||||
{
|
||||
/* 2-word format */
|
||||
uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]);
|
||||
case 0: {
|
||||
/* 1-word format */
|
||||
iter->array_size = 1;
|
||||
iter->tag = (pb_size_t)((word0 >> 2) & 0x3F);
|
||||
size_offset = (int_least8_t)((word0 >> 24) & 0x0F);
|
||||
data_offset = (word0 >> 16) & 0xFF;
|
||||
iter->data_size = (pb_size_t)((word0 >> 28) & 0x0F);
|
||||
break;
|
||||
}
|
||||
|
||||
iter->array_size = (pb_size_t)((word0 >> 16) & 0x0FFF);
|
||||
iter->tag = (pb_size_t)(iter->tag | ((word1 >> 28) << 6));
|
||||
size_offset = (int_least8_t)((word0 >> 28) & 0x0F);
|
||||
data_offset = word1 & 0xFFFF;
|
||||
iter->data_size = (pb_size_t)((word1 >> 16) & 0x0FFF);
|
||||
}
|
||||
else if (format == 2)
|
||||
{
|
||||
/* 4-word format */
|
||||
uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]);
|
||||
uint32_t word2 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 2]);
|
||||
uint32_t word3 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 3]);
|
||||
case 1: {
|
||||
/* 2-word format */
|
||||
uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]);
|
||||
|
||||
iter->array_size = (pb_size_t)(word0 >> 16);
|
||||
iter->tag = (pb_size_t)(iter->tag | ((word1 >> 8) << 6));
|
||||
size_offset = (int_least8_t)(word1 & 0xFF);
|
||||
data_offset = word2;
|
||||
iter->data_size = (pb_size_t)word3;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 8-word format */
|
||||
uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]);
|
||||
uint32_t word2 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 2]);
|
||||
uint32_t word3 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 3]);
|
||||
uint32_t word4 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 4]);
|
||||
iter->array_size = (pb_size_t)((word0 >> 16) & 0x0FFF);
|
||||
iter->tag = (pb_size_t)(((word0 >> 2) & 0x3F) | ((word1 >> 28) << 6));
|
||||
size_offset = (int_least8_t)((word0 >> 28) & 0x0F);
|
||||
data_offset = word1 & 0xFFFF;
|
||||
iter->data_size = (pb_size_t)((word1 >> 16) & 0x0FFF);
|
||||
break;
|
||||
}
|
||||
|
||||
iter->array_size = (pb_size_t)word4;
|
||||
iter->tag = (pb_size_t)(iter->tag | ((word1 >> 8) << 6));
|
||||
size_offset = (int_least8_t)(word1 & 0xFF);
|
||||
data_offset = word2;
|
||||
iter->data_size = (pb_size_t)word3;
|
||||
case 2: {
|
||||
/* 4-word format */
|
||||
uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]);
|
||||
uint32_t word2 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 2]);
|
||||
uint32_t word3 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 3]);
|
||||
|
||||
iter->array_size = (pb_size_t)(word0 >> 16);
|
||||
iter->tag = (pb_size_t)(((word0 >> 2) & 0x3F) | ((word1 >> 8) << 6));
|
||||
size_offset = (int_least8_t)(word1 & 0xFF);
|
||||
data_offset = word2;
|
||||
iter->data_size = (pb_size_t)word3;
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
/* 8-word format */
|
||||
uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]);
|
||||
uint32_t word2 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 2]);
|
||||
uint32_t word3 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 3]);
|
||||
uint32_t word4 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 4]);
|
||||
|
||||
iter->array_size = (pb_size_t)word4;
|
||||
iter->tag = (pb_size_t)(((word0 >> 2) & 0x3F) | ((word1 >> 8) << 6));
|
||||
size_offset = (int_least8_t)(word1 & 0xFF);
|
||||
data_offset = word2;
|
||||
iter->data_size = (pb_size_t)word3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
iter->pField = (char*)iter->message + data_offset;
|
||||
|
||||
if (size_offset)
|
||||
{
|
||||
iter->pSize = (char*)iter->pField - size_offset;
|
||||
}
|
||||
else if (PB_HTYPE(iter->type) == PB_HTYPE_REPEATED &&
|
||||
(PB_ATYPE(iter->type) == PB_ATYPE_STATIC ||
|
||||
PB_ATYPE(iter->type) == PB_ATYPE_POINTER))
|
||||
{
|
||||
/* Fixed count array */
|
||||
iter->pSize = &iter->array_size;
|
||||
}
|
||||
else
|
||||
if (!iter->message)
|
||||
{
|
||||
/* Avoid doing arithmetic on null pointers, it is undefined */
|
||||
iter->pField = NULL;
|
||||
iter->pSize = NULL;
|
||||
}
|
||||
|
||||
if (PB_ATYPE(iter->type) == PB_ATYPE_POINTER && iter->pField != NULL)
|
||||
{
|
||||
iter->pData = *(void**)iter->pField;
|
||||
}
|
||||
else
|
||||
{
|
||||
iter->pData = iter->pField;
|
||||
iter->pField = (char*)iter->message + data_offset;
|
||||
|
||||
if (size_offset)
|
||||
{
|
||||
iter->pSize = (char*)iter->pField - size_offset;
|
||||
}
|
||||
else if (PB_HTYPE(iter->type) == PB_HTYPE_REPEATED &&
|
||||
(PB_ATYPE(iter->type) == PB_ATYPE_STATIC ||
|
||||
PB_ATYPE(iter->type) == PB_ATYPE_POINTER))
|
||||
{
|
||||
/* Fixed count array */
|
||||
iter->pSize = &iter->array_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
iter->pSize = NULL;
|
||||
}
|
||||
|
||||
if (PB_ATYPE(iter->type) == PB_ATYPE_POINTER && iter->pField != NULL)
|
||||
{
|
||||
iter->pData = *(void**)iter->pField;
|
||||
}
|
||||
else
|
||||
{
|
||||
iter->pData = iter->pField;
|
||||
}
|
||||
}
|
||||
|
||||
if (PB_LTYPE_IS_SUBMSG(iter->type))
|
||||
@@ -130,17 +143,13 @@ static void advance_iterator(pb_field_iter_t *iter)
|
||||
pb_type_t prev_type = (prev_descriptor >> 8) & 0xFF;
|
||||
pb_size_t descriptor_len = (pb_size_t)(1 << (prev_descriptor & 3));
|
||||
|
||||
/* Add to fields.
|
||||
* The cast to pb_size_t is needed to avoid -Wconversion warning.
|
||||
* Because the data is is constants from generator, there is no danger of overflow.
|
||||
*/
|
||||
iter->field_info_index = (pb_size_t)(iter->field_info_index + descriptor_len);
|
||||
|
||||
if (PB_HTYPE(prev_type) == PB_HTYPE_REQUIRED)
|
||||
{
|
||||
iter->required_field_index++;
|
||||
}
|
||||
|
||||
if (PB_LTYPE_IS_SUBMSG(prev_type))
|
||||
{
|
||||
iter->submessage_index++;
|
||||
}
|
||||
iter->required_field_index = (pb_size_t)(iter->required_field_index + (PB_HTYPE(prev_type) == PB_HTYPE_REQUIRED));
|
||||
iter->submessage_index = (pb_size_t)(iter->submessage_index + PB_LTYPE_IS_SUBMSG(prev_type));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,11 +198,23 @@ bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag)
|
||||
{
|
||||
return true; /* Nothing to do, correct field already. */
|
||||
}
|
||||
else if (tag > iter->descriptor->largest_tag)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
pb_size_t start = iter->index;
|
||||
uint32_t fieldinfo;
|
||||
|
||||
if (tag < iter->tag)
|
||||
{
|
||||
/* Fields are in tag number order, so we know that tag is between
|
||||
* 0 and our start position. Setting index to end forces
|
||||
* advance_iterator() call below to restart from beginning. */
|
||||
iter->index = iter->descriptor->field_count;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
/* Advance iterator but don't load values yet */
|
||||
@@ -222,6 +243,37 @@ bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag)
|
||||
}
|
||||
}
|
||||
|
||||
bool pb_field_iter_find_extension(pb_field_iter_t *iter)
|
||||
{
|
||||
if (PB_LTYPE(iter->type) == PB_LTYPE_EXTENSION)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
pb_size_t start = iter->index;
|
||||
uint32_t fieldinfo;
|
||||
|
||||
do
|
||||
{
|
||||
/* Advance iterator but don't load values yet */
|
||||
advance_iterator(iter);
|
||||
|
||||
/* Do fast check for field type */
|
||||
fieldinfo = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]);
|
||||
|
||||
if (PB_LTYPE((fieldinfo >> 8) & 0xFF) == PB_LTYPE_EXTENSION)
|
||||
{
|
||||
return load_descriptor_values(iter);
|
||||
}
|
||||
} while (iter->index != start);
|
||||
|
||||
/* Searched all the way back to start, and found nothing. */
|
||||
(void)load_descriptor_values(iter);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void *pb_const_cast(const void *p)
|
||||
{
|
||||
/* Note: this casts away const, in order to use the common field iterator
|
||||
|
||||
@@ -24,19 +24,17 @@
|
||||
static bool checkreturn buf_read(pb_istream_t *stream, pb_byte_t *buf, size_t count);
|
||||
static bool checkreturn pb_decode_varint32_eof(pb_istream_t *stream, uint32_t *dest, bool *eof);
|
||||
static bool checkreturn read_raw_value(pb_istream_t *stream, pb_wire_type_t wire_type, pb_byte_t *buf, size_t *size);
|
||||
static bool checkreturn check_wire_type(pb_wire_type_t wire_type, pb_field_iter_t *field);
|
||||
static bool checkreturn decode_basic_field(pb_istream_t *stream, pb_field_iter_t *field);
|
||||
static bool checkreturn decode_basic_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field);
|
||||
static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field);
|
||||
static bool checkreturn decode_pointer_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field);
|
||||
static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field);
|
||||
static bool checkreturn decode_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field);
|
||||
static bool checkreturn default_extension_decoder(pb_istream_t *stream, pb_extension_t *extension, uint32_t tag, pb_wire_type_t wire_type);
|
||||
static bool checkreturn decode_extension(pb_istream_t *stream, uint32_t tag, pb_wire_type_t wire_type, pb_field_iter_t *iter);
|
||||
static bool checkreturn find_extension_field(pb_field_iter_t *iter);
|
||||
static bool checkreturn decode_extension(pb_istream_t *stream, uint32_t tag, pb_wire_type_t wire_type, pb_extension_t *extension);
|
||||
static bool pb_field_set_to_default(pb_field_iter_t *field);
|
||||
static bool pb_message_set_to_defaults(pb_field_iter_t *iter);
|
||||
static bool checkreturn pb_dec_bool(pb_istream_t *stream, const pb_field_iter_t *field);
|
||||
static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_iter_t *field);
|
||||
static bool checkreturn pb_dec_fixed(pb_istream_t *stream, const pb_field_iter_t *field);
|
||||
static bool checkreturn pb_dec_bytes(pb_istream_t *stream, const pb_field_iter_t *field);
|
||||
static bool checkreturn pb_dec_string(pb_istream_t *stream, const pb_field_iter_t *field);
|
||||
static bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_iter_t *field);
|
||||
@@ -59,6 +57,8 @@ static void pb_release_single_field(pb_field_iter_t *field);
|
||||
#define pb_uint64_t uint64_t
|
||||
#endif
|
||||
|
||||
#define PB_WT_PACKED ((pb_wire_type_t)0xFF)
|
||||
|
||||
typedef struct {
|
||||
uint32_t bitfield[(PB_MAX_REQUIRED_FIELDS + 31) / 32];
|
||||
} pb_fields_seen_t;
|
||||
@@ -139,7 +139,7 @@ static bool checkreturn pb_readbyte(pb_istream_t *stream, pb_byte_t *buf)
|
||||
return true;
|
||||
}
|
||||
|
||||
pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t bufsize)
|
||||
pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t msglen)
|
||||
{
|
||||
pb_istream_t stream;
|
||||
/* Cast away the const from buf without a compiler error. We are
|
||||
@@ -156,7 +156,7 @@ pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t bufsize)
|
||||
#endif
|
||||
state.c_state = buf;
|
||||
stream.state = state.state;
|
||||
stream.bytes_left = bufsize;
|
||||
stream.bytes_left = msglen;
|
||||
#ifndef PB_NO_ERRMSG
|
||||
stream.errmsg = NULL;
|
||||
#endif
|
||||
@@ -205,8 +205,10 @@ static bool checkreturn pb_decode_varint32_eof(pb_istream_t *stream, uint32_t *d
|
||||
{
|
||||
/* Note: The varint could have trailing 0x80 bytes, or 0xFF for negative. */
|
||||
pb_byte_t sign_extension = (bitpos < 63) ? 0xFF : 0x01;
|
||||
|
||||
if ((byte & 0x7F) != 0x00 && ((result >> 31) == 0 || byte != sign_extension))
|
||||
bool valid_extension = ((byte & 0x7F) == 0x00 ||
|
||||
((result >> 31) != 0 && byte == sign_extension));
|
||||
|
||||
if (bitpos >= 64 || !valid_extension)
|
||||
{
|
||||
PB_RETURN_ERROR(stream, "varint overflow");
|
||||
}
|
||||
@@ -388,61 +390,70 @@ bool checkreturn pb_close_string_substream(pb_istream_t *stream, pb_istream_t *s
|
||||
* Decode a single field *
|
||||
*************************/
|
||||
|
||||
static bool checkreturn check_wire_type(pb_wire_type_t wire_type, pb_field_iter_t *field)
|
||||
static bool checkreturn decode_basic_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field)
|
||||
{
|
||||
switch (PB_LTYPE(field->type))
|
||||
{
|
||||
case PB_LTYPE_BOOL:
|
||||
case PB_LTYPE_VARINT:
|
||||
case PB_LTYPE_UVARINT:
|
||||
case PB_LTYPE_SVARINT:
|
||||
return wire_type == PB_WT_VARINT;
|
||||
if (wire_type != PB_WT_VARINT && wire_type != PB_WT_PACKED)
|
||||
PB_RETURN_ERROR(stream, "wrong wire type");
|
||||
|
||||
case PB_LTYPE_FIXED32:
|
||||
return wire_type == PB_WT_32BIT;
|
||||
|
||||
case PB_LTYPE_FIXED64:
|
||||
return wire_type == PB_WT_64BIT;
|
||||
|
||||
case PB_LTYPE_BYTES:
|
||||
case PB_LTYPE_STRING:
|
||||
case PB_LTYPE_SUBMESSAGE:
|
||||
case PB_LTYPE_SUBMSG_W_CB:
|
||||
case PB_LTYPE_FIXED_LENGTH_BYTES:
|
||||
return wire_type == PB_WT_STRING;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool checkreturn decode_basic_field(pb_istream_t *stream, pb_field_iter_t *field)
|
||||
{
|
||||
switch (PB_LTYPE(field->type))
|
||||
{
|
||||
case PB_LTYPE_BOOL:
|
||||
return pb_dec_bool(stream, field);
|
||||
|
||||
case PB_LTYPE_VARINT:
|
||||
case PB_LTYPE_UVARINT:
|
||||
case PB_LTYPE_SVARINT:
|
||||
if (wire_type != PB_WT_VARINT && wire_type != PB_WT_PACKED)
|
||||
PB_RETURN_ERROR(stream, "wrong wire type");
|
||||
|
||||
return pb_dec_varint(stream, field);
|
||||
|
||||
case PB_LTYPE_FIXED32:
|
||||
if (wire_type != PB_WT_32BIT && wire_type != PB_WT_PACKED)
|
||||
PB_RETURN_ERROR(stream, "wrong wire type");
|
||||
|
||||
return pb_decode_fixed32(stream, field->pData);
|
||||
|
||||
case PB_LTYPE_FIXED64:
|
||||
return pb_dec_fixed(stream, field);
|
||||
if (wire_type != PB_WT_64BIT && wire_type != PB_WT_PACKED)
|
||||
PB_RETURN_ERROR(stream, "wrong wire type");
|
||||
|
||||
#ifdef PB_CONVERT_DOUBLE_FLOAT
|
||||
if (field->data_size == sizeof(float))
|
||||
{
|
||||
return pb_decode_double_as_float(stream, (float*)field->pData);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PB_WITHOUT_64BIT
|
||||
PB_RETURN_ERROR(stream, "invalid data_size");
|
||||
#else
|
||||
return pb_decode_fixed64(stream, field->pData);
|
||||
#endif
|
||||
|
||||
case PB_LTYPE_BYTES:
|
||||
if (wire_type != PB_WT_STRING)
|
||||
PB_RETURN_ERROR(stream, "wrong wire type");
|
||||
|
||||
return pb_dec_bytes(stream, field);
|
||||
|
||||
case PB_LTYPE_STRING:
|
||||
if (wire_type != PB_WT_STRING)
|
||||
PB_RETURN_ERROR(stream, "wrong wire type");
|
||||
|
||||
return pb_dec_string(stream, field);
|
||||
|
||||
case PB_LTYPE_SUBMESSAGE:
|
||||
case PB_LTYPE_SUBMSG_W_CB:
|
||||
if (wire_type != PB_WT_STRING)
|
||||
PB_RETURN_ERROR(stream, "wrong wire type");
|
||||
|
||||
return pb_dec_submessage(stream, field);
|
||||
|
||||
case PB_LTYPE_FIXED_LENGTH_BYTES:
|
||||
if (wire_type != PB_WT_STRING)
|
||||
PB_RETURN_ERROR(stream, "wrong wire type");
|
||||
|
||||
return pb_dec_fixed_length_bytes(stream, field);
|
||||
|
||||
default:
|
||||
@@ -455,18 +466,12 @@ static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t
|
||||
switch (PB_HTYPE(field->type))
|
||||
{
|
||||
case PB_HTYPE_REQUIRED:
|
||||
if (!check_wire_type(wire_type, field))
|
||||
PB_RETURN_ERROR(stream, "wrong wire type");
|
||||
|
||||
return decode_basic_field(stream, field);
|
||||
return decode_basic_field(stream, wire_type, field);
|
||||
|
||||
case PB_HTYPE_OPTIONAL:
|
||||
if (!check_wire_type(wire_type, field))
|
||||
PB_RETURN_ERROR(stream, "wrong wire type");
|
||||
|
||||
if (field->pSize != NULL)
|
||||
*(bool*)field->pSize = true;
|
||||
return decode_basic_field(stream, field);
|
||||
return decode_basic_field(stream, wire_type, field);
|
||||
|
||||
case PB_HTYPE_REPEATED:
|
||||
if (wire_type == PB_WT_STRING
|
||||
@@ -483,7 +488,7 @@ static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t
|
||||
|
||||
while (substream.bytes_left > 0 && *size < field->array_size)
|
||||
{
|
||||
if (!decode_basic_field(&substream, field))
|
||||
if (!decode_basic_field(&substream, PB_WT_PACKED, field))
|
||||
{
|
||||
status = false;
|
||||
break;
|
||||
@@ -505,18 +510,15 @@ static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t
|
||||
pb_size_t *size = (pb_size_t*)field->pSize;
|
||||
field->pData = (char*)field->pField + field->data_size * (*size);
|
||||
|
||||
if (!check_wire_type(wire_type, field))
|
||||
PB_RETURN_ERROR(stream, "wrong wire type");
|
||||
|
||||
if ((*size)++ >= field->array_size)
|
||||
PB_RETURN_ERROR(stream, "array overflow");
|
||||
|
||||
return decode_basic_field(stream, field);
|
||||
return decode_basic_field(stream, wire_type, field);
|
||||
}
|
||||
|
||||
case PB_HTYPE_ONEOF:
|
||||
*(pb_size_t*)field->pSize = field->tag;
|
||||
if (PB_LTYPE_IS_SUBMSG(field->type))
|
||||
if (PB_LTYPE_IS_SUBMSG(field->type) &&
|
||||
*(pb_size_t*)field->pSize != field->tag)
|
||||
{
|
||||
/* We memset to zero so that any callbacks are set to NULL.
|
||||
* This is because the callbacks might otherwise have values
|
||||
@@ -526,12 +528,14 @@ static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t
|
||||
* that can set the fields before submessage is decoded.
|
||||
* pb_dec_submessage() will set any default values. */
|
||||
memset(field->pData, 0, (size_t)field->data_size);
|
||||
|
||||
/* Set default values for the submessage fields. */
|
||||
if (!pb_field_set_to_default(field))
|
||||
PB_RETURN_ERROR(stream, "failed to set defaults");
|
||||
}
|
||||
*(pb_size_t*)field->pSize = field->tag;
|
||||
|
||||
if (!check_wire_type(wire_type, field))
|
||||
PB_RETURN_ERROR(stream, "wrong wire type");
|
||||
|
||||
return decode_basic_field(stream, field);
|
||||
return decode_basic_field(stream, wire_type, field);
|
||||
|
||||
default:
|
||||
PB_RETURN_ERROR(stream, "invalid field type");
|
||||
@@ -599,14 +603,8 @@ static void initialize_pointer_field(void *pItem, pb_field_iter_t *field)
|
||||
else if (PB_LTYPE_IS_SUBMSG(field->type))
|
||||
{
|
||||
/* We memset to zero so that any callbacks are set to NULL.
|
||||
* Then set any default values. */
|
||||
pb_field_iter_t submsg_iter;
|
||||
* Default values will be set by pb_dec_submessage(). */
|
||||
memset(pItem, 0, field->data_size);
|
||||
|
||||
if (pb_field_iter_begin(&submsg_iter, field->submsg_desc, pItem))
|
||||
{
|
||||
(void)pb_message_set_to_defaults(&submsg_iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -623,9 +621,6 @@ static bool checkreturn decode_pointer_field(pb_istream_t *stream, pb_wire_type_
|
||||
case PB_HTYPE_REQUIRED:
|
||||
case PB_HTYPE_OPTIONAL:
|
||||
case PB_HTYPE_ONEOF:
|
||||
if (!check_wire_type(wire_type, field))
|
||||
PB_RETURN_ERROR(stream, "wrong wire type");
|
||||
|
||||
if (PB_LTYPE_IS_SUBMSG(field->type) && *(void**)field->pField != NULL)
|
||||
{
|
||||
/* Duplicate field, have to release the old allocation first. */
|
||||
@@ -643,7 +638,7 @@ static bool checkreturn decode_pointer_field(pb_istream_t *stream, pb_wire_type_
|
||||
{
|
||||
/* pb_dec_string and pb_dec_bytes handle allocation themselves */
|
||||
field->pData = field->pField;
|
||||
return decode_basic_field(stream, field);
|
||||
return decode_basic_field(stream, wire_type, field);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -652,7 +647,7 @@ static bool checkreturn decode_pointer_field(pb_istream_t *stream, pb_wire_type_
|
||||
|
||||
field->pData = *(void**)field->pField;
|
||||
initialize_pointer_field(field->pData, field);
|
||||
return decode_basic_field(stream, field);
|
||||
return decode_basic_field(stream, wire_type, field);
|
||||
}
|
||||
|
||||
case PB_HTYPE_REPEATED:
|
||||
@@ -700,7 +695,7 @@ static bool checkreturn decode_pointer_field(pb_istream_t *stream, pb_wire_type_
|
||||
/* Decode the array entry */
|
||||
field->pData = *(char**)field->pField + field->data_size * (*size);
|
||||
initialize_pointer_field(field->pData, field);
|
||||
if (!decode_basic_field(&substream, field))
|
||||
if (!decode_basic_field(&substream, PB_WT_PACKED, field))
|
||||
{
|
||||
status = false;
|
||||
break;
|
||||
@@ -721,16 +716,13 @@ static bool checkreturn decode_pointer_field(pb_istream_t *stream, pb_wire_type_
|
||||
if (*size == PB_SIZE_MAX)
|
||||
PB_RETURN_ERROR(stream, "too many array entries");
|
||||
|
||||
if (!check_wire_type(wire_type, field))
|
||||
PB_RETURN_ERROR(stream, "wrong wire type");
|
||||
|
||||
if (!allocate_field(stream, field->pField, field->data_size, (size_t)(*size + 1)))
|
||||
return false;
|
||||
|
||||
field->pData = *(char**)field->pField + field->data_size * (*size);
|
||||
(*size)++;
|
||||
initialize_pointer_field(field->pData, field);
|
||||
return decode_basic_field(stream, field);
|
||||
return decode_basic_field(stream, wire_type, field);
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -821,7 +813,7 @@ static bool checkreturn default_extension_decoder(pb_istream_t *stream,
|
||||
if (!pb_field_iter_begin_extension(&iter, extension))
|
||||
PB_RETURN_ERROR(stream, "invalid extension");
|
||||
|
||||
if (iter.tag != tag)
|
||||
if (iter.tag != tag || !iter.message)
|
||||
return true;
|
||||
|
||||
extension->found = true;
|
||||
@@ -831,9 +823,8 @@ static bool checkreturn default_extension_decoder(pb_istream_t *stream,
|
||||
/* Try to decode an unknown field as an extension field. Tries each extension
|
||||
* decoder in turn, until one of them handles the field or loop ends. */
|
||||
static bool checkreturn decode_extension(pb_istream_t *stream,
|
||||
uint32_t tag, pb_wire_type_t wire_type, pb_field_iter_t *iter)
|
||||
uint32_t tag, pb_wire_type_t wire_type, pb_extension_t *extension)
|
||||
{
|
||||
pb_extension_t *extension = *(pb_extension_t* const *)iter->pData;
|
||||
size_t pos = stream->bytes_left;
|
||||
|
||||
while (extension != NULL && pos == stream->bytes_left)
|
||||
@@ -853,22 +844,6 @@ static bool checkreturn decode_extension(pb_istream_t *stream,
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Step through the iterator until an extension field is found or until all
|
||||
* entries have been checked. There can be only one extension field per
|
||||
* message. Returns false if no extension field is found. */
|
||||
static bool checkreturn find_extension_field(pb_field_iter_t *iter)
|
||||
{
|
||||
pb_size_t start = iter->index;
|
||||
|
||||
do {
|
||||
if (PB_LTYPE(iter->type) == PB_LTYPE_EXTENSION)
|
||||
return true;
|
||||
(void)pb_field_iter_next(iter);
|
||||
} while (iter->index != start);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Initialize message fields to default values, recursively */
|
||||
static bool pb_field_set_to_default(pb_field_iter_t *field)
|
||||
{
|
||||
@@ -910,9 +885,14 @@ static bool pb_field_set_to_default(pb_field_iter_t *field)
|
||||
|
||||
if (init_data)
|
||||
{
|
||||
if (PB_LTYPE_IS_SUBMSG(field->type))
|
||||
if (PB_LTYPE_IS_SUBMSG(field->type) &&
|
||||
(field->submsg_desc->default_value != NULL ||
|
||||
field->submsg_desc->field_callback != NULL ||
|
||||
field->submsg_desc->submsg_info[0] != NULL))
|
||||
{
|
||||
/* Initialize submessage to defaults */
|
||||
/* Initialize submessage to defaults.
|
||||
* Only needed if it has default values
|
||||
* or callback/submessage fields. */
|
||||
pb_field_iter_t submsg_iter;
|
||||
if (pb_field_iter_begin(&submsg_iter, field->submsg_desc, field->pData))
|
||||
{
|
||||
@@ -989,6 +969,7 @@ static bool pb_message_set_to_defaults(pb_field_iter_t *iter)
|
||||
static bool checkreturn pb_decode_inner(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct, unsigned int flags)
|
||||
{
|
||||
uint32_t extension_range_start = 0;
|
||||
pb_extension_t *extensions = NULL;
|
||||
|
||||
/* 'fixed_count_field' and 'fixed_count_size' track position of a repeated fixed
|
||||
* count field. This can only handle _one_ repeated fixed count field that
|
||||
@@ -1040,25 +1021,31 @@ static bool checkreturn pb_decode_inner(pb_istream_t *stream, const pb_msgdesc_t
|
||||
if (!pb_field_iter_find(&iter, tag) || PB_LTYPE(iter.type) == PB_LTYPE_EXTENSION)
|
||||
{
|
||||
/* No match found, check if it matches an extension. */
|
||||
if (extension_range_start == 0)
|
||||
{
|
||||
if (pb_field_iter_find_extension(&iter))
|
||||
{
|
||||
extensions = *(pb_extension_t* const *)iter.pData;
|
||||
extension_range_start = iter.tag;
|
||||
}
|
||||
|
||||
if (!extensions)
|
||||
{
|
||||
extension_range_start = (uint32_t)-1;
|
||||
}
|
||||
}
|
||||
|
||||
if (tag >= extension_range_start)
|
||||
{
|
||||
if (!find_extension_field(&iter))
|
||||
extension_range_start = (uint32_t)-1;
|
||||
else
|
||||
extension_range_start = iter.tag;
|
||||
size_t pos = stream->bytes_left;
|
||||
|
||||
if (tag >= extension_range_start)
|
||||
if (!decode_extension(stream, tag, wire_type, extensions))
|
||||
return false;
|
||||
|
||||
if (pos != stream->bytes_left)
|
||||
{
|
||||
size_t pos = stream->bytes_left;
|
||||
|
||||
if (!decode_extension(stream, tag, wire_type, &iter))
|
||||
return false;
|
||||
|
||||
if (pos != stream->bytes_left)
|
||||
{
|
||||
/* The field was handled */
|
||||
continue;
|
||||
}
|
||||
/* The field was handled */
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1112,27 +1099,15 @@ static bool checkreturn pb_decode_inner(pb_istream_t *stream, const pb_msgdesc_t
|
||||
|
||||
/* Check that all required fields were present. */
|
||||
{
|
||||
/* First figure out the number of required fields by
|
||||
* seeking to the end of the field array. Usually we
|
||||
* are already close to end after decoding.
|
||||
*/
|
||||
pb_size_t req_field_count;
|
||||
pb_type_t last_type;
|
||||
pb_size_t i;
|
||||
do {
|
||||
req_field_count = iter.required_field_index;
|
||||
last_type = iter.type;
|
||||
} while (pb_field_iter_next(&iter));
|
||||
|
||||
/* Fixup if last field was also required. */
|
||||
if (PB_HTYPE(last_type) == PB_HTYPE_REQUIRED && iter.tag != 0)
|
||||
req_field_count++;
|
||||
|
||||
if (req_field_count > PB_MAX_REQUIRED_FIELDS)
|
||||
req_field_count = PB_MAX_REQUIRED_FIELDS;
|
||||
pb_size_t req_field_count = iter.descriptor->required_field_count;
|
||||
|
||||
if (req_field_count > 0)
|
||||
{
|
||||
pb_size_t i;
|
||||
|
||||
if (req_field_count > PB_MAX_REQUIRED_FIELDS)
|
||||
req_field_count = PB_MAX_REQUIRED_FIELDS;
|
||||
|
||||
/* Check the whole words */
|
||||
for (i = 0; i < (req_field_count >> 5); i++)
|
||||
{
|
||||
@@ -1277,7 +1252,7 @@ static void pb_release_single_field(pb_field_iter_t *field)
|
||||
|
||||
if (field->pData)
|
||||
{
|
||||
while (count--)
|
||||
for (; count > 0; count--)
|
||||
{
|
||||
pb_release(field->submsg_desc, field->pData);
|
||||
field->pData = (char*)field->pData + field->data_size;
|
||||
@@ -1294,7 +1269,7 @@ static void pb_release_single_field(pb_field_iter_t *field)
|
||||
/* Release entries in repeated string or bytes array */
|
||||
void **pItem = *(void***)field->pField;
|
||||
pb_size_t count = *(pb_size_t*)field->pSize;
|
||||
while (count--)
|
||||
for (; count > 0; count--)
|
||||
{
|
||||
pb_free(*pItem);
|
||||
*pItem++ = NULL;
|
||||
@@ -1454,7 +1429,7 @@ static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_iter_
|
||||
|
||||
/* See issue 97: Google's C++ protobuf allows negative varint values to
|
||||
* be cast as int32_t, instead of the int64_t that should be used when
|
||||
* encoding. Previous nanopb versions had a bug in encoding. In order to
|
||||
* encoding. Nanopb versions before 0.2.5 had a bug in encoding. In order to
|
||||
* not break decoding of such messages, we cast <=32 bit fields to
|
||||
* int32_t first to get the sign correct.
|
||||
*/
|
||||
@@ -1483,31 +1458,6 @@ static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_iter_
|
||||
}
|
||||
}
|
||||
|
||||
static bool checkreturn pb_dec_fixed(pb_istream_t *stream, const pb_field_iter_t *field)
|
||||
{
|
||||
#ifdef PB_CONVERT_DOUBLE_FLOAT
|
||||
if (field->data_size == sizeof(float) && PB_LTYPE(field->type) == PB_LTYPE_FIXED64)
|
||||
{
|
||||
return pb_decode_double_as_float(stream, (float*)field->pData);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (field->data_size == sizeof(uint32_t))
|
||||
{
|
||||
return pb_decode_fixed32(stream, field->pData);
|
||||
}
|
||||
#ifndef PB_WITHOUT_64BIT
|
||||
else if (field->data_size == sizeof(uint64_t))
|
||||
{
|
||||
return pb_decode_fixed64(stream, field->pData);
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
PB_RETURN_ERROR(stream, "invalid data_size");
|
||||
}
|
||||
}
|
||||
|
||||
static bool checkreturn pb_dec_bytes(pb_istream_t *stream, const pb_field_iter_t *field)
|
||||
{
|
||||
uint32_t size;
|
||||
@@ -1601,6 +1551,7 @@ static bool checkreturn pb_dec_string(pb_istream_t *stream, const pb_field_iter_
|
||||
static bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_iter_t *field)
|
||||
{
|
||||
bool status = true;
|
||||
bool submsg_consumed = false;
|
||||
pb_istream_t substream;
|
||||
|
||||
if (!pb_make_string_substream(stream, &substream))
|
||||
@@ -1609,19 +1560,6 @@ static bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_i
|
||||
if (field->submsg_desc == NULL)
|
||||
PB_RETURN_ERROR(stream, "invalid field descriptor");
|
||||
|
||||
/* New array entries need to be initialized, while required and optional
|
||||
* submessages have already been initialized in the top-level pb_decode. */
|
||||
if (PB_HTYPE(field->type) == PB_HTYPE_REPEATED ||
|
||||
PB_HTYPE(field->type) == PB_HTYPE_ONEOF)
|
||||
{
|
||||
pb_field_iter_t submsg_iter;
|
||||
if (pb_field_iter_begin(&submsg_iter, field->submsg_desc, field->pData))
|
||||
{
|
||||
if (!pb_message_set_to_defaults(&submsg_iter))
|
||||
PB_RETURN_ERROR(stream, "failed to set defaults");
|
||||
}
|
||||
}
|
||||
|
||||
/* Submessages can have a separate message-level callback that is called
|
||||
* before decoding the message. Typically it is used to set callback fields
|
||||
* inside oneofs. */
|
||||
@@ -1632,13 +1570,28 @@ static bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_i
|
||||
if (callback->funcs.decode)
|
||||
{
|
||||
status = callback->funcs.decode(&substream, field, &callback->arg);
|
||||
|
||||
if (substream.bytes_left == 0)
|
||||
{
|
||||
submsg_consumed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Now decode the submessage contents */
|
||||
if (status)
|
||||
if (status && !submsg_consumed)
|
||||
{
|
||||
status = pb_decode_inner(&substream, field->submsg_desc, field->pData, 0);
|
||||
unsigned int flags = 0;
|
||||
|
||||
/* Static required/optional fields are already initialized by top-level
|
||||
* pb_decode(), no need to initialize them again. */
|
||||
if (PB_ATYPE(field->type) == PB_ATYPE_STATIC &&
|
||||
PB_HTYPE(field->type) != PB_HTYPE_REPEATED)
|
||||
{
|
||||
flags = PB_DECODE_NOINIT;
|
||||
}
|
||||
|
||||
status = pb_decode_inner(&substream, field->submsg_desc, field->pData, flags);
|
||||
}
|
||||
|
||||
if (!pb_close_string_substream(stream, &substream))
|
||||
@@ -1692,37 +1645,41 @@ bool pb_decode_double_as_float(pb_istream_t *stream, float *dest)
|
||||
{
|
||||
/* Special value */
|
||||
exponent = 128;
|
||||
}
|
||||
else if (exponent > 127)
|
||||
{
|
||||
/* Too large, convert to infinity */
|
||||
exponent = 128;
|
||||
mantissa = 0;
|
||||
}
|
||||
else if (exponent < -150)
|
||||
{
|
||||
/* Too small, convert to zero */
|
||||
exponent = -127;
|
||||
mantissa = 0;
|
||||
}
|
||||
else if (exponent < -126)
|
||||
{
|
||||
/* Denormalized */
|
||||
mantissa |= 0x1000000;
|
||||
mantissa >>= (-126 - exponent);
|
||||
exponent = -127;
|
||||
}
|
||||
|
||||
/* Round off mantissa */
|
||||
mantissa = (mantissa + 1) >> 1;
|
||||
|
||||
/* Check if mantissa went over 2.0 */
|
||||
if (mantissa & 0x800000)
|
||||
{
|
||||
exponent += 1;
|
||||
mantissa &= 0x7FFFFF;
|
||||
mantissa >>= 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (exponent > 127)
|
||||
{
|
||||
/* Too large, convert to infinity */
|
||||
exponent = 128;
|
||||
mantissa = 0;
|
||||
}
|
||||
else if (exponent < -150)
|
||||
{
|
||||
/* Too small, convert to zero */
|
||||
exponent = -127;
|
||||
mantissa = 0;
|
||||
}
|
||||
else if (exponent < -126)
|
||||
{
|
||||
/* Denormalized */
|
||||
mantissa |= 0x1000000;
|
||||
mantissa >>= (-126 - exponent);
|
||||
exponent = -127;
|
||||
}
|
||||
|
||||
/* Round off mantissa */
|
||||
mantissa = (mantissa + 1) >> 1;
|
||||
|
||||
/* Check if mantissa went over 2.0 */
|
||||
if (mantissa & 0x800000)
|
||||
{
|
||||
exponent += 1;
|
||||
mantissa &= 0x7FFFFF;
|
||||
mantissa >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Combine fields */
|
||||
out.i = mantissa;
|
||||
|
||||
@@ -82,8 +82,11 @@ bool checkreturn pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t cou
|
||||
{
|
||||
if (count > 0 && stream->callback != NULL)
|
||||
{
|
||||
if (stream->bytes_written + count > stream->max_size)
|
||||
if (stream->bytes_written + count < stream->bytes_written ||
|
||||
stream->bytes_written + count > stream->max_size)
|
||||
{
|
||||
PB_RETURN_ERROR(stream, "stream full");
|
||||
}
|
||||
|
||||
#ifdef PB_BUFFER_ONLY
|
||||
if (!buf_write(stream, buf, count))
|
||||
@@ -262,9 +265,33 @@ static bool checkreturn pb_check_proto3_default_value(const pb_field_iter_t *fie
|
||||
* submessage fields. */
|
||||
return safe_read_bool(field->pSize) == false;
|
||||
}
|
||||
else if (field->descriptor->default_value)
|
||||
{
|
||||
/* Proto3 messages do not have default values, but proto2 messages
|
||||
* can contain optional fields without has_fields (generator option 'proto3').
|
||||
* In this case they must always be encoded, to make sure that the
|
||||
* non-zero default value is overwritten.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Rest is proto3 singular fields */
|
||||
if (PB_LTYPE(type) == PB_LTYPE_BYTES)
|
||||
if (PB_LTYPE(type) <= PB_LTYPE_LAST_PACKABLE)
|
||||
{
|
||||
/* Simple integer / float fields */
|
||||
pb_size_t i;
|
||||
const char *p = (const char*)field->pData;
|
||||
for (i = 0; i < field->data_size; i++)
|
||||
{
|
||||
if (p[i] != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (PB_LTYPE(type) == PB_LTYPE_BYTES)
|
||||
{
|
||||
const pb_bytes_array_t *bytes = (const pb_bytes_array_t*)field->pData;
|
||||
return bytes->size == 0;
|
||||
@@ -302,27 +329,29 @@ static bool checkreturn pb_check_proto3_default_value(const pb_field_iter_t *fie
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
else if (PB_ATYPE(type) == PB_ATYPE_POINTER)
|
||||
{
|
||||
/* Catch-all branch that does byte-per-byte comparison for zero value.
|
||||
*
|
||||
* This is for all pointer fields, and for static PB_LTYPE_VARINT,
|
||||
* UVARINT, SVARINT, FIXED32, FIXED64, EXTENSION fields, and also
|
||||
* callback fields. These all have integer or pointer value which
|
||||
* can be compared with 0.
|
||||
*/
|
||||
pb_size_t i;
|
||||
const char *p = (const char*)field->pData;
|
||||
for (i = 0; i < field->data_size; i++)
|
||||
{
|
||||
if (p[i] != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return field->pData == NULL;
|
||||
}
|
||||
else if (PB_ATYPE(type) == PB_ATYPE_CALLBACK)
|
||||
{
|
||||
if (PB_LTYPE(type) == PB_LTYPE_EXTENSION)
|
||||
{
|
||||
const pb_extension_t *extension = *(const pb_extension_t* const *)field->pData;
|
||||
return extension == NULL;
|
||||
}
|
||||
else if (field->descriptor->field_callback == pb_default_field_callback)
|
||||
{
|
||||
pb_callback_t *pCallback = (pb_callback_t*)field->pData;
|
||||
return pCallback->funcs.encode == NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
return field->descriptor->field_callback == NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return false; /* Not typically reached, safe default for weird special cases. */
|
||||
}
|
||||
|
||||
/* Encode a field with static or pointer allocation, i.e. one whose data
|
||||
@@ -823,7 +852,7 @@ static bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_iter_t
|
||||
}
|
||||
|
||||
if (PB_ATYPE(field->type) == PB_ATYPE_STATIC &&
|
||||
PB_BYTES_ARRAY_T_ALLOCSIZE(bytes->size) > field->data_size)
|
||||
bytes->size > field->data_size - offsetof(pb_bytes_array_t, bytes))
|
||||
{
|
||||
PB_RETURN_ERROR(stream, "bytes size exceeded");
|
||||
}
|
||||
|
||||
@@ -9,8 +9,13 @@
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[platformio]
|
||||
default_envs = tbeam # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you'd like to change the default to something like lora-relay-v1 put that here
|
||||
;default_envs = heltec # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you'd like to change the default to something like lora-relay-v1 put that here
|
||||
;default_envs = tbeam
|
||||
;default_envs = tbeam0.7
|
||||
;default_envs = heltec
|
||||
;default_envs = tlora-v1
|
||||
;default_envs = tlora-v2
|
||||
;default_envs = lora-relay-v1 # nrf board
|
||||
default_envs = linux # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you'd like to change the default to something like lora-relay-v1 put that here
|
||||
|
||||
[common]
|
||||
; common is not currently used
|
||||
@@ -19,24 +24,25 @@ default_envs = tbeam # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you
|
||||
; 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]
|
||||
|
||||
; note: APP_VERSION now comes from bin/version.json
|
||||
extra_scripts = bin/platformio-custom.py
|
||||
|
||||
; note: we add src to our include search path so that lmic_project_config can override
|
||||
; FIXME: fix lib/BluetoothOTA dependency back on src/ so we can remove -Isrc
|
||||
build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -Ilib/nanopb/include -Wl,-Map,.pio/build/output.map
|
||||
-DHW_VERSION_${sysenv.COUNTRY}
|
||||
-DAPP_VERSION=${sysenv.APP_VERSION}
|
||||
-DHW_VERSION=${sysenv.HW_VERSION}
|
||||
-DUSE_THREAD_NAMES
|
||||
-DTINYGPSPLUS_OPTION_NO_CUSTOM_FIELDS
|
||||
|
||||
; leave this commented out to avoid breaking Windows
|
||||
;upload_port = /dev/ttyUSB0
|
||||
;monitor_port = /dev/ttyUSB0
|
||||
|
||||
; geeksville: I think setting this should not be required - it breaks linux
|
||||
;upload_port = /dev/cu.SLAB_USBtoUART
|
||||
;monitor_port = /dev/cu.SLAB_USBtoUART
|
||||
|
||||
@@ -64,8 +70,8 @@ lib_deps =
|
||||
1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib
|
||||
https://github.com/meshtastic/arduino-fsm.git#2f106146071fc7bc620e1e8d4b88dc4e0266ce39
|
||||
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git#31015a55e630a2df77d9d714669c621a5bf355ad
|
||||
https://github.com/meshtastic/RadioLib.git#8657380241bce681c33aab46598bbf13b11f876c
|
||||
https://github.com/meshtastic/TinyGPSPlus.git
|
||||
https://github.com/meshtastic/RadioLib.git#07de964e929238949035fb0d5887026a3058df1a
|
||||
https://github.com/meshtastic/TinyGPSPlus.git#9c1d584d2469523381e077b0b9c1bf868d6c0206
|
||||
https://github.com/meshtastic/AXP202X_Library.git#8404abb6d4b486748636bc6ad72d2a47baaf5460
|
||||
Wire ; explicitly needed here because the AXP202 library forgets to add it
|
||||
SPI
|
||||
@@ -95,11 +101,14 @@ build_flags =
|
||||
${arduino_base.build_flags} -Wall -Wextra -Isrc/esp32 -Isrc/esp32-mfix-esp32-psram-cache-issue -lnimble -std=c++11
|
||||
-DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
|
||||
-DAXP_DEBUG_PORT=Serial
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
https://github.com/meshtastic/esp32_https_server.git
|
||||
# Hmm - this doesn't work yet
|
||||
# board_build.ldscript = linker/esp32.extram.bss.ld
|
||||
lib_ignore = segger_rtt
|
||||
platform_packages =
|
||||
framework-arduinoespressif32@https://github.com/meshtastic/arduino-esp32.git#2814f110aa618429bdd9a0a2d6a93c55f29f87a6
|
||||
framework-arduinoespressif32@https://github.com/meshtastic/arduino-esp32.git#352c8ea7cb73f10433ed139f34251979c470ad56
|
||||
|
||||
; customize the partition table
|
||||
; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
|
||||
@@ -117,7 +126,7 @@ board_build.partitions = partition-table.csv
|
||||
extends = esp32_base
|
||||
board = ttgo-t-beam
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
${esp32_base.lib_deps}
|
||||
build_flags =
|
||||
${esp32_base.build_flags} -D TBEAM_V10
|
||||
|
||||
@@ -176,10 +185,10 @@ build_type = debug ; I'm debugging with ICE a lot now
|
||||
build_flags =
|
||||
${arduino_base.build_flags} -Wno-unused-variable
|
||||
-Isrc/nrf52
|
||||
-Isdk-nrfxlib/crypto/nrf_oberon/include -Lsdk-nrfxlib/crypto/nrf_oberon/lib/cortex-m4/hard-float/ -lliboberon_3.0.3
|
||||
-Isdk-nrfxlib/crypto/nrf_oberon/include -Lsdk-nrfxlib/crypto/nrf_oberon/lib/cortex-m4/hard-float/ -lliboberon_3.0.7
|
||||
;-DCFG_DEBUG=3
|
||||
src_filter =
|
||||
${arduino_base.src_filter} -<esp32/> -<nimble/> -<meshwifi/>
|
||||
${arduino_base.src_filter} -<esp32/> -<nimble/> -<mesh/wifi/> -<mesh/http/>
|
||||
lib_ignore =
|
||||
BluetoothOTA
|
||||
monitor_port = /dev/ttyACM1
|
||||
@@ -234,6 +243,15 @@ lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
UC1701
|
||||
|
||||
; The PPR board
|
||||
[env:ppr1]
|
||||
extends = nrf52_base
|
||||
board = ppr1
|
||||
build_flags = ${nrf52_base.build_flags} -Ivariants/ppr1
|
||||
src_filter = ${nrf52_base.src_filter} +<../variants/ppr1>
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
|
||||
; Prototype eink/nrf52840/sx1262 device
|
||||
[env:eink]
|
||||
extends = nrf52_base
|
||||
@@ -269,12 +287,45 @@ lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
SparkFun BQ27441 LiPo Fuel Gauge Arduino Library
|
||||
TFT_eSPI
|
||||
# Adafruit ST7735 and ST7789 Library
|
||||
|
||||
; The https://github.com/BigCorvus/LoRa-BLE-Relay-v2 board by @BigCorvus
|
||||
[env:lora-relay-v2]
|
||||
extends = nrf52_base
|
||||
board = lora-relay-v2
|
||||
# add our variants files to the include and src paths
|
||||
# define build flags for the TFT_eSPI library
|
||||
build_flags = ${nrf52_base.build_flags} -Ivariants/lora_relay_v2
|
||||
-DUSER_SETUP_LOADED
|
||||
-DTFT_WIDTH=80
|
||||
-DTFT_HEIGHT=160
|
||||
-DST7735_GREENTAB160x80
|
||||
-DST7735_DRIVER
|
||||
-DTFT_CS=ST7735_CS
|
||||
-DTFT_DC=ST7735_RS
|
||||
-DTFT_RST=ST7735_RESET
|
||||
-DSPI_FREQUENCY=27000000
|
||||
-DTFT_WR=ST7735_SDA
|
||||
-DTFT_SCLK=ST7735_SCK
|
||||
src_filter = ${nrf52_base.src_filter} +<../variants/lora_relay_v2>
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
SparkFun BQ27441 LiPo Fuel Gauge Arduino Library
|
||||
TFT_eSPI
|
||||
|
||||
; The Portduino based sim environment on top of linux
|
||||
[env:linux]
|
||||
platform = https://github.com/geeksville/platform-portduino.git
|
||||
src_filter = ${env.src_filter} -<esp32/> -<nimble/> -<nrf52/> -<meshwifi/>
|
||||
src_filter = ${env.src_filter} -<esp32/> -<nimble/> -<nrf52/> -<mesh/http/>
|
||||
build_flags = ${arduino_base.build_flags} -O0
|
||||
framework = arduino
|
||||
board = linux_x86_64
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
rweather/Crypto
|
||||
|
||||
; The GenieBlocks LORA prototype board
|
||||
[env:genieblocks_lora]
|
||||
extends = esp32_base
|
||||
board = genieblocks_lora
|
||||
build_flags =
|
||||
${esp32_base.build_flags} -D GENIEBLOCKS
|
||||
|
||||
2
proto
2
proto
Submodule proto updated: a0b8d88896...855da8701e
Submodule sdk-nrfxlib updated: 17e8453553...e6e02cb83d
21
src/FSCommon.cpp
Normal file
21
src/FSCommon.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "FSCommon.h"
|
||||
|
||||
void fsInit()
|
||||
{
|
||||
#ifdef FS
|
||||
if (!FSBegin())
|
||||
{
|
||||
DEBUG_MSG("ERROR filesystem mount Failed\n");
|
||||
assert(0); // FIXME - report failure to phone
|
||||
}
|
||||
|
||||
DEBUG_MSG("Filesystem files:\n");
|
||||
File dir = FS.open("/");
|
||||
File f = dir.openNextFile();
|
||||
while (f) {
|
||||
DEBUG_MSG(" %s\n", f.name());
|
||||
f.close();
|
||||
f = dir.openNextFile();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
29
src/FSCommon.h
Normal file
29
src/FSCommon.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "configuration.h"
|
||||
|
||||
// Cross platform filesystem API
|
||||
|
||||
#ifdef PORTDUINO
|
||||
// Portduino version
|
||||
#include "PortduinoFS.h"
|
||||
#define FS PortduinoFS
|
||||
#define FSBegin() true
|
||||
#define FILE_O_WRITE "w"
|
||||
#define FILE_O_READ "r"
|
||||
#elif !defined(NO_ESP32)
|
||||
// ESP32 version
|
||||
#include "SPIFFS.h"
|
||||
#define FS SPIFFS
|
||||
#define FSBegin() FS.begin(true)
|
||||
#define FILE_O_WRITE "w"
|
||||
#define FILE_O_READ "r"
|
||||
#else
|
||||
// NRF52 version
|
||||
#include "InternalFileSystem.h"
|
||||
#define FS InternalFS
|
||||
#define FSBegin() FS.begin()
|
||||
using namespace Adafruit_LittleFS_Namespace;
|
||||
#endif
|
||||
|
||||
void fsInit();
|
||||
@@ -16,6 +16,10 @@ template <class T> class Observer
|
||||
public:
|
||||
virtual ~Observer();
|
||||
|
||||
/// Stop watching our current obserable
|
||||
void unobserve();
|
||||
|
||||
/// Start watching a specified observable
|
||||
void observe(Observable<T> *o);
|
||||
|
||||
private:
|
||||
@@ -82,6 +86,11 @@ template <class T> class Observable
|
||||
};
|
||||
|
||||
template <class T> Observer<T>::~Observer()
|
||||
{
|
||||
unobserve();
|
||||
}
|
||||
|
||||
template <class T> void Observer<T>::unobserve()
|
||||
{
|
||||
if (observed)
|
||||
observed->removeObserver(this);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "power.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "main.h"
|
||||
#include "sleep.h"
|
||||
@@ -18,6 +19,21 @@ Power *power;
|
||||
|
||||
using namespace meshtastic;
|
||||
|
||||
#if defined(NRF52_SERIES)
|
||||
/*
|
||||
* Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4,
|
||||
* 1/3, 1/2 or 1, meaning 3.6, 3.0, 2.4, 1.8, 1.2 or 0.6V for the ADC levels.
|
||||
*
|
||||
* External Reference is VDD/4, with an adjustable gain of 1, 2 or 4, meaning
|
||||
* VDD/4, VDD/2 or VDD for the ADC levels.
|
||||
*
|
||||
* Default settings are internal reference with 1/6 gain (GND..3.6V ADC range)
|
||||
*/
|
||||
#define AREF_VOLTAGE 3.6
|
||||
#else
|
||||
#define AREF_VOLTAGE 3.3
|
||||
#endif
|
||||
|
||||
/**
|
||||
* If this board has a battery level sensor, set this to a valid implementation
|
||||
*/
|
||||
@@ -37,10 +53,13 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
{
|
||||
float v = getBattVoltage() / 1000;
|
||||
|
||||
if (v < 2.1)
|
||||
if (v < noBatVolt)
|
||||
return -1; // If voltage is super low assume no battery installed
|
||||
|
||||
return 100 * (v - 3.27) / (4.2 - 3.27);
|
||||
if (v > chargingVolt)
|
||||
return 0; // While charging we can't report % full on the battery
|
||||
|
||||
return 100 * (v - emptyVolt) / (fullVolt - emptyVolt);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,9 +67,11 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
*/
|
||||
virtual float getBattVoltage()
|
||||
{
|
||||
// Tested ttgo eink nrf52 board and the reported value is perfect
|
||||
// DEBUG_MSG("raw val %u", raw);
|
||||
return
|
||||
#ifdef BATTERY_PIN
|
||||
1000.0 * analogRead(BATTERY_PIN) * 2.0 * (3.3 / 1024.0);
|
||||
1000.0 * 2.0 * (AREF_VOLTAGE / 1024.0) * analogRead(BATTERY_PIN);
|
||||
#else
|
||||
NAN;
|
||||
#endif
|
||||
@@ -59,7 +80,20 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
/**
|
||||
* return true if there is a battery installed in this unit
|
||||
*/
|
||||
virtual bool isBatteryConnect() { return getBattVoltage() != -1; }
|
||||
virtual bool isBatteryConnect() { return getBattPercentage() != -1; }
|
||||
|
||||
/// If we see a battery voltage higher than physics allows - assume charger is pumping
|
||||
/// in power
|
||||
virtual bool isVBUSPlug() { return getBattVoltage() > chargingVolt; }
|
||||
|
||||
/// Assume charging if we have a battery and external power is connected.
|
||||
/// we can't be smart enough to say 'full'?
|
||||
virtual bool isChargeing() { return isBatteryConnect() && isVBUSPlug(); }
|
||||
|
||||
private:
|
||||
/// If we see a battery voltage higher than physics allows - assume charger is pumping
|
||||
/// in power
|
||||
const float fullVolt = 4.2, emptyVolt = 3.27, chargingVolt = 4.3, noBatVolt = 2.1;
|
||||
} analogLevel;
|
||||
|
||||
Power::Power() : OSThread("Power") {}
|
||||
@@ -68,10 +102,18 @@ bool Power::analogInit()
|
||||
{
|
||||
#ifdef BATTERY_PIN
|
||||
DEBUG_MSG("Using analog input for battery level\n");
|
||||
|
||||
// disable any internal pullups
|
||||
pinMode(BATTERY_PIN, INPUT);
|
||||
|
||||
#ifndef NO_ESP32
|
||||
// ESP32 needs special analog stuff
|
||||
adcAttachPin(BATTERY_PIN);
|
||||
#endif
|
||||
#ifdef NRF52_SERIES
|
||||
analogReference(AR_INTERNAL); // 3.6V
|
||||
#endif
|
||||
|
||||
// adcStart(BATTERY_PIN);
|
||||
analogReadResolution(10); // Default of 12 is not very linear. Recommended to use 10 or 11 depending on needed resolution.
|
||||
batteryLevel = &analogLevel;
|
||||
@@ -93,6 +135,14 @@ bool Power::setup()
|
||||
return found;
|
||||
}
|
||||
|
||||
void Power::shutdown()
|
||||
{
|
||||
#ifdef TBEAM_V10
|
||||
DEBUG_MSG("Shutting down\n");
|
||||
axp.shutdown();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Reads power status to powerStatus singleton.
|
||||
//
|
||||
// TODO(girts): move this and other axp stuff to power.h/power.cpp.
|
||||
@@ -121,7 +171,8 @@ void Power::readPowerStatus()
|
||||
const PowerStatus powerStatus =
|
||||
PowerStatus(hasBattery ? OptTrue : OptFalse, batteryLevel->isVBUSPlug() ? OptTrue : OptFalse,
|
||||
batteryLevel->isChargeing() ? OptTrue : OptFalse, batteryVoltageMv, batteryChargePercent);
|
||||
DEBUG_MSG("Read power stat %d\n", powerStatus.getHasUSB());
|
||||
DEBUG_MSG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d\n", powerStatus.getHasUSB(),
|
||||
powerStatus.getIsCharging(), powerStatus.getBatteryVoltageMv(), powerStatus.getBatteryChargePercent());
|
||||
newStatus.notifyObservers(&powerStatus);
|
||||
|
||||
// If we have a battery at all and it is less than 10% full, force deep sleep
|
||||
@@ -218,7 +269,42 @@ bool Power::axp192Init()
|
||||
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
|
||||
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
|
||||
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1320MA); // actual limit (in HW) on the tbeam is 450mA
|
||||
if (radioConfig.preferences.charge_current == ChargeCurrent_MAUnset) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_450MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA100) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_100MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA190) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_190MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA280) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_280MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA360) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_360MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA450) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_450MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA550) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_550MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA630) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_630MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA700) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_700MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA780) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_780MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA880) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_880MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA960) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_960MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA1000) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1000MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA1080) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1080MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA1160) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1160MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA1240) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1240MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA1320) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1320MA);
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
// Not connected
|
||||
|
||||
@@ -144,7 +144,8 @@ static void onEnter()
|
||||
|
||||
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 (now - lastPingMs >
|
||||
30 * 1000) { // if more than a minute since our last press, ask node we are looking at to update their state
|
||||
if (displayedNodeNum)
|
||||
service.sendNetworkPing(displayedNodeNum, true); // Refresh the currently displayed node
|
||||
lastPingMs = now;
|
||||
@@ -170,11 +171,18 @@ Fsm powerFSM(&stateBOOT);
|
||||
|
||||
void PowerFSM_setup()
|
||||
{
|
||||
bool isRouter = radioConfig.preferences.is_router;
|
||||
|
||||
// If we are not a router and we already have AC power go to POWER state after init, otherwise go to ON
|
||||
// We assume routers might be powered all the time, but from a low current (solar) source
|
||||
bool isLowPower = radioConfig.preferences.is_low_power;
|
||||
bool hasPower = !isLowPower && powerStatus && powerStatus->getHasUSB();
|
||||
bool isRouter = radioConfig.preferences.is_router;
|
||||
bool isLowPower = radioConfig.preferences.is_low_power || isRouter;
|
||||
|
||||
/* To determine if we're externally powered, assumptions
|
||||
1) If we're powered up and there's no battery, we must be getting power externally.
|
||||
2) If we detect USB power from the power management chip, we must be getting power externally.
|
||||
*/
|
||||
bool hasPower = (powerStatus && !powerStatus->getHasBattery()) || (!isLowPower && powerStatus && powerStatus->getHasUSB());
|
||||
|
||||
DEBUG_MSG("PowerFSM init, USB power=%d\n", hasPower);
|
||||
powerFSM.add_timed_transition(&stateBOOT, hasPower ? &statePOWER : &stateON, 3 * 1000, NULL, "boot timeout");
|
||||
|
||||
@@ -244,20 +252,24 @@ void PowerFSM_setup()
|
||||
|
||||
powerFSM.add_timed_transition(&stateON, &stateDARK, getPref_screen_on_secs() * 1000, NULL, "Screen-on timeout");
|
||||
|
||||
powerFSM.add_timed_transition(&stateDARK, &stateNB, getPref_phone_timeout_secs() * 1000, NULL, "Phone timeout");
|
||||
// On most boards we use light-sleep to be our main state, but on NRF52 we just stay in DARK
|
||||
State *lowPowerState = &stateLS;
|
||||
|
||||
#ifndef NRF52_SERIES
|
||||
// We never enter light-sleep state on NRF52 (because the CPU uses so little power normally)
|
||||
powerFSM.add_timed_transition(&stateNB, &stateLS, getPref_min_wake_secs() * 1000, NULL, "Min wake timeout");
|
||||
// We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally)
|
||||
|
||||
powerFSM.add_timed_transition(&stateDARK, &stateNB, getPref_phone_timeout_secs() * 1000, NULL, "Phone timeout");
|
||||
powerFSM.add_timed_transition(&stateNB, &stateLS, getPref_min_wake_secs() * 1000, NULL, "Min wake timeout");
|
||||
powerFSM.add_timed_transition(&stateDARK, &stateLS, getPref_wait_bluetooth_secs() * 1000, NULL, "Bluetooth timeout");
|
||||
#else
|
||||
lowPowerState = &stateDARK;
|
||||
#endif
|
||||
|
||||
auto meshSds = getPref_mesh_sds_timeout_secs();
|
||||
if (meshSds != UINT32_MAX)
|
||||
powerFSM.add_timed_transition(&stateLS, &stateSDS, meshSds * 1000, NULL, "mesh timeout");
|
||||
powerFSM.add_timed_transition(lowPowerState, &stateSDS, meshSds * 1000, NULL, "mesh timeout");
|
||||
// removing for now, because some users don't even have phones
|
||||
// powerFSM.add_timed_transition(&stateLS, &stateSDS, getPref_phone_sds_timeout_sec() * 1000, NULL, "phone
|
||||
// powerFSM.add_timed_transition(lowPowerState, &stateSDS, getPref_phone_sds_timeout_sec() * 1000, NULL, "phone
|
||||
// timeout");
|
||||
|
||||
powerFSM.run_machine(); // run one interation of the state machine, so we run our on enter tasks for the initial DARK state
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace meshtastic
|
||||
*/
|
||||
enum OptionalBool { OptFalse = 0, OptTrue = 1, OptUnknown = 2 };
|
||||
|
||||
/// Describes the state of the GPS system.
|
||||
/// Describes the state of the Power system.
|
||||
class PowerStatus : public Status
|
||||
{
|
||||
|
||||
@@ -82,7 +82,7 @@ class PowerStatus : public Status
|
||||
isCharging = newStatus->isCharging;
|
||||
}
|
||||
if (isDirty) {
|
||||
DEBUG_MSG("Battery %dmV %d%%\n", batteryVoltageMv, batteryChargePercent);
|
||||
// DEBUG_MSG("Battery %dmV %d%%\n", batteryVoltageMv, batteryChargePercent);
|
||||
onNewStatus.notifyObservers(this);
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#include "RedirectablePrint.h"
|
||||
#include "configuration.h"
|
||||
#include <assert.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include "concurrency/OSThread.h"
|
||||
|
||||
/**
|
||||
* A printer that doesn't go anywhere
|
||||
@@ -10,4 +14,86 @@ void RedirectablePrint::setDestination(Print *_dest)
|
||||
{
|
||||
assert(_dest);
|
||||
dest = _dest;
|
||||
}
|
||||
|
||||
size_t RedirectablePrint::write(uint8_t c)
|
||||
{
|
||||
// Always send the characters to our segger JTAG debugger
|
||||
#ifdef SEGGER_STDOUT_CH
|
||||
SEGGER_RTT_PutCharSkip(SEGGER_STDOUT_CH, c);
|
||||
#endif
|
||||
|
||||
dest->write(c);
|
||||
return 1; // We always claim one was written, rather than trusting what the serial port said (which could be zero)
|
||||
}
|
||||
|
||||
size_t RedirectablePrint::vprintf(const char *format, va_list arg)
|
||||
{
|
||||
va_list copy;
|
||||
|
||||
va_copy(copy, arg);
|
||||
int len = vsnprintf(printBuf, printBufLen, format, copy);
|
||||
va_end(copy);
|
||||
if (len < 0) {
|
||||
va_end(arg);
|
||||
return 0;
|
||||
};
|
||||
if (len >= printBufLen) {
|
||||
delete[] printBuf;
|
||||
printBufLen *= 2;
|
||||
printBuf = new char[printBufLen];
|
||||
len = vsnprintf(printBuf, printBufLen, format, arg);
|
||||
}
|
||||
|
||||
len = Print::write(printBuf, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
#define SEC_PER_DAY 86400
|
||||
#define SEC_PER_HOUR 3600
|
||||
#define SEC_PER_MIN 60
|
||||
|
||||
size_t RedirectablePrint::logDebug(const char *format, ...)
|
||||
{
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
|
||||
// Cope with 0 len format strings, but look for new line terminator
|
||||
bool hasNewline = *format && format[strlen(format) - 1] == '\n';
|
||||
|
||||
size_t r = 0;
|
||||
|
||||
// If we are the first message on a report, include the header
|
||||
if (!isContinuationMessage) {
|
||||
struct timeval tv;
|
||||
if (!gettimeofday(&tv, NULL)) {
|
||||
long hms = tv.tv_sec % SEC_PER_DAY;
|
||||
//hms += tz.tz_dsttime * SEC_PER_HOUR;
|
||||
//hms -= tz.tz_minuteswest * SEC_PER_MIN;
|
||||
// mod `hms` to ensure in positive range of [0...SEC_PER_DAY)
|
||||
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
|
||||
|
||||
// Tear apart hms into h:m:s
|
||||
int hour = hms / SEC_PER_HOUR;
|
||||
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
||||
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
|
||||
|
||||
r += printf("%02d:%02d:%02d ", hour, min, sec);
|
||||
} else
|
||||
r += printf("??:??:?? ");
|
||||
|
||||
auto thread = concurrency::OSThread::currentThread;
|
||||
if(thread) {
|
||||
print("[");
|
||||
print(thread->ThreadName);
|
||||
print("] ");
|
||||
}
|
||||
}
|
||||
|
||||
r += vprintf(format, arg);
|
||||
va_end(arg);
|
||||
|
||||
isContinuationMessage = !hasNewline;
|
||||
|
||||
return r;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <Print.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
/**
|
||||
* A Printable that can be switched to squirt its bytes to a different sink.
|
||||
@@ -11,6 +12,13 @@ class RedirectablePrint : public Print
|
||||
{
|
||||
Print *dest;
|
||||
|
||||
/// We dynamically grow this scratch buffer if necessary
|
||||
char *printBuf = new char[64];
|
||||
size_t printBufLen = 64;
|
||||
|
||||
/// Used to allow multiple logDebug messages to appear on a single log line
|
||||
bool isContinuationMessage = false;
|
||||
|
||||
public:
|
||||
RedirectablePrint(Print *_dest) : dest(_dest) {}
|
||||
|
||||
@@ -19,7 +27,22 @@ class RedirectablePrint : public Print
|
||||
*/
|
||||
void setDestination(Print *dest);
|
||||
|
||||
virtual size_t write(uint8_t c) { return dest->write(c); }
|
||||
virtual size_t write(uint8_t c);
|
||||
|
||||
/**
|
||||
* Debug logging print message
|
||||
*
|
||||
* If the provide format string ends with a newline we assume it is the final print of a single
|
||||
* log message. Otherwise we assume more prints will come before the log message ends. This
|
||||
* allows you to call logDebug a few times to build up a single log message line if you wish.
|
||||
*
|
||||
* FIXME, eventually add log levels (INFO, WARN, ERROR) and subsystems. Move into
|
||||
* a different class.
|
||||
*/
|
||||
size_t logDebug(const char * format, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
|
||||
/** like printf but va_list based */
|
||||
size_t vprintf(const char *format, va_list arg);
|
||||
};
|
||||
|
||||
class NoopPrint : public Print
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "SerialConsole.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "configuration.h"
|
||||
#include "NodeDB.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#define Port Serial
|
||||
@@ -28,7 +29,8 @@ void SerialConsole::init()
|
||||
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);
|
||||
if(!radioConfig.preferences.debug_log_enabled)
|
||||
setDestination(&noopPrint);
|
||||
canWrite = true;
|
||||
|
||||
StreamAPI::handleToRadio(buf, len);
|
||||
|
||||
107
src/airtime.cpp
Normal file
107
src/airtime.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
#include "airtime.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#define periodsToLog 48
|
||||
|
||||
AirTime *airTime;
|
||||
|
||||
uint32_t secondsPerPeriod = 3600;
|
||||
uint32_t lastMillis = 0;
|
||||
uint32_t secSinceBoot = 0;
|
||||
|
||||
// AirTime at;
|
||||
|
||||
// Don't read out of this directly. Use the helper functions.
|
||||
struct airtimeStruct {
|
||||
uint32_t periodTX[periodsToLog]; // AirTime transmitted
|
||||
uint32_t periodRX[periodsToLog]; // AirTime received and repeated (Only valid mesh packets)
|
||||
uint32_t periodRX_ALL[periodsToLog]; // AirTime received regardless of valid mesh packet. Could include noise.
|
||||
uint8_t lastPeriodIndex;
|
||||
} airtimes;
|
||||
|
||||
void AirTime::logAirtime(reportTypes reportType, uint32_t airtime_ms)
|
||||
{
|
||||
if (reportType == TX_LOG) {
|
||||
DEBUG_MSG("AirTime - Packet transmitted : %ums\n", airtime_ms);
|
||||
airtimes.periodTX[0] = airtimes.periodTX[0] + airtime_ms;
|
||||
} else if (reportType == RX_LOG) {
|
||||
DEBUG_MSG("AirTime - Packet received : %ums\n", airtime_ms);
|
||||
airtimes.periodRX[0] = airtimes.periodRX[0] + airtime_ms;
|
||||
} else if (reportType == RX_ALL_LOG) {
|
||||
DEBUG_MSG("AirTime - Packet received (noise?) : %ums\n", airtime_ms);
|
||||
airtimes.periodRX_ALL[0] = airtimes.periodRX_ALL[0] + airtime_ms;
|
||||
} else {
|
||||
DEBUG_MSG("AirTime - Unknown report time. This should never happen!!\n");
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t currentPeriodIndex()
|
||||
{
|
||||
return ((getSecondsSinceBoot() / secondsPerPeriod) % periodsToLog);
|
||||
}
|
||||
|
||||
void airtimeRotatePeriod()
|
||||
{
|
||||
|
||||
if (airtimes.lastPeriodIndex != currentPeriodIndex()) {
|
||||
DEBUG_MSG("Rotating airtimes to a new period = %u\n", currentPeriodIndex());
|
||||
|
||||
for (int i = periodsToLog - 2; i >= 0; --i) {
|
||||
airtimes.periodTX[i + 1] = airtimes.periodTX[i];
|
||||
airtimes.periodRX[i + 1] = airtimes.periodRX[i];
|
||||
airtimes.periodRX_ALL[i + 1] = airtimes.periodRX_ALL[i];
|
||||
}
|
||||
airtimes.periodTX[0] = 0;
|
||||
airtimes.periodRX[0] = 0;
|
||||
airtimes.periodRX_ALL[0] = 0;
|
||||
|
||||
airtimes.lastPeriodIndex = currentPeriodIndex();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t *airtimeReport(reportTypes reportType)
|
||||
{
|
||||
|
||||
if (reportType == TX_LOG) {
|
||||
return airtimes.periodTX;
|
||||
} else if (reportType == RX_LOG) {
|
||||
return airtimes.periodRX;
|
||||
} else if (reportType == RX_ALL_LOG) {
|
||||
return airtimes.periodRX_ALL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t getPeriodsToLog()
|
||||
{
|
||||
return periodsToLog;
|
||||
}
|
||||
|
||||
uint32_t getSecondsPerPeriod()
|
||||
{
|
||||
return secondsPerPeriod;
|
||||
}
|
||||
|
||||
uint32_t getSecondsSinceBoot()
|
||||
{
|
||||
return secSinceBoot;
|
||||
}
|
||||
|
||||
AirTime::AirTime() : concurrency::OSThread("AirTime") {}
|
||||
|
||||
int32_t AirTime::runOnce()
|
||||
{
|
||||
//DEBUG_MSG("AirTime::runOnce()\n");
|
||||
|
||||
airtimeRotatePeriod();
|
||||
secSinceBoot++;
|
||||
|
||||
/*
|
||||
This actually doesn't need to be run once per second but we currently use it for the
|
||||
secSinceBoot counter.
|
||||
|
||||
If we have a better counter of how long the device has been online (and not millis())
|
||||
then we can change this to something less frequent. Maybe once ever 5 seconds?
|
||||
*/
|
||||
return (1000 * 1);
|
||||
}
|
||||
54
src/airtime.h
Normal file
54
src/airtime.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "configuration.h"
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
|
||||
/*
|
||||
TX_LOG - Time on air this device has transmitted
|
||||
|
||||
RX_LOG - Time on air used by valid and routable mesh packets, does not include
|
||||
TX air time
|
||||
|
||||
RX_ALL_LOG - Time of all received lora packets. This includes packets that are not
|
||||
for meshtastic devices. Does not include TX air time.
|
||||
|
||||
Example analytics:
|
||||
|
||||
TX_LOG + RX_LOG = Total air time for a perticular meshtastic channel.
|
||||
|
||||
TX_LOG + RX_LOG = Total air time for a perticular meshtastic channel, including
|
||||
other lora radios.
|
||||
|
||||
RX_ALL_LOG - RX_LOG = Other lora radios on our frequency channel.
|
||||
*/
|
||||
enum reportTypes { TX_LOG, RX_LOG, RX_ALL_LOG };
|
||||
|
||||
void logAirtime(reportTypes reportType, uint32_t airtime_ms);
|
||||
|
||||
void airtimeRotatePeriod();
|
||||
|
||||
uint8_t currentPeriodIndex();
|
||||
uint8_t getPeriodsToLog();
|
||||
|
||||
uint32_t getSecondsSinceBoot();
|
||||
|
||||
uint32_t *airtimeReport(reportTypes reportType);
|
||||
|
||||
uint32_t getSecondsPerPeriod();
|
||||
|
||||
class AirTime : private concurrency::OSThread
|
||||
{
|
||||
|
||||
public:
|
||||
AirTime();
|
||||
|
||||
void logAirtime(reportTypes reportType, uint32_t airtime_ms);
|
||||
|
||||
protected:
|
||||
|
||||
virtual int32_t runOnce();
|
||||
};
|
||||
|
||||
extern AirTime *airTime;
|
||||
@@ -14,6 +14,8 @@ bool OSThread::showRun = false;
|
||||
/// Show debugging info for threads we decide not to run;
|
||||
bool OSThread::showWaiting = false;
|
||||
|
||||
const OSThread *OSThread::currentThread;
|
||||
|
||||
ThreadController mainController, timerController;
|
||||
InterruptableDelay mainDelay;
|
||||
|
||||
@@ -26,6 +28,8 @@ void OSThread::setup()
|
||||
OSThread::OSThread(const char *_name, uint32_t period, ThreadController *_controller)
|
||||
: Thread(NULL, period), controller(_controller)
|
||||
{
|
||||
assertIsSetup();
|
||||
|
||||
ThreadName = _name;
|
||||
|
||||
if (controller)
|
||||
@@ -68,12 +72,47 @@ bool OSThread::shouldRun(unsigned long time)
|
||||
|
||||
void OSThread::run()
|
||||
{
|
||||
currentThread = this;
|
||||
auto newDelay = runOnce();
|
||||
|
||||
runned();
|
||||
|
||||
if (newDelay >= 0)
|
||||
setInterval(newDelay);
|
||||
|
||||
currentThread = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* This flag is set **only** when setup() starts, to provide a way for us to check for sloppy static constructor calls.
|
||||
* Call assertIsSetup() to force a crash if someone tries to create an instance too early.
|
||||
*
|
||||
* it is super important to never allocate those object statically. instead, you should explicitly
|
||||
* new them at a point where you are guaranteed that other objects that this instance
|
||||
* depends on have already been created.
|
||||
*
|
||||
* in particular, for OSThread that means "all instances must be declared via new() in setup() or later" -
|
||||
* this makes it guaranteed that the global mainController is fully constructed first.
|
||||
*/
|
||||
bool hasBeenSetup;
|
||||
|
||||
void assertIsSetup()
|
||||
{
|
||||
|
||||
/**
|
||||
* Dear developer comrade - If this assert fails() that means you need to fix the following:
|
||||
*
|
||||
* This flag is set **only** when setup() starts, to provide a way for us to check for sloppy static constructor calls.
|
||||
* Call assertIsSetup() to force a crash if someone tries to create an instance too early.
|
||||
*
|
||||
* it is super important to never allocate those object statically. instead, you should explicitly
|
||||
* new them at a point where you are guaranteed that other objects that this instance
|
||||
* depends on have already been created.
|
||||
*
|
||||
* in particular, for OSThread that means "all instances must be declared via new() in setup() or later" -
|
||||
* this makes it guaranteed that the global mainController is fully constructed first.
|
||||
*/
|
||||
assert(hasBeenSetup);
|
||||
}
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
@@ -17,14 +17,14 @@ extern InterruptableDelay mainDelay;
|
||||
|
||||
/**
|
||||
* @brief Base threading
|
||||
*
|
||||
*
|
||||
* This is a pseudo threading layer that is super easy to port, well suited to our slow network and very ram & power efficient.
|
||||
*
|
||||
* TODO FIXME @geeksville
|
||||
*
|
||||
* move more things into OSThreads
|
||||
* remove lock/lockguard
|
||||
*
|
||||
*
|
||||
* move typedQueue into concurrency
|
||||
* remove freertos from typedqueue
|
||||
*/
|
||||
@@ -42,6 +42,9 @@ class OSThread : public Thread
|
||||
static bool showWaiting;
|
||||
|
||||
public:
|
||||
/// For debug printing only (might be null)
|
||||
static const OSThread *currentThread;
|
||||
|
||||
OSThread(const char *name, uint32_t period = 0, ThreadController *controller = &mainController);
|
||||
|
||||
virtual ~OSThread();
|
||||
@@ -67,4 +70,19 @@ class OSThread : public Thread
|
||||
virtual void run();
|
||||
};
|
||||
|
||||
/**
|
||||
* This flag is set **only** when setup() starts, to provide a way for us to check for sloppy static constructor calls.
|
||||
* Call assertIsSetup() to force a crash if someone tries to create an instance too early.
|
||||
*
|
||||
* it is super important to never allocate those object statically. instead, you should explicitly
|
||||
* new them at a point where you are guaranteed that other objects that this instance
|
||||
* depends on have already been created.
|
||||
*
|
||||
* in particular, for OSThread that means "all instances must be declared via new() in setup() or later" -
|
||||
* this makes it guaranteed that the global mainController is fully constructed first.
|
||||
*/
|
||||
extern bool hasBeenSetup;
|
||||
|
||||
void assertIsSetup();
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
@@ -31,9 +31,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// If app version is not specified we assume we are not being invoked by the build script
|
||||
#ifndef APP_VERSION
|
||||
#error APP_VERSION, HW_VERSION, and HW_VERSION_countryname must be set by the build environment
|
||||
//#define APP_VERSION 0.0.0 // this def normally comes from build-all.sh
|
||||
//#define HW_VERSION 1.0 - US // normally comes from build-all.sh and contains the region code
|
||||
#error APP_VERSION must be set by the build environment
|
||||
#endif
|
||||
|
||||
// If app version is not specified we assume we are not being invoked by the build script
|
||||
#ifndef HW_VERSION
|
||||
#error HW_VERSION, and HW_VERSION_countryname must be set by the build environment
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -139,6 +142,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#define SSD1306_ADDRESS 0x3C
|
||||
#define ST7567_ADDRESS 0x3F
|
||||
|
||||
// The SH1106 controller is almost, but not quite, the same as SSD1306
|
||||
// Define this if you know you have that controller or your "SSD1306" misbehaves.
|
||||
@@ -146,7 +150,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Flip the screen upside down by default as it makes more sense on T-BEAM
|
||||
// devices. Comment this out to not rotate screen 180 degrees.
|
||||
#define FLIP_SCREEN_VERTICALLY
|
||||
#define SCREEN_FLIP_VERTICALLY
|
||||
|
||||
// Define if screen should be mirrored left to right
|
||||
// #define SCREEN_MIRROR
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// GPS
|
||||
@@ -282,6 +289,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define GPS_RX_PIN 36
|
||||
#define GPS_TX_PIN 13 // per @eugene
|
||||
|
||||
#define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
|
||||
|
||||
#define I2C_SDA 21 // I2C pins for this board
|
||||
#define I2C_SCL 22
|
||||
|
||||
@@ -329,6 +338,35 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define LORA_DIO1 35 // Not really used
|
||||
#define LORA_DIO2 34 // Not really used
|
||||
|
||||
#elif defined(GENIEBLOCKS)
|
||||
// This string must exactly match the case used in release file names or the android updater won't work
|
||||
#define HW_VENDOR "genieblocks"
|
||||
#undef GPS_RX_PIN
|
||||
#undef GPS_TX_PIN
|
||||
#define GPS_RX_PIN 5
|
||||
#define GPS_TX_PIN 18
|
||||
#define GPS_RESET_N 10
|
||||
#define GPS_EXTINT 23 // On MAX-M8 module pin name is EXTINT. On L70 module pin name is STANDBY.
|
||||
|
||||
#define BATTERY_PIN 39 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
|
||||
#define BATTERY_EN_PIN 14 // Voltage voltage divider enable pin connected to mosfet
|
||||
|
||||
#define I2C_SDA 4 // I2C pins for this board
|
||||
#define I2C_SCL 2
|
||||
|
||||
#define LED_PIN 12 // If defined we will blink this LED
|
||||
//#define BUTTON_PIN 36 // If defined, this will be used for user button presses (ToDo problem on that line on debug screen --> Long press start!)
|
||||
//#define BUTTON_NEED_PULLUP //GPIOs 34 to 39 are GPIs – input only pins. These pins don’t have internal pull-ups or pull-down resistors.
|
||||
|
||||
#define USE_RF95
|
||||
#define LORA_DIO0 38 // a No connect on the SX1262 module
|
||||
#define LORA_RESET 9
|
||||
|
||||
#define RF95_SCK 22
|
||||
#define RF95_MISO 19
|
||||
#define RF95_MOSI 13
|
||||
#define RF95_NSS 21
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_NRF52840_PCA10056
|
||||
@@ -356,7 +394,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#define USE_RF95
|
||||
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
||||
#define LORA_RESET 23
|
||||
#define LORA_RESET RADIOLIB_NC
|
||||
#define LORA_DIO1 33 // Not really used
|
||||
#define LORA_DIO2 32 // Not really used
|
||||
|
||||
@@ -364,7 +402,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define RF95_SCK 5
|
||||
#define RF95_MISO 19
|
||||
#define RF95_MOSI 27
|
||||
#define RF95_NSS 18
|
||||
#define RF95_NSS RADIOLIB_NC // the ch341f spi controller does CS for us
|
||||
|
||||
#endif
|
||||
|
||||
@@ -400,8 +438,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// Always include the SEGGER code on NRF52 - because useful for debugging
|
||||
#include "SEGGER_RTT.h"
|
||||
|
||||
// The channel we send stdout data to
|
||||
#define SEGGER_STDOUT_CH 0
|
||||
|
||||
// Debug printing to segger console
|
||||
#define SEGGER_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__)
|
||||
#define SEGGER_MSG(...) SEGGER_RTT_printf(SEGGER_STDOUT_CH, __VA_ARGS__)
|
||||
|
||||
// If we are not on a NRF52840 (which has built in USB-ACM serial support) and we don't have serial pins hooked up, then we MUST
|
||||
// use SEGGER for debug output
|
||||
@@ -418,7 +459,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define DEBUG_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__)
|
||||
#else
|
||||
#ifdef DEBUG_PORT
|
||||
#define DEBUG_MSG(...) DEBUG_PORT.printf(__VA_ARGS__)
|
||||
#define DEBUG_MSG(...) DEBUG_PORT.logDebug(__VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_MSG(...)
|
||||
#endif
|
||||
@@ -430,3 +471,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#define GPS_POWER_CTRL_CH 3
|
||||
#define LORA_POWER_CTRL_CH 2
|
||||
|
||||
// Default Bluetooth PIN
|
||||
#define defaultBLEPin 123456
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
/// Error codes for critical error
|
||||
enum CriticalErrorCode { NoError, ErrTxWatchdog, ErrSleepEnterWait, ErrNoRadio, ErrUnspecified, UBloxInitFailed };
|
||||
#include "mesh/generated/mesh.pb.h" // For CriticalErrorCode
|
||||
|
||||
/// Record an error that should be reported via analytics
|
||||
void recordCriticalError(CriticalErrorCode code, uint32_t address = 0);
|
||||
void recordCriticalError(CriticalErrorCode code = CriticalErrorCode_Unspecified, uint32_t address = 0);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "RadioLibInterface.h"
|
||||
#include "configuration.h"
|
||||
#include "nimble/BluetoothUtil.h"
|
||||
#include "NodeDB.h"
|
||||
|
||||
#include <CRC32.h>
|
||||
#include <Update.h>
|
||||
@@ -16,6 +17,8 @@ static CRC32 crc;
|
||||
static uint32_t rebootAtMsec = 0; // If not zero we will reboot at this time (used to reboot shortly after the update completes)
|
||||
|
||||
static uint32_t updateExpectedSize, updateActualSize;
|
||||
static uint8_t update_result;
|
||||
static uint8_t update_region;
|
||||
|
||||
static concurrency::Lock *updateLock;
|
||||
|
||||
@@ -32,8 +35,8 @@ int update_size_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_
|
||||
crc.reset();
|
||||
if (Update.isRunning())
|
||||
Update.abort();
|
||||
bool canBegin = Update.begin(updateExpectedSize);
|
||||
DEBUG_MSG("Setting update size %u, result %d\n", updateExpectedSize, canBegin);
|
||||
bool canBegin = Update.begin(updateExpectedSize, update_region);
|
||||
DEBUG_MSG("Setting region %d update size %u, result %d\n", update_region, updateExpectedSize, canBegin);
|
||||
if (!canBegin) {
|
||||
// Indicate failure by forcing the size to 0 (client will read it back)
|
||||
updateExpectedSize = 0;
|
||||
@@ -72,13 +75,11 @@ int update_data_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_
|
||||
crc.update(data, len);
|
||||
Update.write(data, len);
|
||||
updateActualSize += len;
|
||||
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG); // Not exactly correct, but we want to force the device to not sleep now
|
||||
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t update_result;
|
||||
|
||||
/// Handle writes to crc32
|
||||
int update_crc32_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
|
||||
{
|
||||
@@ -100,8 +101,14 @@ int update_crc32_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble
|
||||
result = 0xe0; // FIXME, use real error codes
|
||||
} else {
|
||||
if (Update.end()) {
|
||||
DEBUG_MSG("OTA done, rebooting in 5 seconds!\n");
|
||||
rebootAtMsec = millis() + 5000;
|
||||
if (update_region == U_SPIFFS) {
|
||||
DEBUG_MSG("SPIFFS updated!\n");
|
||||
nodeDB.saveToDisk(); // Since we just wiped spiffs, we need to save our current state
|
||||
}
|
||||
else {
|
||||
DEBUG_MSG("Appload updated, rebooting in 5 seconds!\n");
|
||||
rebootAtMsec = millis() + 5000;
|
||||
}
|
||||
} else {
|
||||
DEBUG_MSG("Error Occurred. Error #: %d\n", Update.getError());
|
||||
}
|
||||
@@ -125,6 +132,11 @@ int update_result_callback(uint16_t conn_handle, uint16_t attr_handle, struct bl
|
||||
return chr_readwrite8(&update_result, sizeof(update_result), ctxt);
|
||||
}
|
||||
|
||||
int update_region_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
|
||||
{
|
||||
return chr_readwrite8(&update_region, sizeof(update_region), ctxt);
|
||||
}
|
||||
|
||||
void bluetoothRebootCheck()
|
||||
{
|
||||
if (rebootAtMsec && millis() > rebootAtMsec) {
|
||||
|
||||
@@ -14,10 +14,11 @@ int update_size_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_
|
||||
int update_data_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
|
||||
int update_result_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
|
||||
int update_crc32_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
|
||||
int update_region_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
|
||||
|
||||
extern const struct ble_gatt_svc_def gatt_update_svcs[];
|
||||
|
||||
extern const ble_uuid128_t update_result_uuid;
|
||||
extern const ble_uuid128_t update_result_uuid, update_region_uuid;
|
||||
|
||||
extern int16_t updateResultHandle;
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ const ble_uuid128_t update_crc32_uuid =
|
||||
const ble_uuid128_t update_result_uuid =
|
||||
BLE_UUID128_INIT(0x77, 0x2c, 0x43, 0x37, 0x09, 0x21, 0x4a, 0xac, 0x24, 0x44, 0x11, 0x74, 0x62, 0x48, 0x13, 0x5e);
|
||||
|
||||
// "5e134862-7411-4424-ac4a-210937432c67" write
|
||||
const ble_uuid128_t update_region_uuid =
|
||||
BLE_UUID128_INIT(0x67, 0x2c, 0x43, 0x37, 0x09, 0x21, 0x4a, 0xac, 0x24, 0x44, 0x11, 0x74, 0x62, 0x48, 0x13, 0x5e);
|
||||
|
||||
const struct ble_gatt_svc_def gatt_update_svcs[] = {
|
||||
{
|
||||
/*** Service: Security test. */
|
||||
@@ -47,9 +51,14 @@ const struct ble_gatt_svc_def gatt_update_svcs[] = {
|
||||
},
|
||||
{
|
||||
.uuid = &update_result_uuid.u,
|
||||
.access_cb = update_size_callback,
|
||||
.access_cb = update_result_callback,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_AUTHEN | BLE_GATT_CHR_F_NOTIFY,
|
||||
},
|
||||
{
|
||||
.uuid = &update_region_uuid.u,
|
||||
.access_cb = update_region_callback,
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_AUTHEN,
|
||||
},
|
||||
{
|
||||
0, /* No more characteristics in this service. */
|
||||
}},
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "utils.h"
|
||||
#include <nvs.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
void getMacAddr(uint8_t *dmac)
|
||||
{
|
||||
@@ -48,7 +49,8 @@ void esp32Setup()
|
||||
|
||||
// Since we are turning on watchdogs rather late in the release schedule, we really don't want to catch any
|
||||
// false positives. The wait-to-sleep timeout for shutting down radios is 30 secs, so pick 45 for now.
|
||||
#define APP_WATCHDOG_SECS 45
|
||||
// #define APP_WATCHDOG_SECS 45
|
||||
#define APP_WATCHDOG_SECS 90
|
||||
|
||||
res = esp_task_wdt_init(APP_WATCHDOG_SECS, true);
|
||||
assert(res == ESP_OK);
|
||||
@@ -86,4 +88,58 @@ void esp32Loop()
|
||||
|
||||
// for debug printing
|
||||
// radio.radioIf.canSleep();
|
||||
}
|
||||
|
||||
void cpuDeepSleep(uint64_t msecToWake)
|
||||
{
|
||||
/*
|
||||
Some ESP32 IOs have internal pullups or pulldowns, which are enabled by default.
|
||||
If an external circuit drives this pin in deep sleep mode, current consumption may
|
||||
increase due to current flowing through these pullups and pulldowns.
|
||||
|
||||
To isolate a pin, preventing extra current draw, call rtc_gpio_isolate() function.
|
||||
For example, on ESP32-WROVER module, GPIO12 is pulled up externally.
|
||||
GPIO12 also has an internal pulldown in the ESP32 chip. This means that in deep sleep,
|
||||
some current will flow through these external and internal resistors, increasing deep
|
||||
sleep current above the minimal possible value.
|
||||
|
||||
Note: we don't isolate pins that are used for the LORA, LED, i2c, spi or the wake button
|
||||
*/
|
||||
static const uint8_t rtcGpios[] = {/* 0, */ 2,
|
||||
/* 4, */
|
||||
#ifndef USE_JTAG
|
||||
13,
|
||||
/* 14, */ /* 15, */
|
||||
#endif
|
||||
/* 25, */ 26, /* 27, */
|
||||
32, 33, 34, 35,
|
||||
36, 37
|
||||
/* 38, 39 */};
|
||||
|
||||
for (int i = 0; i < sizeof(rtcGpios); i++)
|
||||
rtc_gpio_isolate((gpio_num_t)rtcGpios[i]);
|
||||
|
||||
// FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using
|
||||
// to detect wake and in normal operation the external part drives them hard.
|
||||
|
||||
// We want RTC peripherals to stay on
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
|
||||
#ifdef BUTTON_PIN
|
||||
// Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39.
|
||||
uint64_t gpioMask = (1ULL << BUTTON_PIN);
|
||||
|
||||
#ifdef BUTTON_NEED_PULLUP
|
||||
gpio_pullup_en((gpio_num_t)BUTTON_PIN);
|
||||
#endif
|
||||
|
||||
// Not needed because both of the current boards have external pullups
|
||||
// FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead of
|
||||
// just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN);
|
||||
|
||||
esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW);
|
||||
#endif
|
||||
|
||||
esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs
|
||||
esp_deep_sleep_start(); // TBD mA sleep current (battery)
|
||||
}
|
||||
@@ -65,9 +65,8 @@ void Air530GPS::sendCommand(const char *cmd) {
|
||||
}
|
||||
|
||||
void Air530GPS::sleep() {
|
||||
NMEAGPS::sleep();
|
||||
#ifdef PIN_GPS_WAKE
|
||||
digitalWrite(PIN_GPS_WAKE, 0);
|
||||
pinMode(PIN_GPS_WAKE, OUTPUT);
|
||||
sendCommand("$PGKC105,4");
|
||||
#endif
|
||||
}
|
||||
@@ -76,10 +75,7 @@ void Air530GPS::sleep() {
|
||||
void Air530GPS::wake()
|
||||
{
|
||||
#if 1
|
||||
#ifdef PIN_GPS_WAKE
|
||||
digitalWrite(PIN_GPS_WAKE, 1);
|
||||
pinMode(PIN_GPS_WAKE, OUTPUT);
|
||||
#endif
|
||||
NMEAGPS::wake();
|
||||
#else
|
||||
// For power testing - keep GPS sleeping forever
|
||||
sleep();
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#ifdef GPS_RX_PIN
|
||||
HardwareSerial _serial_gps_real(GPS_SERIAL_NUM);
|
||||
HardwareSerial *GPS::_serial_gps = &_serial_gps_real;
|
||||
#elif defined(NRF52840_XXAA)
|
||||
#elif defined(NRF52840_XXAA) || defined(NRF52833_XXAA)
|
||||
// Assume NRF52840
|
||||
HardwareSerial *GPS::_serial_gps = &Serial1;
|
||||
#else
|
||||
@@ -25,17 +25,75 @@ uint8_t GPS::i2cAddress = 0;
|
||||
|
||||
GPS *gps;
|
||||
|
||||
/// Multiple GPS instances might use the same serial port (in sequence), but we can
|
||||
/// only init that port once.
|
||||
static bool didSerialInit;
|
||||
|
||||
bool GPS::setupGPS()
|
||||
{
|
||||
if (_serial_gps && !didSerialInit) {
|
||||
didSerialInit = true;
|
||||
|
||||
#ifdef GPS_RX_PIN
|
||||
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
|
||||
#else
|
||||
_serial_gps->begin(GPS_BAUDRATE);
|
||||
#endif
|
||||
#ifndef NO_ESP32
|
||||
_serial_gps->setRxBufferSize(2048); // the default is 256
|
||||
#endif
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GPS::setup()
|
||||
{
|
||||
// Master power for the GPS
|
||||
#ifdef PIN_GPS_EN
|
||||
digitalWrite(PIN_GPS_EN, PIN_GPS_EN);
|
||||
pinMode(PIN_GPS_EN, OUTPUT);
|
||||
#endif
|
||||
|
||||
#ifdef PIN_GPS_RESET
|
||||
digitalWrite(PIN_GPS_RESET, 1); // assert for 10ms
|
||||
pinMode(PIN_GPS_RESET, OUTPUT);
|
||||
delay(10);
|
||||
digitalWrite(PIN_GPS_RESET, 0);
|
||||
#endif
|
||||
|
||||
setAwake(true); // Wake GPS power before doing any init
|
||||
bool ok = setupGPS();
|
||||
|
||||
if (ok)
|
||||
if (ok) {
|
||||
notifySleepObserver.observe(¬ifySleep);
|
||||
notifyDeepSleepObserver.observe(¬ifyDeepSleep);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Allow defining the polarity of the WAKE output. default is active high
|
||||
#ifndef GPS_WAKE_ACTIVE
|
||||
#define GPS_WAKE_ACTIVE 1
|
||||
#endif
|
||||
|
||||
void GPS::wake()
|
||||
{
|
||||
#ifdef PIN_GPS_WAKE
|
||||
digitalWrite(PIN_GPS_WAKE, GPS_WAKE_ACTIVE);
|
||||
pinMode(PIN_GPS_WAKE, OUTPUT);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void GPS::sleep() {
|
||||
#ifdef PIN_GPS_WAKE
|
||||
digitalWrite(PIN_GPS_WAKE, GPS_WAKE_ACTIVE ? 0 : 1);
|
||||
pinMode(PIN_GPS_WAKE, OUTPUT);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Record that we have a GPS
|
||||
void GPS::setConnected()
|
||||
{
|
||||
@@ -100,7 +158,7 @@ uint32_t GPS::getWakeTime() const
|
||||
return t; // already maxint
|
||||
|
||||
if (t == 0)
|
||||
t = 15 * 60; // Allow up to 5 mins for each attempt (probably will be much less if we can find sats)
|
||||
t = radioConfig.preferences.is_router ? 5 * 60 : 15 * 60; // Allow up to 15 mins for each attempt (probably will be much less if we can find sats) or less if a router
|
||||
|
||||
t *= 1000; // msecs
|
||||
|
||||
@@ -121,8 +179,8 @@ uint32_t GPS::getSleepTime() const
|
||||
if (t == UINT32_MAX)
|
||||
return t; // already maxint
|
||||
|
||||
if (t == 0)
|
||||
t = 2 * 60; // 2 mins
|
||||
if (t == 0) // default - unset in preferences
|
||||
t = radioConfig.preferences.is_router ? 24 * 60 * 60 : 2 * 60; // 2 mins or once per day for routers
|
||||
|
||||
t *= 1000;
|
||||
|
||||
@@ -181,6 +239,7 @@ int32_t GPS::runOnce()
|
||||
}
|
||||
|
||||
// We've been awake too long - force sleep
|
||||
now = millis();
|
||||
auto wakeTime = getWakeTime();
|
||||
bool tooLong = wakeTime != UINT32_MAX && (now - lastWakeStartMsec) > wakeTime;
|
||||
|
||||
@@ -225,7 +284,19 @@ void GPS::forceWake(bool on)
|
||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||
int GPS::prepareSleep(void *unused)
|
||||
{
|
||||
DEBUG_MSG("GPS prepare sleep!\n");
|
||||
forceWake(false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||
int GPS::prepareDeepSleep(void *unused)
|
||||
{
|
||||
DEBUG_MSG("GPS deep sleep!\n");
|
||||
|
||||
// For deep sleep we also want abandon any lock attempts (because we want minimum power)
|
||||
setAwake(false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ class GPS : private concurrency::OSThread
|
||||
uint8_t numSatellites = 0;
|
||||
|
||||
CallbackObserver<GPS, void *> notifySleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareSleep);
|
||||
CallbackObserver<GPS, void *> notifyDeepSleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareDeepSleep);
|
||||
|
||||
public:
|
||||
/** If !NULL we will use this serial port to construct our GPS */
|
||||
@@ -72,13 +73,13 @@ class GPS : private concurrency::OSThread
|
||||
|
||||
protected:
|
||||
/// Do gps chipset specific init, return true for success
|
||||
virtual bool setupGPS() = 0;
|
||||
virtual bool setupGPS();
|
||||
|
||||
/// If possible force the GPS into sleep/low power mode
|
||||
virtual void sleep() {}
|
||||
virtual void sleep();
|
||||
|
||||
/// wake the GPS into normal operation mode
|
||||
virtual void wake() {}
|
||||
virtual void wake();
|
||||
|
||||
/** Subclasses should look for serial rx characters here and feed it to their GPS parser
|
||||
*
|
||||
@@ -115,6 +116,10 @@ class GPS : private concurrency::OSThread
|
||||
/// always returns 0 to indicate okay to sleep
|
||||
int prepareSleep(void *unused);
|
||||
|
||||
/// Prepare the GPS for the cpu entering deep sleep, expect to be gone for at least 100s of msecs
|
||||
/// always returns 0 to indicate okay to sleep
|
||||
int prepareDeepSleep(void *unused);
|
||||
|
||||
/**
|
||||
* Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode
|
||||
*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "NMEAGPS.h"
|
||||
#include "configuration.h"
|
||||
#include "RTC.h"
|
||||
#include "configuration.h"
|
||||
|
||||
static int32_t toDegInt(RawDegrees d)
|
||||
{
|
||||
@@ -13,6 +13,8 @@ static int32_t toDegInt(RawDegrees d)
|
||||
|
||||
bool NMEAGPS::setupGPS()
|
||||
{
|
||||
GPS::setupGPS();
|
||||
|
||||
#ifdef PIN_GPS_PPS
|
||||
// pulse per second
|
||||
// FIXME - move into shared GPS code
|
||||
@@ -32,7 +34,7 @@ bool NMEAGPS::lookForTime()
|
||||
{
|
||||
auto ti = reader.time;
|
||||
auto d = reader.date;
|
||||
if (ti.isUpdated() && ti.isValid() && d.isValid()) {
|
||||
if (ti.isValid() && d.isValid()) { // Note: we don't check for updated, because we'll only be called if needed
|
||||
/* Convert to unix time
|
||||
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
|
||||
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
||||
@@ -65,27 +67,27 @@ bool NMEAGPS::lookForLocation()
|
||||
// uint8_t fixtype = reader.fixQuality();
|
||||
// hasValidLocation = ((fixtype >= 1) && (fixtype <= 5));
|
||||
|
||||
if (reader.satellites.isUpdated()) {
|
||||
setNumSatellites(reader.satellites.value());
|
||||
}
|
||||
|
||||
// Diminution of precision (an accuracy metric) is reported in 10^2 units, so we need to scale down when we use it
|
||||
if (reader.hdop.isUpdated()) {
|
||||
dop = reader.hdop.value();
|
||||
}
|
||||
if (reader.course.isUpdated()) {
|
||||
heading = reader.course.value() * 1e3; // Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5
|
||||
}
|
||||
|
||||
if (reader.altitude.isUpdated())
|
||||
altitude = reader.altitude.meters();
|
||||
|
||||
if (reader.location.isUpdated()) {
|
||||
if (reader.altitude.isValid())
|
||||
altitude = reader.altitude.meters();
|
||||
|
||||
if (reader.location.isValid()) {
|
||||
auto loc = reader.location.value();
|
||||
latitude = toDegInt(loc.lat);
|
||||
longitude = toDegInt(loc.lng);
|
||||
foundLocation = true;
|
||||
}
|
||||
|
||||
// Diminution of precision (an accuracy metric) is reported in 10^2 units, so we need to scale down when we use it
|
||||
if (reader.hdop.isValid()) {
|
||||
dop = reader.hdop.value();
|
||||
}
|
||||
if (reader.course.isValid()) {
|
||||
heading = reader.course.value() * 1e3; // Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5
|
||||
}
|
||||
if (reader.satellites.isValid()) {
|
||||
setNumSatellites(reader.satellites.value());
|
||||
}
|
||||
auto loc = reader.location.value();
|
||||
latitude = toDegInt(loc.lat);
|
||||
longitude = toDegInt(loc.lng);
|
||||
foundLocation = true;
|
||||
|
||||
// expect gps pos lat=37.520825, lon=-122.309162, alt=158
|
||||
DEBUG_MSG("new NMEA GPS pos lat=%f, lon=%f, alt=%d, hdop=%g, heading=%f\n", latitude * 1e-7, longitude * 1e-7, altitude,
|
||||
|
||||
@@ -31,9 +31,24 @@ void readFromRTC()
|
||||
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
|
||||
{
|
||||
static uint32_t lastSetMsec = 0;
|
||||
uint32_t now = millis();
|
||||
|
||||
bool shouldSet;
|
||||
if (q > currentQuality) {
|
||||
currentQuality = q;
|
||||
DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec);
|
||||
shouldSet = true;
|
||||
DEBUG_MSG("Upgrading time to RTC %ld secs (quality %d)\n", tv->tv_sec, q);
|
||||
} else if(q == RTCQualityGPS && (now - lastSetMsec) > (12 * 60 * 60 * 1000L)) {
|
||||
// Every 12 hrs we will slam in a new GPS time, to correct for local RTC clock drift
|
||||
shouldSet = true;
|
||||
DEBUG_MSG("Reapplying GPS time to correct clock drift %ld secs\n", tv->tv_sec);
|
||||
}
|
||||
else
|
||||
shouldSet = false;
|
||||
|
||||
if (shouldSet) {
|
||||
lastSetMsec = now;
|
||||
#ifndef NO_ESP32
|
||||
settimeofday(tv, NULL);
|
||||
#else
|
||||
|
||||
@@ -29,16 +29,7 @@ bool UBloxGPS::tryConnect()
|
||||
|
||||
bool UBloxGPS::setupGPS()
|
||||
{
|
||||
if (_serial_gps) {
|
||||
#ifdef GPS_RX_PIN
|
||||
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
|
||||
#else
|
||||
_serial_gps->begin(GPS_BAUDRATE);
|
||||
#endif
|
||||
#ifndef NO_ESP32
|
||||
_serial_gps->setRxBufferSize(2048); // the default is 256
|
||||
#endif
|
||||
}
|
||||
GPS::setupGPS();
|
||||
|
||||
// uncomment to see debug info
|
||||
// ublox.enableDebugging(Serial);
|
||||
@@ -52,7 +43,7 @@ bool UBloxGPS::setupGPS()
|
||||
DEBUG_MSG("Connected to UBLOX GPS successfully\n");
|
||||
|
||||
if (!setUBXMode())
|
||||
recordCriticalError(UBloxInitFailed); // Don't halt the boot if saving the config fails, but do report the bug
|
||||
recordCriticalError(CriticalErrorCode_UBloxInitFailed); // Don't halt the boot if saving the config fails, but do report the bug
|
||||
|
||||
return true;
|
||||
} else {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "EInkDisplay.h"
|
||||
#include "SPILock.h"
|
||||
#include "epd1in54.h" // Screen specific library
|
||||
#include "graphics/configs.h"
|
||||
#include <SPI.h>
|
||||
#include <TFT_eSPI.h> // Graphics library and Sprite class
|
||||
|
||||
@@ -46,15 +45,18 @@ void updateDisplay(uint8_t *blackFrame = framePtr)
|
||||
|
||||
EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl)
|
||||
{
|
||||
setGeometry(GEOMETRY_128_64); // FIXME - currently we lie and claim 128x64 because I'm not yet sure other resolutions will
|
||||
// work ie GEOMETRY_RAWMODE
|
||||
setGeometry(GEOMETRY_RAWMODE, EPD_WIDTH, EPD_HEIGHT);
|
||||
// setGeometry(GEOMETRY_RAWMODE, 128, 64); // old resolution
|
||||
// setGeometry(GEOMETRY_128_64); // We originally used this because I wasn't sure if rawmode worked - it does
|
||||
}
|
||||
|
||||
// FIXME quick hack to limit drawing to a very slow rate
|
||||
uint32_t lastDrawMsec;
|
||||
|
||||
// Write the buffer to the display memory
|
||||
void EInkDisplay::display(void)
|
||||
/**
|
||||
* Force a display update if we haven't drawn within the specified msecLimit
|
||||
*/
|
||||
bool EInkDisplay::forceDisplay(uint32_t msecLimit)
|
||||
{
|
||||
// No need to grab this lock because we are on our own SPI bus
|
||||
// concurrency::LockGuard g(spiLock);
|
||||
@@ -62,16 +64,16 @@ void EInkDisplay::display(void)
|
||||
uint32_t now = millis();
|
||||
uint32_t sinceLast = now - lastDrawMsec;
|
||||
|
||||
if (framePtr && (sinceLast > 60 * 1000 || lastDrawMsec == 0)) {
|
||||
if (framePtr && (sinceLast > msecLimit || lastDrawMsec == 0)) {
|
||||
lastDrawMsec = now;
|
||||
|
||||
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
|
||||
// tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK);
|
||||
for (uint8_t y = 0; y < SCREEN_HEIGHT; y++) {
|
||||
for (uint8_t x = 0; x < SCREEN_WIDTH; x++) {
|
||||
for (uint8_t y = 0; y < displayHeight; y++) {
|
||||
for (uint8_t x = 0; x < displayWidth; x++) {
|
||||
|
||||
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent
|
||||
auto b = buffer[x + (y / 8) * SCREEN_WIDTH];
|
||||
auto b = buffer[x + (y / 8) * displayWidth];
|
||||
auto isset = b & (1 << (y & 7));
|
||||
frame.drawPixel(x, y, isset ? INK : PAPER);
|
||||
}
|
||||
@@ -83,11 +85,25 @@ void EInkDisplay::display(void)
|
||||
updateDisplay(); // Send image to display and refresh
|
||||
DEBUG_MSG("done\n");
|
||||
|
||||
// Put screen to sleep to save power
|
||||
// Put screen to sleep to save power
|
||||
ePaper.Sleep();
|
||||
return true;
|
||||
} else {
|
||||
// DEBUG_MSG("Skipping eink display\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Write the buffer to the display memory
|
||||
void EInkDisplay::display(void)
|
||||
{
|
||||
// We don't allow regular 'dumb' display() calls to draw on eink until we've shown
|
||||
// at least one forceDisplay() keyframe. This prevents flashing when we should the critical
|
||||
// bootscreen (that we want to look nice)
|
||||
if (lastDrawMsec)
|
||||
forceDisplay(slowUpdateMsec); // Show the first screen a few seconds after boot, then slower
|
||||
}
|
||||
|
||||
// Send a command to the display (low level function)
|
||||
void EInkDisplay::sendCommand(uint8_t com)
|
||||
{
|
||||
|
||||
@@ -14,15 +14,26 @@
|
||||
*/
|
||||
class EInkDisplay : public OLEDDisplay
|
||||
{
|
||||
/// How often should we update the display
|
||||
/// thereafter we do once per 5 minutes
|
||||
uint32_t slowUpdateMsec = 5 * 60 * 1000;
|
||||
|
||||
public:
|
||||
/* constructor
|
||||
FIXME - the parameters are not used, just a temporary hack to keep working like the old displays
|
||||
*/
|
||||
EInkDisplay(uint8_t address, int sda, int scl);
|
||||
|
||||
// Write the buffer to the display memory
|
||||
// Write the buffer to the display memory (for eink we only do this occasionally)
|
||||
virtual void display(void);
|
||||
|
||||
/**
|
||||
* Force a display update if we haven't drawn within the specified msecLimit
|
||||
*
|
||||
* @return true if we did draw the screen
|
||||
*/
|
||||
bool forceDisplay(uint32_t msecLimit = 1000);
|
||||
|
||||
protected:
|
||||
// the header size of the buffer used, e.g. for the SPI command header
|
||||
virtual int getBufferOffset(void) { return 0; }
|
||||
@@ -33,3 +44,5 @@ class EInkDisplay : public OLEDDisplay
|
||||
// Connect to the display
|
||||
virtual bool connect();
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -26,20 +26,33 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "Screen.h"
|
||||
#include "configs.h"
|
||||
#include "configuration.h"
|
||||
#include "fonts.h"
|
||||
#include "graphics/images.h"
|
||||
#include "main.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "meshwifi/meshwifi.h"
|
||||
#include "plugins/TextMessagePlugin.h"
|
||||
#include "target_specific.h"
|
||||
#include "utils.h"
|
||||
|
||||
#ifndef NO_ESP32
|
||||
#include "mesh/http/WiFiAPClient.h"
|
||||
#endif
|
||||
|
||||
using namespace meshtastic; /** @todo remove */
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
|
||||
// This means the *visible* area (sh1106 can address 132, but shows 128 for example)
|
||||
#define IDLE_FRAMERATE 1 // in fps
|
||||
#define COMPASS_DIAM 44
|
||||
|
||||
// DEBUG
|
||||
#define NUM_EXTRA_FRAMES 3 // text message and debug frame
|
||||
// if defined a pixel will blink to show redraws
|
||||
// #define SHOW_REDRAWS
|
||||
|
||||
// A text message frame + debug frame + all the node infos
|
||||
static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
|
||||
static uint32_t targetFramerate = IDLE_FRAMERATE;
|
||||
@@ -58,39 +71,92 @@ static char ourId[5];
|
||||
static bool heartbeat = false;
|
||||
#endif
|
||||
|
||||
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
static uint16_t displayWidth, displayHeight;
|
||||
|
||||
#define SCREEN_WIDTH displayWidth
|
||||
#define SCREEN_HEIGHT displayHeight
|
||||
|
||||
#ifdef HAS_EINK
|
||||
// The screen is bigger so use bigger fonts
|
||||
#define FONT_SMALL ArialMT_Plain_16
|
||||
#define FONT_MEDIUM ArialMT_Plain_24
|
||||
#define FONT_LARGE ArialMT_Plain_24
|
||||
#else
|
||||
#define FONT_SMALL ArialMT_Plain_10
|
||||
#define FONT_MEDIUM ArialMT_Plain_16
|
||||
#define FONT_LARGE ArialMT_Plain_24
|
||||
#endif
|
||||
|
||||
#define fontHeight(font) ((font)[1] + 1) // height is position 1
|
||||
|
||||
#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
|
||||
#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
|
||||
|
||||
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
|
||||
|
||||
#ifndef SCREEN_TRANSITION_MSECS
|
||||
#define SCREEN_TRANSITION_MSECS 300
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Draw the icon with extra info printed around the corners
|
||||
*/
|
||||
static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
// draw an xbm image.
|
||||
// Please note that everything that should be transitioned
|
||||
// needs to be drawn relative to x and y
|
||||
display->drawXbm(x + 32, y, icon_width, icon_height, (const uint8_t *)icon_bits);
|
||||
|
||||
display->setFont(ArialMT_Plain_16);
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT_16, "meshtastic.org");
|
||||
display->setFont(ArialMT_Plain_10);
|
||||
const char *region = xstr(HW_VERSION);
|
||||
if (*region && region[3] == '-') // Skip past 1.0- in the 1.0-EU865 string
|
||||
region += 4;
|
||||
// draw centered icon left to right and centered above the one line of app text
|
||||
display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2,
|
||||
icon_width, icon_height, (const uint8_t *)icon_bits);
|
||||
|
||||
display->setFont(FONT_MEDIUM);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
const char *title = "meshtastic.org";
|
||||
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
// Draw region in upper left
|
||||
if (upperMsg)
|
||||
display->drawString(x + 0, y + 0, upperMsg);
|
||||
|
||||
// Draw version in upper right
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf), "%s",
|
||||
xstr(APP_VERSION)); // Note: we don't bother printing region or now, it makes the string too long
|
||||
display->drawString(SCREEN_WIDTH - 20, 0, buf);
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf);
|
||||
screen->forceDisplay();
|
||||
|
||||
// FIXME - draw serial # somewhere?
|
||||
}
|
||||
|
||||
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
// Draw region in upper left
|
||||
const char *region = myRegion ? myRegion->name : NULL;
|
||||
drawIconScreen(region, display, state, x, y);
|
||||
}
|
||||
|
||||
/// Used on eink displays while in deep sleep
|
||||
static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
drawIconScreen("Sleeping...", display, state, x, y);
|
||||
}
|
||||
|
||||
static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->setFont(ArialMT_Plain_16);
|
||||
display->setFont(FONT_MEDIUM);
|
||||
display->drawString(64 + x, y, "Bluetooth");
|
||||
|
||||
display->setFont(ArialMT_Plain_10);
|
||||
display->drawString(64 + x, FONT_HEIGHT + y + 2, "Enter this code");
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Enter this code");
|
||||
|
||||
display->setFont(ArialMT_Plain_24);
|
||||
display->setFont(FONT_LARGE);
|
||||
display->drawString(64 + x, 26 + y, btPIN);
|
||||
|
||||
display->setFont(ArialMT_Plain_10);
|
||||
display->setFont(FONT_SMALL);
|
||||
char buf[30];
|
||||
const char *name = "Name: ";
|
||||
strcpy(buf, name);
|
||||
@@ -98,6 +164,22 @@ static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state,
|
||||
display->drawString(64 + x, 48 + y, buf);
|
||||
}
|
||||
|
||||
/// Draw the last text message we received
|
||||
static void drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
displayedNodeNum = 0; // Not currently showing a node pane
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_MEDIUM);
|
||||
|
||||
char tempBuf[24];
|
||||
snprintf(tempBuf, sizeof(tempBuf), "Critical fault #%d", myNodeInfo.error_code);
|
||||
display->drawString(0 + x, 0 + y, tempBuf);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawString(0 + x, FONT_HEIGHT_MEDIUM + y, "For help, please post on\nmeshtastic.discourse.group");
|
||||
}
|
||||
|
||||
/// Draw the last text message we received
|
||||
static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
@@ -112,10 +194,10 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
|
||||
// with the third parameter you can define the width after which words will
|
||||
// be wrapped. Currently only spaces and "-" are allowed for wrapping
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(ArialMT_Plain_16);
|
||||
display->setFont(FONT_MEDIUM);
|
||||
String sender = (node && node->has_user) ? node->user.short_name : "???";
|
||||
display->drawString(0 + x, 0 + y, sender);
|
||||
display->setFont(ArialMT_Plain_10);
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
// the max length of this buffer is much longer than we can possibly print
|
||||
static char tempBuf[96];
|
||||
@@ -135,8 +217,8 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char *
|
||||
int xo = x, yo = y;
|
||||
while (*f) {
|
||||
display->drawString(xo, yo, *f);
|
||||
yo += FONT_HEIGHT;
|
||||
if (yo > SCREEN_HEIGHT - FONT_HEIGHT) {
|
||||
yo += FONT_HEIGHT_SMALL;
|
||||
if (yo > SCREEN_HEIGHT - FONT_HEIGHT_SMALL) {
|
||||
xo += SCREEN_WIDTH / 2;
|
||||
yo = 0;
|
||||
}
|
||||
@@ -162,14 +244,14 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char *
|
||||
// Wrap to next row, if needed.
|
||||
if (++col >= COLUMNS) {
|
||||
xo = x;
|
||||
yo += FONT_HEIGHT;
|
||||
yo += FONT_HEIGHT_SMALL;
|
||||
col = 0;
|
||||
}
|
||||
f++;
|
||||
}
|
||||
if (col != 0) {
|
||||
// Include last incomplete line in our total.
|
||||
yo += FONT_HEIGHT;
|
||||
yo += FONT_HEIGHT_SMALL;
|
||||
}
|
||||
|
||||
return yo;
|
||||
@@ -236,7 +318,7 @@ static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus
|
||||
display->drawFastImage(x + 24, y, 8, 8, imgSatellite);
|
||||
|
||||
// Draw the number of satellites
|
||||
sprintf(satsString, "%d", gps->getNumSatellites());
|
||||
sprintf(satsString, "%u", gps->getNumSatellites());
|
||||
display->drawString(x + 34, y - 2, satsString);
|
||||
}
|
||||
}
|
||||
@@ -476,7 +558,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
|
||||
NodeInfo *node = nodeDB.getNodeByIndex(nodeIndex);
|
||||
|
||||
display->setFont(ArialMT_Plain_10);
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
// The coordinates define the left starting point of the text
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
@@ -531,7 +613,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
// direction to node is unknown so display question mark
|
||||
// Debug info for gps lock errors
|
||||
// DEBUG_MSG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasPosition(ourNode), hasPosition(node));
|
||||
display->drawString(compassX - FONT_HEIGHT / 4, compassY - FONT_HEIGHT / 2, "?");
|
||||
display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?");
|
||||
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
||||
|
||||
// Must be after distStr is populated
|
||||
@@ -561,10 +643,26 @@ void _screen_header()
|
||||
}
|
||||
#endif
|
||||
|
||||
Screen::Screen(uint8_t address, int sda, int scl) : OSThread("Screen"), cmdQueue(32), dispdev(address, sda, scl), ui(&dispdev) {
|
||||
Screen::Screen(uint8_t address, int sda, int scl) : OSThread("Screen"), cmdQueue(32), dispdev(address, sda, scl), ui(&dispdev)
|
||||
{
|
||||
cmdQueue.setReader(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the display for the unit going to the lowest power mode possible. Most screens will just
|
||||
* poweroff, but eink screens will show a "I'm sleeping" graphic, possibly with a QR code
|
||||
*/
|
||||
void Screen::doDeepSleep()
|
||||
{
|
||||
#ifdef HAS_EINK
|
||||
static FrameCallback sleepFrames[] = {drawSleepScreen};
|
||||
static const int sleepFrameCount = sizeof(sleepFrames) / sizeof(sleepFrames[0]);
|
||||
ui.setFrames(sleepFrames, sleepFrameCount);
|
||||
ui.update();
|
||||
#endif
|
||||
setOn(false);
|
||||
}
|
||||
|
||||
void Screen::handleSetOn(bool on)
|
||||
{
|
||||
if (!useDisplay)
|
||||
@@ -592,11 +690,17 @@ void Screen::setup()
|
||||
// is never found when probing i2c and therefore we don't call setup and never want to do (invalid) accesses to this device.
|
||||
useDisplay = true;
|
||||
|
||||
dispdev.resetOrientation();
|
||||
// I think this is not needed - redundant with ui.init
|
||||
// dispdev.resetOrientation();
|
||||
|
||||
// Initialising the UI will init the display too.
|
||||
ui.init();
|
||||
ui.setTimePerTransition(300); // msecs
|
||||
|
||||
displayWidth = dispdev.width();
|
||||
displayHeight = dispdev.height();
|
||||
|
||||
ui.setTimePerTransition(SCREEN_TRANSITION_MSECS);
|
||||
|
||||
ui.setIndicatorPosition(BOTTOM);
|
||||
// Defines where the first frame is located in the bar.
|
||||
ui.setIndicatorDirection(LEFT_RIGHT);
|
||||
@@ -622,7 +726,9 @@ void Screen::setup()
|
||||
// Set up a log buffer with 3 lines, 32 chars each.
|
||||
dispdev.setLogBuffer(3, 32);
|
||||
|
||||
#ifdef FLIP_SCREEN_VERTICALLY
|
||||
#ifdef SCREEN_MIRROR
|
||||
dispdev.mirrorScreen();
|
||||
#elif defined(SCREEN_FLIP_VERTICALLY)
|
||||
dispdev.flipScreenVertically();
|
||||
#endif
|
||||
|
||||
@@ -643,6 +749,15 @@ void Screen::setup()
|
||||
powerStatusObserver.observe(&powerStatus->onNewStatus);
|
||||
gpsStatusObserver.observe(&gpsStatus->onNewStatus);
|
||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||
textMessageObserver.observe(textMessagePlugin);
|
||||
}
|
||||
|
||||
void Screen::forceDisplay()
|
||||
{
|
||||
// Nasty hack to force epaper updates for 'key' frames. FIXME, cleanup.
|
||||
#ifdef HAS_EINK
|
||||
dispdev.forceDisplay();
|
||||
#endif
|
||||
}
|
||||
|
||||
int32_t Screen::runOnce()
|
||||
@@ -655,7 +770,8 @@ int32_t Screen::runOnce()
|
||||
|
||||
// Show boot screen for first 3 seconds, then switch to normal operation.
|
||||
static bool showingBootScreen = true;
|
||||
if (showingBootScreen && (millis() > 3000)) {
|
||||
if (showingBootScreen && (millis() > 5000)) {
|
||||
DEBUG_MSG("Done with boot screen...\n");
|
||||
stopBootScreen();
|
||||
showingBootScreen = false;
|
||||
}
|
||||
@@ -701,6 +817,10 @@ int32_t Screen::runOnce()
|
||||
return 0;
|
||||
}
|
||||
|
||||
// this must be before the frameState == FIXED check, because we always
|
||||
// want to draw at least one FIXED frame before doing forceDisplay
|
||||
ui.update();
|
||||
|
||||
// Switch to a low framerate (to save CPU) when we are not in transition
|
||||
// but we should only call setTargetFPS when framestate changes, because
|
||||
// otherwise that breaks animations.
|
||||
@@ -709,6 +829,7 @@ int32_t Screen::runOnce()
|
||||
DEBUG_MSG("Setting idle framerate\n");
|
||||
targetFramerate = IDLE_FRAMERATE;
|
||||
ui.setTargetFPS(targetFramerate);
|
||||
forceDisplay();
|
||||
}
|
||||
|
||||
// While showing the bootscreen or Bluetooth pair screen all of our
|
||||
@@ -717,8 +838,6 @@ int32_t Screen::runOnce()
|
||||
// standard screen loop handling here
|
||||
}
|
||||
|
||||
ui.update();
|
||||
|
||||
// DEBUG_MSG("want fps %d, fixed=%d\n", targetFramerate,
|
||||
// ui.getUiState()->frameState); If we are scrolling we need to be called
|
||||
// soon, otherwise just 1 fps (to save CPU) We also ask to be called twice
|
||||
@@ -758,7 +877,11 @@ void Screen::setFrames()
|
||||
|
||||
size_t numframes = 0;
|
||||
|
||||
// If we have a text message - show it first
|
||||
// If we have a critical fault, show it first
|
||||
if (myNodeInfo.error_code)
|
||||
normalFrames[numframes++] = drawCriticalFaultFrame;
|
||||
|
||||
// If we have a text message - show it next
|
||||
if (devicestate.has_rx_text_message)
|
||||
normalFrames[numframes++] = drawTextMessageFrame;
|
||||
|
||||
@@ -775,16 +898,20 @@ void Screen::setFrames()
|
||||
// call a method on debugInfoScreen object (for more details)
|
||||
normalFrames[numframes++] = &Screen::drawDebugInfoSettingsTrampoline;
|
||||
|
||||
#ifndef NO_ESP32
|
||||
if (isWifiAvailable()) {
|
||||
// call a method on debugInfoScreen object (for more details)
|
||||
normalFrames[numframes++] = &Screen::drawDebugInfoWiFiTrampoline;
|
||||
}
|
||||
#endif
|
||||
|
||||
ui.setFrames(normalFrames, numframes);
|
||||
ui.enableAllIndicators();
|
||||
|
||||
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
|
||||
// just changed)
|
||||
|
||||
setFastFramerate(); // Draw ASAP
|
||||
}
|
||||
|
||||
void Screen::handleStartBluetoothPinScreen(uint32_t pin)
|
||||
@@ -794,16 +921,36 @@ void Screen::handleStartBluetoothPinScreen(uint32_t pin)
|
||||
|
||||
static FrameCallback btFrames[] = {drawFrameBluetooth};
|
||||
|
||||
snprintf(btPIN, sizeof(btPIN), "%06u", pin);
|
||||
snprintf(btPIN, sizeof(btPIN), "%06lu", pin);
|
||||
|
||||
ui.disableAllIndicators();
|
||||
ui.setFrames(btFrames, 1);
|
||||
setFastFramerate();
|
||||
}
|
||||
|
||||
void Screen::blink()
|
||||
{
|
||||
setFastFramerate();
|
||||
uint8_t count = 10;
|
||||
dispdev.setBrightness(254);
|
||||
while (count > 0) {
|
||||
dispdev.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
dispdev.display();
|
||||
delay(50);
|
||||
dispdev.clear();
|
||||
dispdev.display();
|
||||
delay(50);
|
||||
count = count - 1;
|
||||
}
|
||||
dispdev.setBrightness(brightness);
|
||||
}
|
||||
|
||||
void Screen::handlePrint(const char *text)
|
||||
{
|
||||
DEBUG_MSG("Screen: %s", text);
|
||||
if (!useDisplay)
|
||||
// the string passed into us probably has a newline, but that would confuse the logging system
|
||||
// so strip it
|
||||
DEBUG_MSG("Screen: %.*s\n", strlen(text) - 1, text);
|
||||
if (!useDisplay || !showingNormalScreen)
|
||||
return;
|
||||
|
||||
dispdev.print(text);
|
||||
@@ -814,22 +961,31 @@ void Screen::handleOnPress()
|
||||
// If screen was off, just wake it, otherwise advance to next frame
|
||||
// If we are in a transition, the press must have bounced, drop it.
|
||||
if (ui.getUiState()->frameState == FIXED) {
|
||||
setInterval(0); // redraw ASAP
|
||||
ui.nextFrame();
|
||||
|
||||
DEBUG_MSG("Setting fast framerate\n");
|
||||
|
||||
// We are about to start a transition so speed up fps
|
||||
targetFramerate = TRANSITION_FRAMERATE;
|
||||
ui.setTargetFPS(targetFramerate);
|
||||
setFastFramerate();
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef SCREEN_TRANSITION_FRAMERATE
|
||||
#define SCREEN_TRANSITION_FRAMERATE 30 // fps
|
||||
#endif
|
||||
|
||||
void Screen::setFastFramerate()
|
||||
{
|
||||
DEBUG_MSG("Setting fast framerate\n");
|
||||
|
||||
// We are about to start a transition so speed up fps
|
||||
targetFramerate = SCREEN_TRANSITION_FRAMERATE;
|
||||
ui.setTargetFPS(targetFramerate);
|
||||
setInterval(0); // redraw ASAP
|
||||
}
|
||||
|
||||
void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
displayedNodeNum = 0; // Not currently showing a node pane
|
||||
|
||||
display->setFont(ArialMT_Plain_10);
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
// The coordinates define the left starting point of the text
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
@@ -851,13 +1007,13 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus);
|
||||
|
||||
// Draw the channel name
|
||||
display->drawString(x, y + FONT_HEIGHT, channelStr);
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr);
|
||||
// Draw our hardware ID to assist with bluetooth pairing
|
||||
display->drawFastImage(x + SCREEN_WIDTH - (10) - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT, 8, 8, imgInfo);
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(ourId), y + FONT_HEIGHT, ourId);
|
||||
display->drawFastImage(x + SCREEN_WIDTH - (10) - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, imgInfo);
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(ourId), y + FONT_HEIGHT_SMALL, ourId);
|
||||
|
||||
// Draw any log messages
|
||||
display->drawLogBuffer(x, y + (FONT_HEIGHT * 2));
|
||||
display->drawLogBuffer(x, y + (FONT_HEIGHT_SMALL * 2));
|
||||
|
||||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||
#ifdef SHOW_REDRAWS
|
||||
@@ -876,12 +1032,14 @@ void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
||||
|
||||
displayedNodeNum = 0; // Not currently showing a node pane
|
||||
|
||||
display->setFont(ArialMT_Plain_10);
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
// The coordinates define the left starting point of the text
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
if (radioConfig.preferences.wifi_ap_mode) {
|
||||
if (isSoftAPForced()) {
|
||||
display->drawString(x, y, String("WiFi: Software AP (Admin)"));
|
||||
} else if (radioConfig.preferences.wifi_ap_mode) {
|
||||
display->drawString(x, y, String("WiFi: Software AP"));
|
||||
} else if (WiFi.status() != WL_CONNECTED) {
|
||||
display->drawString(x, y, String("WiFi: Not Connected"));
|
||||
@@ -904,89 +1062,110 @@ void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
||||
- WL_NO_SHIELD: assigned when no WiFi shield is present;
|
||||
|
||||
*/
|
||||
if (WiFi.status() == WL_CONNECTED || isSoftAPForced() || radioConfig.preferences.wifi_ap_mode) {
|
||||
if (radioConfig.preferences.wifi_ap_mode || isSoftAPForced()) {
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IP: " + String(WiFi.softAPIP().toString().c_str()));
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
if (radioConfig.preferences.wifi_ap_mode) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "IP: " + String(WiFi.softAPIP().toString().c_str()));
|
||||
// Number of connections to the AP. Default mmax for the esp32 is 4
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth("(" + String(WiFi.softAPgetStationNum()) + "/4)"),
|
||||
y + FONT_HEIGHT_SMALL * 1, "(" + String(WiFi.softAPgetStationNum()) + "/4)");
|
||||
} else {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "IP: " + String(WiFi.localIP().toString().c_str()));
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IP: " + String(WiFi.localIP().toString().c_str()));
|
||||
}
|
||||
|
||||
} else if (WiFi.status() == WL_NO_SSID_AVAIL) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "SSID Not Found");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "SSID Not Found");
|
||||
} else if (WiFi.status() == WL_CONNECTION_LOST) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "Connection Lost");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Lost");
|
||||
} else if (WiFi.status() == WL_CONNECT_FAILED) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "Connection Failed");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Failed");
|
||||
//} else if (WiFi.status() == WL_DISCONNECTED) {
|
||||
// display->drawString(x, y + FONT_HEIGHT * 1, "Disconnected");
|
||||
// display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Disconnected");
|
||||
} else if (WiFi.status() == WL_IDLE_STATUS) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "Idle ... Reconnecting");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Idle ... Reconnecting");
|
||||
} else {
|
||||
// Codes:
|
||||
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-reason-code
|
||||
if (getWifiDisconnectReason() == 2) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "Authentication Invalid");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Authentication Invalid");
|
||||
} else if (getWifiDisconnectReason() == 3) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "De-authenticated");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "De-authenticated");
|
||||
} else if (getWifiDisconnectReason() == 4) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "Disassociated Expired");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Disassociated Expired");
|
||||
} else if (getWifiDisconnectReason() == 5) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "AP - Too Many Clients");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AP - Too Many Clients");
|
||||
} else if (getWifiDisconnectReason() == 6) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "NOT_AUTHED");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "NOT_AUTHED");
|
||||
} else if (getWifiDisconnectReason() == 7) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "NOT_ASSOCED");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "NOT_ASSOCED");
|
||||
} else if (getWifiDisconnectReason() == 8) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "Disassociated");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Disassociated");
|
||||
} else if (getWifiDisconnectReason() == 9) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "ASSOC_NOT_AUTHED");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "ASSOC_NOT_AUTHED");
|
||||
} else if (getWifiDisconnectReason() == 10) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "DISASSOC_PWRCAP_BAD");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "DISASSOC_PWRCAP_BAD");
|
||||
} else if (getWifiDisconnectReason() == 11) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "DISASSOC_SUPCHAN_BAD");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "DISASSOC_SUPCHAN_BAD");
|
||||
} else if (getWifiDisconnectReason() == 13) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "IE_INVALID");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IE_INVALID");
|
||||
} else if (getWifiDisconnectReason() == 14) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "MIC_FAILURE");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "MIC_FAILURE");
|
||||
} else if (getWifiDisconnectReason() == 15) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "AP Handshake Timeout");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AP Handshake Timeout");
|
||||
} else if (getWifiDisconnectReason() == 16) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "GROUP_KEY_UPDATE_TIMEOUT");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "GROUP_KEY_UPDATE_TIMEOUT");
|
||||
} else if (getWifiDisconnectReason() == 17) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "IE_IN_4WAY_DIFFERS");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IE_IN_4WAY_DIFFERS");
|
||||
} else if (getWifiDisconnectReason() == 18) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "Invalid Group Cipher");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Invalid Group Cipher");
|
||||
} else if (getWifiDisconnectReason() == 19) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "Invalid Pairwise Cipher");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Invalid Pairwise Cipher");
|
||||
} else if (getWifiDisconnectReason() == 20) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "AKMP_INVALID");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AKMP_INVALID");
|
||||
} else if (getWifiDisconnectReason() == 21) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "UNSUPP_RSN_IE_VERSION");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "UNSUPP_RSN_IE_VERSION");
|
||||
} else if (getWifiDisconnectReason() == 22) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "INVALID_RSN_IE_CAP");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "INVALID_RSN_IE_CAP");
|
||||
} else if (getWifiDisconnectReason() == 23) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "802_1X_AUTH_FAILED");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "802_1X_AUTH_FAILED");
|
||||
} else if (getWifiDisconnectReason() == 24) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "CIPHER_SUITE_REJECTED");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "CIPHER_SUITE_REJECTED");
|
||||
} else if (getWifiDisconnectReason() == 200) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "BEACON_TIMEOUT");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "BEACON_TIMEOUT");
|
||||
} else if (getWifiDisconnectReason() == 201) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "AP Not Found");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AP Not Found");
|
||||
} else if (getWifiDisconnectReason() == 202) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "AUTH_FAIL");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AUTH_FAIL");
|
||||
} else if (getWifiDisconnectReason() == 203) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "ASSOC_FAIL");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "ASSOC_FAIL");
|
||||
} else if (getWifiDisconnectReason() == 204) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "HANDSHAKE_TIMEOUT");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "HANDSHAKE_TIMEOUT");
|
||||
} else if (getWifiDisconnectReason() == 205) {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "Connection Failed");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Failed");
|
||||
} else {
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, "Unknown Status");
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Unknown Status");
|
||||
}
|
||||
}
|
||||
|
||||
display->drawString(x, y + FONT_HEIGHT * 2, "SSID: " + String(wifiName));
|
||||
display->drawString(x, y + FONT_HEIGHT * 3, "PWD: " + String(wifiPsw));
|
||||
if (isSoftAPForced()) {
|
||||
if ((millis() / 10000) % 2) {
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 2, "SSID: meshtasticAdmin");
|
||||
} else {
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 2, "PWD: 12345678");
|
||||
}
|
||||
|
||||
} else {
|
||||
if (radioConfig.preferences.wifi_ap_mode) {
|
||||
if ((millis() / 10000) % 2) {
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 2, "SSID: " + String(wifiName));
|
||||
} else {
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 2, "PWD: " + String(wifiPsw));
|
||||
}
|
||||
} else {
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 2, "SSID: " + String(wifiName));
|
||||
}
|
||||
}
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 3, "http://meshtastic.local");
|
||||
|
||||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||
#ifdef SHOW_REDRAWS
|
||||
@@ -1001,7 +1180,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
{
|
||||
displayedNodeNum = 0; // Not currently showing a node pane
|
||||
|
||||
display->setFont(ArialMT_Plain_10);
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
// The coordinates define the left starting point of the text
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
@@ -1035,15 +1214,21 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
minutes %= 60;
|
||||
hours %= 24;
|
||||
|
||||
display->drawString(x, y + FONT_HEIGHT * 1,
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1,
|
||||
String(days) + "d " + (hours < 10 ? "0" : "") + String(hours) + ":" + (minutes < 10 ? "0" : "") +
|
||||
String(minutes) + ":" + (seconds < 10 ? "0" : "") + String(seconds));
|
||||
|
||||
#ifndef NO_ESP32
|
||||
// Show CPU Frequency.
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth("CPU " + String(getCpuFrequencyMhz()) + "MHz"),
|
||||
y + FONT_HEIGHT_SMALL * 1, "CPU " + String(getCpuFrequencyMhz()) + "MHz");
|
||||
#endif
|
||||
|
||||
// Line 3
|
||||
drawGPSAltitude(display, x, y + FONT_HEIGHT * 2, gpsStatus);
|
||||
drawGPSAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
|
||||
|
||||
// Line 4
|
||||
drawGPScoordinates(display, x, y + FONT_HEIGHT * 3, gpsStatus);
|
||||
drawGPScoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus);
|
||||
|
||||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||
#ifdef SHOW_REDRAWS
|
||||
@@ -1072,16 +1257,23 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
|
||||
// DEBUG_MSG("Screen got status update %d\n", arg->getStatusType());
|
||||
switch (arg->getStatusType()) {
|
||||
case STATUS_TYPE_NODE:
|
||||
if (nodeDB.updateTextMessage || nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
|
||||
setFrames(); // Regen the list of screens
|
||||
prevFrame = -1; // Force a GUI update
|
||||
setInterval(0); // Update the screen right away
|
||||
if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
|
||||
setFrames(); // Regen the list of screens
|
||||
}
|
||||
nodeDB.updateGUI = false;
|
||||
nodeDB.updateTextMessage = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Screen::handleTextMessage(const MeshPacket *arg)
|
||||
{
|
||||
if (showingNormalScreen) {
|
||||
setFrames(); // Regen the list of screens (will show new text message)
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace graphics
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#ifdef USE_SH1106
|
||||
#include <SH1106Wire.h>
|
||||
#elif defined(USE_ST7567)
|
||||
#include <ST7567Wire.h>
|
||||
#else
|
||||
#include <SSD1306Wire.h>
|
||||
#endif
|
||||
@@ -19,6 +21,11 @@
|
||||
#include "power.h"
|
||||
#include <string>
|
||||
|
||||
// 0 to 255, though particular variants might define different defaults
|
||||
#ifndef BRIGHTNESS_DEFAULT
|
||||
#define BRIGHTNESS_DEFAULT 150
|
||||
#endif
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
|
||||
@@ -70,6 +77,8 @@ class Screen : public concurrency::OSThread
|
||||
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
||||
CallbackObserver<Screen, const meshtastic::Status *> nodeStatusObserver =
|
||||
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
||||
CallbackObserver<Screen, const MeshPacket *> textMessageObserver =
|
||||
CallbackObserver<Screen, const MeshPacket *>(this, &Screen::handleTextMessage);
|
||||
|
||||
public:
|
||||
Screen(uint8_t address, int sda = -1, int scl = -1);
|
||||
@@ -92,12 +101,20 @@ class Screen : public concurrency::OSThread
|
||||
enqueueCmd(ScreenCmd{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the display for the unit going to the lowest power mode possible. Most screens will just
|
||||
* poweroff, but eink screens will show a "I'm sleeping" graphic, possibly with a QR code
|
||||
*/
|
||||
void doDeepSleep();
|
||||
|
||||
void blink();
|
||||
|
||||
/// Handles a button press.
|
||||
void onPress() { enqueueCmd(ScreenCmd{.cmd = Cmd::ON_PRESS}); }
|
||||
|
||||
// Implementation to Adjust Brightness
|
||||
void adjustBrightness();
|
||||
uint8_t brightness = 150;
|
||||
uint8_t brightness = BRIGHTNESS_DEFAULT;
|
||||
|
||||
/// Starts showing the Bluetooth PIN screen.
|
||||
//
|
||||
@@ -179,6 +196,10 @@ class Screen : public concurrency::OSThread
|
||||
DebugInfo *debug_info() { return &debugInfo; }
|
||||
|
||||
int handleStatusUpdate(const meshtastic::Status *arg);
|
||||
int handleTextMessage(const MeshPacket *arg);
|
||||
|
||||
/// Used to force (super slow) eink displays to draw critical frames
|
||||
void forceDisplay();
|
||||
|
||||
protected:
|
||||
/// Updates the UI.
|
||||
@@ -216,6 +237,9 @@ class Screen : public concurrency::OSThread
|
||||
/// Rebuilds our list of frames (screens) to default ones.
|
||||
void setFrames();
|
||||
|
||||
/// Try to start drawing ASAP
|
||||
void setFastFramerate();
|
||||
|
||||
/// Called when debug screen is to be drawn, calls through to debugInfo.drawFrame.
|
||||
static void drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
|
||||
@@ -244,6 +268,8 @@ class Screen : public concurrency::OSThread
|
||||
EInkDisplay dispdev;
|
||||
#elif defined(USE_SH1106)
|
||||
SH1106Wire dispdev;
|
||||
#elif defined(USE_ST7567)
|
||||
ST7567Wire dispdev;
|
||||
#else
|
||||
SSD1306Wire dispdev;
|
||||
#endif
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#ifdef ST7735_CS
|
||||
#include "SPILock.h"
|
||||
#include "TFTDisplay.h"
|
||||
#include "graphics/configs.h"
|
||||
#include <SPI.h>
|
||||
#include <TFT_eSPI.h> // Graphics and font library for ST7735 driver chip
|
||||
|
||||
@@ -11,8 +10,7 @@ static TFT_eSPI tft = TFT_eSPI(); // Invoke library, pins defined in User_Setup.
|
||||
|
||||
TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl)
|
||||
{
|
||||
setGeometry(GEOMETRY_128_64); // FIXME - currently we lie and claim 128x64 because I'm not yet sure other resolutions will
|
||||
// work ie GEOMETRY_RAWMODE
|
||||
setGeometry(GEOMETRY_RAWMODE, 160, 80);
|
||||
}
|
||||
|
||||
// Write the buffer to the display memory
|
||||
@@ -22,11 +20,11 @@ void TFTDisplay::display(void)
|
||||
|
||||
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
|
||||
// tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK);
|
||||
for (uint8_t y = 0; y < SCREEN_HEIGHT; y++) {
|
||||
for (uint8_t x = 0; x < SCREEN_WIDTH; x++) {
|
||||
for (uint8_t y = 0; y < displayHeight; y++) {
|
||||
for (uint8_t x = 0; x < displayWidth; x++) {
|
||||
|
||||
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent
|
||||
auto b = buffer[x + (y / 8) * SCREEN_WIDTH];
|
||||
auto b = buffer[x + (y / 8) * displayWidth];
|
||||
auto isset = b & (1 << (y & 7));
|
||||
tft.drawPixel(x, y, isset ? TFT_WHITE : TFT_BLACK);
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "fonts.h"
|
||||
|
||||
#define FONT_HEIGHT 14 // actually 13 for "Arial 10" but want a little extra space
|
||||
#define FONT_HEIGHT_16 (ArialMT_Plain_16[1] + 1)
|
||||
// This means the *visible* area (sh1106 can address 132, but shows 128 for example)
|
||||
#define SCREEN_WIDTH 128
|
||||
#define SCREEN_HEIGHT 64
|
||||
#define TRANSITION_FRAMERATE 30 // fps
|
||||
#define IDLE_FRAMERATE 1 // in fps
|
||||
#define COMPASS_DIAM 44
|
||||
|
||||
// DEBUG
|
||||
#define NUM_EXTRA_FRAMES 2 // text message and debug frame
|
||||
// if defined a pixel will blink to show redraws
|
||||
// #define SHOW_REDRAWS
|
||||
215
src/main.cpp
215
src/main.cpp
@@ -5,20 +5,21 @@
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "UBloxGPS.h"
|
||||
#include "airtime.h"
|
||||
#include "configuration.h"
|
||||
#include "error.h"
|
||||
#include "power.h"
|
||||
// #include "rom/rtc.h"
|
||||
#include "DSRRouter.h"
|
||||
// #include "debug.h"
|
||||
#include "FSCommon.h"
|
||||
#include "RTC.h"
|
||||
#include "SPILock.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "concurrency/Periodic.h"
|
||||
#include "graphics/Screen.h"
|
||||
#include "main.h"
|
||||
#include "meshwifi/meshhttp.h"
|
||||
#include "meshwifi/meshwifi.h"
|
||||
#include "plugins/Plugins.h"
|
||||
#include "sleep.h"
|
||||
#include "target_specific.h"
|
||||
#include <OneButton.h>
|
||||
@@ -26,6 +27,8 @@
|
||||
// #include <driver/rtc_io.h>
|
||||
|
||||
#ifndef NO_ESP32
|
||||
#include "mesh/http/WebServer.h"
|
||||
#include "mesh/http/WiFiAPClient.h"
|
||||
#include "nimble/BluetoothUtil.h"
|
||||
#endif
|
||||
|
||||
@@ -50,7 +53,9 @@ meshtastic::GPSStatus *gpsStatus = new meshtastic::GPSStatus();
|
||||
// Global Node status
|
||||
meshtastic::NodeStatus *nodeStatus = new meshtastic::NodeStatus();
|
||||
|
||||
bool ssd1306_found;
|
||||
/// The I2C address of our display (if found)
|
||||
uint8_t screen_found;
|
||||
|
||||
bool axp192_found;
|
||||
|
||||
Router *router = NULL; // Users of router don't care what sort of subclass implements that API
|
||||
@@ -72,9 +77,13 @@ void scanI2Cdevice(void)
|
||||
nDevices++;
|
||||
|
||||
if (addr == SSD1306_ADDRESS) {
|
||||
ssd1306_found = true;
|
||||
screen_found = addr;
|
||||
DEBUG_MSG("ssd1306 display found\n");
|
||||
}
|
||||
if (addr == ST7567_ADDRESS) {
|
||||
screen_found = addr;
|
||||
DEBUG_MSG("st7567 display found\n");
|
||||
}
|
||||
#ifdef AXP192_SLAVE_ADDRESS
|
||||
if (addr == AXP192_SLAVE_ADDRESS) {
|
||||
axp192_found = true;
|
||||
@@ -85,6 +94,7 @@ void scanI2Cdevice(void)
|
||||
DEBUG_MSG("Unknow error at address 0x%x\n", addr);
|
||||
}
|
||||
}
|
||||
|
||||
if (nDevices == 0)
|
||||
DEBUG_MSG("No I2C devices found\n");
|
||||
else
|
||||
@@ -161,19 +171,35 @@ class ButtonThread : public OSThread
|
||||
#endif
|
||||
|
||||
public:
|
||||
static uint32_t longPressTime;
|
||||
|
||||
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
|
||||
ButtonThread() : OSThread("Button")
|
||||
{
|
||||
#ifdef BUTTON_PIN
|
||||
userButton = OneButton(BUTTON_PIN, true, true);
|
||||
#ifdef INPUT_PULLUP_SENSE
|
||||
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
|
||||
pinMode(BUTTON_PIN, INPUT_PULLUP_SENSE);
|
||||
#endif
|
||||
userButton.attachClick(userButtonPressed);
|
||||
userButton.attachDuringLongPress(userButtonPressedLong);
|
||||
userButton.attachDoubleClick(userButtonDoublePressed);
|
||||
userButton.attachLongPressStart(userButtonPressedLongStart);
|
||||
userButton.attachLongPressStop(userButtonPressedLongStop);
|
||||
wakeOnIrq(BUTTON_PIN, FALLING);
|
||||
#endif
|
||||
#ifdef BUTTON_PIN_ALT
|
||||
userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
|
||||
#ifdef INPUT_PULLUP_SENSE
|
||||
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
|
||||
pinMode(BUTTON_PIN_ALT, INPUT_PULLUP_SENSE);
|
||||
#endif
|
||||
userButtonAlt.attachClick(userButtonPressed);
|
||||
userButton.attachDuringLongPress(userButtonPressedLong);
|
||||
userButtonAlt.attachDuringLongPress(userButtonPressedLong);
|
||||
userButtonAlt.attachDoubleClick(userButtonDoublePressed);
|
||||
userButtonAlt.attachLongPressStart(userButtonPressedLongStart);
|
||||
userButtonAlt.attachLongPressStop(userButtonPressedLongStop);
|
||||
wakeOnIrq(BUTTON_PIN_ALT, FALLING);
|
||||
#endif
|
||||
}
|
||||
@@ -190,9 +216,10 @@ class ButtonThread : public OSThread
|
||||
#endif
|
||||
#ifdef BUTTON_PIN_ALT
|
||||
userButtonAlt.tick();
|
||||
canSleep &= userButton.isIdle();
|
||||
canSleep &= userButtonAlt.isIdle();
|
||||
#endif
|
||||
// if(!canSleep) DEBUG_MSG("Supressing sleep!\n");
|
||||
// if (!canSleep) DEBUG_MSG("Supressing sleep!\n");
|
||||
// else DEBUG_MSG("sleep ok\n");
|
||||
|
||||
return 5;
|
||||
}
|
||||
@@ -203,16 +230,58 @@ class ButtonThread : public OSThread
|
||||
// DEBUG_MSG("press!\n");
|
||||
powerFSM.trigger(EVENT_PRESS);
|
||||
}
|
||||
static void userButtonPressedLong() { screen->adjustBrightness(); }
|
||||
static void userButtonPressedLong()
|
||||
{
|
||||
// DEBUG_MSG("Long press!\n");
|
||||
screen->adjustBrightness();
|
||||
|
||||
// If user button is held down for 10 seconds, shutdown the device.
|
||||
if (millis() - longPressTime > 10 * 1000) {
|
||||
#ifdef TBEAM_V10
|
||||
if (axp192_found == true) {
|
||||
setLed(false);
|
||||
power->shutdown();
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
// DEBUG_MSG("Long press %u\n", (millis() - longPressTime));
|
||||
}
|
||||
}
|
||||
|
||||
static void userButtonDoublePressed()
|
||||
{
|
||||
#ifndef NO_ESP32
|
||||
disablePin();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void userButtonPressedLongStart()
|
||||
{
|
||||
DEBUG_MSG("Long press start!\n");
|
||||
longPressTime = millis();
|
||||
}
|
||||
|
||||
static void userButtonPressedLongStop()
|
||||
{
|
||||
DEBUG_MSG("Long press stop!\n");
|
||||
longPressTime = 0;
|
||||
}
|
||||
};
|
||||
|
||||
static Periodic *ledPeriodic;
|
||||
static OSThread *powerFSMthread, *buttonThread;
|
||||
uint32_t ButtonThread::longPressTime = 0;
|
||||
|
||||
RadioInterface *rIf = NULL;
|
||||
|
||||
void setup()
|
||||
{
|
||||
concurrency::hasBeenSetup = true;
|
||||
|
||||
#ifdef SEGGER_STDOUT_CH
|
||||
SEGGER_RTT_ConfigUpBuffer(SEGGER_STDOUT_CH, NULL, NULL, 1024, SEGGER_RTT_MODE_NO_BLOCK_TRIM);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SEGGER
|
||||
SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_TRIM);
|
||||
#endif
|
||||
@@ -234,10 +303,31 @@ void setup()
|
||||
digitalWrite(RESET_OLED, 1);
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN
|
||||
#ifndef NO_ESP32
|
||||
// If BUTTON_PIN is held down during the startup process,
|
||||
// force the device to go into a SoftAP mode.
|
||||
bool forceSoftAP = 0;
|
||||
pinMode(BUTTON_PIN, INPUT);
|
||||
#ifdef BUTTON_NEED_PULLUP
|
||||
gpio_pullup_en((gpio_num_t)BUTTON_PIN);
|
||||
#endif
|
||||
|
||||
// BUTTON_PIN is pulled high by a 12k resistor.
|
||||
if (!digitalRead(BUTTON_PIN)) {
|
||||
forceSoftAP = 1;
|
||||
DEBUG_MSG("Setting forceSoftAP = 1\n");
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
OSThread::setup();
|
||||
|
||||
ledPeriodic = new Periodic("Blink", ledBlinker);
|
||||
|
||||
fsInit();
|
||||
|
||||
router = new DSRRouter();
|
||||
|
||||
#ifdef I2C_SDA
|
||||
@@ -245,13 +335,21 @@ void setup()
|
||||
#else
|
||||
Wire.begin();
|
||||
#endif
|
||||
// i2c still busted on new board
|
||||
#ifndef ARDUINO_NRF52840_PPR
|
||||
scanI2Cdevice();
|
||||
|
||||
#ifdef PIN_LCD_RESET
|
||||
// FIXME - move this someplace better, LCD is at address 0x3F
|
||||
pinMode(PIN_LCD_RESET, OUTPUT);
|
||||
digitalWrite(PIN_LCD_RESET, 0);
|
||||
delay(1);
|
||||
digitalWrite(PIN_LCD_RESET, 1);
|
||||
delay(1);
|
||||
#endif
|
||||
|
||||
scanI2Cdevice();
|
||||
|
||||
// Buttons & LED
|
||||
buttonThread = new ButtonThread();
|
||||
|
||||
#ifdef LED_PIN
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
digitalWrite(LED_PIN, 1 ^ LED_INVERTED); // turn on for now
|
||||
@@ -263,7 +361,7 @@ void setup()
|
||||
#ifndef NO_ESP32
|
||||
// Don't init display if we don't have one or we are waking headless due to a timer event
|
||||
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER)
|
||||
ssd1306_found = false; // forget we even have the hardware
|
||||
screen_found = 0; // forget we even have the hardware
|
||||
|
||||
esp32Setup();
|
||||
#endif
|
||||
@@ -272,6 +370,10 @@ void setup()
|
||||
nrf52Setup();
|
||||
#endif
|
||||
|
||||
// We do this as early as possible because this loads preferences from flash
|
||||
// but we need to do this after main cpu iniot (esp32setup), because we need the random seed set
|
||||
nodeDB.init();
|
||||
|
||||
// Currently only the tbeam has a PMU
|
||||
power = new Power();
|
||||
power->setStatusHandler(powerStatus);
|
||||
@@ -289,55 +391,80 @@ void setup()
|
||||
#endif
|
||||
|
||||
// Initialize the screen first so we can show the logo while we start up everything else.
|
||||
screen = new graphics::Screen(SSD1306_ADDRESS);
|
||||
#if defined(ST7735_CS) || defined(HAS_EINK)
|
||||
screen->setup();
|
||||
#else
|
||||
if (ssd1306_found)
|
||||
screen->setup();
|
||||
#endif
|
||||
|
||||
screen->print("Started...\n");
|
||||
screen = new graphics::Screen(screen_found);
|
||||
|
||||
readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time)
|
||||
|
||||
// If we know we have a L80 GPS, don't try UBLOX
|
||||
#ifndef L80_RESET
|
||||
#ifdef GENIEBLOCKS
|
||||
// gps setup
|
||||
pinMode(GPS_RESET_N, OUTPUT);
|
||||
pinMode(GPS_EXTINT, OUTPUT);
|
||||
digitalWrite(GPS_RESET_N, HIGH);
|
||||
digitalWrite(GPS_EXTINT, LOW);
|
||||
// battery setup
|
||||
// If we want to read battery level, we need to set BATTERY_EN_PIN pin to low.
|
||||
// ToDo: For low power consumption after read battery level, set that pin to high.
|
||||
pinMode(BATTERY_EN_PIN, OUTPUT);
|
||||
digitalWrite(BATTERY_EN_PIN, LOW);
|
||||
#endif
|
||||
|
||||
// If we don't have bidirectional comms, we can't even try talking to UBLOX
|
||||
UBloxGPS *ublox = NULL;
|
||||
#ifdef GPS_TX_PIN
|
||||
// Init GPS - first try ublox
|
||||
auto ublox = new UBloxGPS();
|
||||
ublox = new UBloxGPS();
|
||||
gps = ublox;
|
||||
if (!gps->setup()) {
|
||||
DEBUG_MSG("ERROR: No UBLOX GPS found\n");
|
||||
|
||||
delete ublox;
|
||||
gps = ublox = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (GPS::_serial_gps) {
|
||||
// Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just
|
||||
// assume NMEA at 9600 baud.
|
||||
// dumb NMEA access only work for serial GPSes)
|
||||
DEBUG_MSG("Hoping that NMEA might work\n");
|
||||
if (!gps && GPS::_serial_gps) {
|
||||
// Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just
|
||||
// assume NMEA at 9600 baud.
|
||||
// dumb NMEA access only work for serial GPSes)
|
||||
DEBUG_MSG("Hoping that NMEA might work\n");
|
||||
|
||||
#ifdef HAS_AIR530_GPS
|
||||
gps = new Air530GPS();
|
||||
gps = new Air530GPS();
|
||||
#else
|
||||
gps = new NMEAGPS();
|
||||
gps = new NMEAGPS();
|
||||
#endif
|
||||
gps->setup();
|
||||
}
|
||||
gps->setup();
|
||||
}
|
||||
#else
|
||||
gps = new NMEAGPS();
|
||||
gps->setup();
|
||||
#endif
|
||||
|
||||
if (gps)
|
||||
gpsStatus->observe(&gps->newStatus);
|
||||
else
|
||||
DEBUG_MSG("Warning: No GPS found - running without GPS\n");
|
||||
|
||||
nodeStatus->observe(&nodeDB.newStatus);
|
||||
|
||||
service.init();
|
||||
|
||||
// Now that the mesh service is created, create any plugins
|
||||
setupPlugins();
|
||||
|
||||
// Do this after service.init (because that clears error_code)
|
||||
#ifdef AXP192_SLAVE_ADDRESS
|
||||
if (!axp192_found)
|
||||
recordCriticalError(CriticalErrorCode_NoAXP192); // Record a hardware fault for missing hardware
|
||||
#endif
|
||||
|
||||
// Don't call screen setup until after nodedb is setup (because we need
|
||||
// the current region name)
|
||||
#if defined(ST7735_CS) || defined(HAS_EINK)
|
||||
screen->setup();
|
||||
#else
|
||||
if (screen_found)
|
||||
screen->setup();
|
||||
#endif
|
||||
|
||||
screen->print("Started...\n");
|
||||
|
||||
// We have now loaded our saved preferences from flash
|
||||
|
||||
// ONCE we will factory reset the GPS for bug #327
|
||||
@@ -389,11 +516,19 @@ void setup()
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef NO_ESP32
|
||||
// Initialize Wifi
|
||||
initWifi();
|
||||
initWifi(forceSoftAP);
|
||||
|
||||
// Start web server thread.
|
||||
webServerThread = new WebServerThread();
|
||||
#endif
|
||||
|
||||
// Start airtime logger thread.
|
||||
airTime = new AirTime();
|
||||
|
||||
if (!rIf)
|
||||
recordCriticalError(ErrNoRadio);
|
||||
recordCriticalError(CriticalErrorCode_NoRadio);
|
||||
else
|
||||
router->addInterface(rIf);
|
||||
|
||||
@@ -452,7 +587,7 @@ void loop()
|
||||
#endif
|
||||
|
||||
// TODO: This should go into a thread handled by FreeRTOS.
|
||||
handleWebResponse();
|
||||
// handleWebResponse();
|
||||
|
||||
service.loop();
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "graphics/Screen.h"
|
||||
|
||||
extern bool axp192_found;
|
||||
extern bool ssd1306_found;
|
||||
extern bool isCharging;
|
||||
extern bool isUSBPowered;
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ bool DSRRouter::weAreInRoute(const RouteDiscovery &route)
|
||||
**/
|
||||
void DSRRouter::updateRoutes(const RouteDiscovery &route, bool isRequest)
|
||||
{
|
||||
DEBUG_MSG("FIXME not implemented");
|
||||
DEBUG_MSG("FIXME not implemented updateRoutes\n");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,7 +178,7 @@ void DSRRouter::updateRoutes(const RouteDiscovery &route, bool isRequest)
|
||||
*/
|
||||
void DSRRouter::sendRouteReply(const RouteDiscovery &route, NodeNum toAppend)
|
||||
{
|
||||
DEBUG_MSG("FIXME not implemented");
|
||||
DEBUG_MSG("FIXME not implemented sendRoute\n");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,7 +188,7 @@ void DSRRouter::sendRouteReply(const RouteDiscovery &route, NodeNum toAppend)
|
||||
*/
|
||||
NodeNum DSRRouter::getNextHop(NodeNum dest)
|
||||
{
|
||||
DEBUG_MSG("FIXME not implemented");
|
||||
DEBUG_MSG("FIXME not implemented getNextHop\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ NodeNum DSRRouter::getNextHop(NodeNum dest)
|
||||
*/
|
||||
void DSRRouter::resendRouteRequest(const MeshPacket *p)
|
||||
{
|
||||
DEBUG_MSG("FIXME not implemented");
|
||||
DEBUG_MSG("FIXME not implemented resendRoute\n");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,7 +208,7 @@ void DSRRouter::resendRouteRequest(const MeshPacket *p)
|
||||
*/
|
||||
void DSRRouter::addRoute(NodeNum dest, NodeNum forwarder, uint8_t numHops)
|
||||
{
|
||||
DEBUG_MSG("FIXME not implemented");
|
||||
DEBUG_MSG("FIXME not implemented addRoute\n");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,7 +216,7 @@ void DSRRouter::addRoute(NodeNum dest, NodeNum forwarder, uint8_t numHops)
|
||||
*/
|
||||
void DSRRouter::removeRoute(NodeNum dest)
|
||||
{
|
||||
DEBUG_MSG("FIXME not implemented");
|
||||
DEBUG_MSG("FIXME not implemented removeRoute\n");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,7 +224,7 @@ void DSRRouter::removeRoute(NodeNum dest)
|
||||
*/
|
||||
void DSRRouter::sendNextHop(NodeNum n, const MeshPacket *p)
|
||||
{
|
||||
DEBUG_MSG("FIXME not implemented");
|
||||
DEBUG_MSG("FIXME not implemented sendNextHop\n");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,7 +232,7 @@ void DSRRouter::sendNextHop(NodeNum n, const MeshPacket *p)
|
||||
*/
|
||||
void DSRRouter::sendRouteError(const MeshPacket *p, RouteError err)
|
||||
{
|
||||
DEBUG_MSG("FIXME not implemented");
|
||||
DEBUG_MSG("FIXME not implemented sendRouteError\n");
|
||||
}
|
||||
|
||||
/** make a copy of p, start discovery, but only if we don't
|
||||
@@ -241,5 +241,5 @@ void DSRRouter::sendRouteError(const MeshPacket *p, RouteError err)
|
||||
*/
|
||||
void DSRRouter::startDiscovery(NodeNum dest)
|
||||
{
|
||||
DEBUG_MSG("FIXME not implemented");
|
||||
DEBUG_MSG("FIXME not implemented startDiscovery\n");
|
||||
}
|
||||
@@ -20,8 +20,7 @@ ErrorCode FloodingRouter::send(MeshPacket *p)
|
||||
bool FloodingRouter::shouldFilterReceived(const MeshPacket *p)
|
||||
{
|
||||
if (wasSeenRecently(p)) { // Note: this will also add a recent packet record
|
||||
DEBUG_MSG("Ignoring incoming msg, because we've already seen it: fr=0x%x,to=0x%x,id=%d,hop_limit=%d\n", p->from, p->to,
|
||||
p->id, p->hop_limit);
|
||||
printPacket("Ignoring incoming msg, because we've already seen it", p);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -38,8 +37,7 @@ void FloodingRouter::sniffReceived(const MeshPacket *p)
|
||||
|
||||
tosend->hop_limit--; // bump down the hop count
|
||||
|
||||
DEBUG_MSG("Rebroadcasting received floodmsg to neighbors, fr=0x%x,to=0x%x,id=%d,hop_limit=%d\n", p->from, p->to,
|
||||
p->id, tosend->hop_limit);
|
||||
printPacket("Rebroadcasting received floodmsg to neighbors", p);
|
||||
// Note: we are careful to resend using the original senders node id
|
||||
// We are careful not to call our hooked version of send() - because we don't want to check this again
|
||||
Router::send(tosend);
|
||||
|
||||
78
src/mesh/MeshPlugin.cpp
Normal file
78
src/mesh/MeshPlugin.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#include "MeshPlugin.h"
|
||||
#include "NodeDB.h"
|
||||
#include "MeshService.h"
|
||||
#include <assert.h>
|
||||
|
||||
std::vector<MeshPlugin *> *MeshPlugin::plugins;
|
||||
|
||||
const MeshPacket *MeshPlugin::currentRequest;
|
||||
|
||||
MeshPlugin::MeshPlugin(const char *_name) : name(_name)
|
||||
{
|
||||
// Can't trust static initalizer order, so we check each time
|
||||
if(!plugins)
|
||||
plugins = new std::vector<MeshPlugin *>();
|
||||
|
||||
plugins->push_back(this);
|
||||
}
|
||||
|
||||
void MeshPlugin::setup() {
|
||||
}
|
||||
|
||||
MeshPlugin::~MeshPlugin()
|
||||
{
|
||||
assert(0); // FIXME - remove from list of plugins once someone needs this feature
|
||||
}
|
||||
|
||||
void MeshPlugin::callPlugins(const MeshPacket &mp)
|
||||
{
|
||||
// DEBUG_MSG("In call plugins\n");
|
||||
bool pluginFound = false;
|
||||
for (auto i = plugins->begin(); i != plugins->end(); ++i) {
|
||||
auto &pi = **i;
|
||||
|
||||
pi.currentRequest = ∓
|
||||
if (pi.wantPortnum(mp.decoded.data.portnum)) {
|
||||
pluginFound = true;
|
||||
|
||||
bool handled = pi.handleReceived(mp);
|
||||
|
||||
// Possibly send replies
|
||||
if (mp.decoded.want_response)
|
||||
pi.sendResponse(mp);
|
||||
|
||||
DEBUG_MSG("Plugin %s handled=%d\n", pi.name, handled);
|
||||
if (handled)
|
||||
break;
|
||||
}
|
||||
|
||||
pi.currentRequest = NULL;
|
||||
}
|
||||
|
||||
if(!pluginFound)
|
||||
DEBUG_MSG("No plugins interested in portnum=%d\n", mp.decoded.data.portnum);
|
||||
}
|
||||
|
||||
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
|
||||
* so that subclasses can (optionally) send a response back to the original sender. Implementing this method
|
||||
* is optional
|
||||
*/
|
||||
void MeshPlugin::sendResponse(const MeshPacket &req) {
|
||||
auto r = allocReply();
|
||||
if(r) {
|
||||
DEBUG_MSG("Sending response\n");
|
||||
setReplyTo(r, req);
|
||||
service.sendToMesh(r);
|
||||
}
|
||||
else {
|
||||
DEBUG_MSG("WARNING: Client requested response but this plugin did not provide\n");
|
||||
}
|
||||
}
|
||||
|
||||
/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet
|
||||
* This ensures that if the request packet was sent reliably, the reply is sent that way as well.
|
||||
*/
|
||||
void setReplyTo(MeshPacket *p, const MeshPacket &to) {
|
||||
p->to = to.from;
|
||||
p->want_ack = to.want_ack;
|
||||
}
|
||||
76
src/mesh/MeshPlugin.h
Normal file
76
src/mesh/MeshPlugin.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include "mesh/MeshTypes.h"
|
||||
#include <vector>
|
||||
/** A baseclass for any mesh "plugin".
|
||||
*
|
||||
* A plugin allows you to add new features to meshtastic device code, without needing to know messaging details.
|
||||
*
|
||||
* A key concept for this is that your plugin should use a particular "portnum" for each message type you want to receive
|
||||
* and handle.
|
||||
*
|
||||
* Interally we use plugins to implement the core meshtastic text messaging and gps position sharing features. You
|
||||
* can use these classes as examples for how to write your own custom plugin. See here: (FIXME)
|
||||
*/
|
||||
class MeshPlugin
|
||||
{
|
||||
static std::vector<MeshPlugin *> *plugins;
|
||||
|
||||
public:
|
||||
/** Constructor
|
||||
* name is for debugging output
|
||||
*/
|
||||
MeshPlugin(const char *_name);
|
||||
|
||||
virtual ~MeshPlugin();
|
||||
|
||||
/** For use only by MeshService
|
||||
*/
|
||||
static void callPlugins(const MeshPacket &mp);
|
||||
|
||||
protected:
|
||||
const char *name;
|
||||
|
||||
/**
|
||||
* If this plugin is currently handling a request currentRequest will be preset
|
||||
* to the packet with the request. This is mostly useful for reply handlers.
|
||||
*
|
||||
* Note: this can be static because we are guaranteed to be processing only one
|
||||
* plugin at a time.
|
||||
*/
|
||||
static const MeshPacket *currentRequest;
|
||||
|
||||
/**
|
||||
* Initialize your plugin. This setup function is called once after all hardware and mesh protocol layers have
|
||||
* been initialized
|
||||
*/
|
||||
virtual void setup();
|
||||
|
||||
/**
|
||||
* @return true if you want to receive the specified portnum
|
||||
*/
|
||||
virtual bool wantPortnum(PortNum p) = 0;
|
||||
|
||||
/** Called to handle a particular incoming message
|
||||
|
||||
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
|
||||
*/
|
||||
virtual bool handleReceived(const MeshPacket &mp) { return false; }
|
||||
|
||||
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
|
||||
* so that subclasses can (optionally) send a response back to the original sender. */
|
||||
virtual MeshPacket *allocReply() { return NULL; }
|
||||
|
||||
private:
|
||||
|
||||
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
|
||||
* so that subclasses can (optionally) send a response back to the original sender. This method calls allocReply()
|
||||
* to generate the reply message, and if !NULL that message will be delivered to whoever sent req
|
||||
*/
|
||||
void sendResponse(const MeshPacket &req);
|
||||
};
|
||||
|
||||
/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet
|
||||
* This ensures that if the request packet was sent reliably, the reply is sent that way as well.
|
||||
*/
|
||||
void setReplyTo(MeshPacket *p, const MeshPacket &to);
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "MeshTypes.h"
|
||||
#include "PointerQueue.h"
|
||||
#include "configuration.h"
|
||||
#include "mesh.pb.h"
|
||||
|
||||
// Map from old region names to new region enums
|
||||
struct RegionInfo {
|
||||
@@ -16,4 +15,7 @@ struct RegionInfo {
|
||||
const char *name; // EU433 etc
|
||||
};
|
||||
|
||||
extern const RegionInfo regions[];
|
||||
extern const RegionInfo regions[];
|
||||
extern const RegionInfo *myRegion;
|
||||
|
||||
extern void initRegion();
|
||||
@@ -13,6 +13,8 @@
|
||||
#include "RTC.h"
|
||||
#include "main.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "plugins/PositionPlugin.h"
|
||||
#include "plugins/NodeInfoPlugin.h"
|
||||
#include "power.h"
|
||||
|
||||
/*
|
||||
@@ -51,7 +53,15 @@ MeshService service;
|
||||
|
||||
static int32_t sendOwnerCb()
|
||||
{
|
||||
service.sendOurOwner();
|
||||
static uint32_t currentGeneration;
|
||||
|
||||
// If we changed channels, ask everyone else for their latest info
|
||||
bool requestReplies = currentGeneration != radioGeneration;
|
||||
currentGeneration = radioGeneration;
|
||||
|
||||
DEBUG_MSG("Sending our nodeinfo to mesh (wantReplies=%d)\n", requestReplies);
|
||||
assert(nodeInfoPlugin);
|
||||
nodeInfoPlugin->sendOurNodeInfo(NODENUM_BROADCAST, requestReplies); // Send our info (don't request replies)
|
||||
|
||||
return getPref_send_owner_interval() * getPref_position_broadcast_secs() * 1000;
|
||||
}
|
||||
@@ -66,121 +76,35 @@ MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE)
|
||||
void MeshService::init()
|
||||
{
|
||||
sendOwnerPeriod = new concurrency::Periodic("SendOwner", sendOwnerCb);
|
||||
sendOwnerPeriod->setIntervalFromNow(30 * 1000); // Send our initial owner announcement 30 seconds after we start (to give network time to setup)
|
||||
|
||||
nodeDB.init();
|
||||
// moved much earlier in boot (called from setup())
|
||||
// nodeDB.init();
|
||||
|
||||
if (gps)
|
||||
gpsObserver.observe(&gps->newStatus);
|
||||
packetReceivedObserver.observe(&router->notifyPacketReceived);
|
||||
}
|
||||
|
||||
void MeshService::sendOurOwner(NodeNum dest, bool wantReplies)
|
||||
{
|
||||
MeshPacket *p = router->allocForSending();
|
||||
p->to = dest;
|
||||
p->decoded.want_response = wantReplies;
|
||||
p->decoded.which_payload = SubPacket_user_tag;
|
||||
User &u = p->decoded.user;
|
||||
u = owner;
|
||||
DEBUG_MSG("sending owner %s/%s/%s\n", u.id, u.long_name, u.short_name);
|
||||
|
||||
sendToMesh(p);
|
||||
}
|
||||
|
||||
/// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all
|
||||
const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp)
|
||||
{
|
||||
bool wasBroadcast = mp->to == NODENUM_BROADCAST;
|
||||
|
||||
// Disable this collision testing if we use 32 bit nodenums
|
||||
bool isCollision = (sizeof(NodeNum) == 1) && (mp->from == myNodeInfo.my_node_num);
|
||||
|
||||
if (isCollision) {
|
||||
// we win if we have a lower macaddr
|
||||
bool weWin = memcmp(&owner.macaddr, &mp->decoded.user.macaddr, sizeof(owner.macaddr)) < 0;
|
||||
|
||||
if (weWin) {
|
||||
DEBUG_MSG("NOTE! Received a nodenum collision and we are vetoing\n");
|
||||
|
||||
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->decoded.user.long_name + "\n";
|
||||
screen->print(lcd.c_str());
|
||||
}
|
||||
|
||||
return mp;
|
||||
}
|
||||
|
||||
void MeshService::handleIncomingPosition(const MeshPacket *mp)
|
||||
{
|
||||
if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_position_tag) {
|
||||
DEBUG_MSG("handled incoming position time=%u\n", mp->decoded.position.time);
|
||||
|
||||
if (mp->decoded.position.time) {
|
||||
struct timeval tv;
|
||||
uint32_t secs = mp->decoded.position.time;
|
||||
|
||||
tv.tv_sec = secs;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
perhapsSetRTC(RTCQualityFromNet, &tv);
|
||||
}
|
||||
} else {
|
||||
DEBUG_MSG("Ignoring incoming packet - not a position\n");
|
||||
}
|
||||
}
|
||||
|
||||
int MeshService::handleFromRadio(const MeshPacket *mp)
|
||||
{
|
||||
powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping
|
||||
|
||||
// If it is a position packet, perhaps set our clock - this must be before nodeDB.updateFrom
|
||||
handleIncomingPosition(mp);
|
||||
printPacket("Forwarding to phone", mp);
|
||||
nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
|
||||
|
||||
if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_user_tag) {
|
||||
mp = handleFromRadioUser(mp);
|
||||
fromNum++;
|
||||
|
||||
if (toPhoneQueue.numFree() == 0) {
|
||||
DEBUG_MSG("NOTE: tophone queue is full, discarding oldest\n");
|
||||
MeshPacket *d = toPhoneQueue.dequeuePtr(0);
|
||||
if (d)
|
||||
releaseToPool(d);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
printPacket("Forwarding to phone", mp);
|
||||
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);
|
||||
}
|
||||
|
||||
MeshPacket *copied = packetPool.allocCopy(*mp);
|
||||
assert(toPhoneQueue.enqueue(copied, 0)); // FIXME, instead of failing for full queue, delete the oldest mssages
|
||||
|
||||
if (mp->decoded.want_response)
|
||||
sendNetworkPing(mp->from);
|
||||
} else {
|
||||
DEBUG_MSG("Not delivering vetoed User message\n");
|
||||
}
|
||||
MeshPacket *copied = packetPool.allocCopy(*mp);
|
||||
assert(toPhoneQueue.enqueue(copied, 0)); // FIXME, instead of failing for full queue, delete the oldest mssages
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -198,7 +122,10 @@ void MeshService::loop()
|
||||
bool MeshService::reloadConfig()
|
||||
{
|
||||
// If we can successfully set this radio to these settings, save them to disk
|
||||
|
||||
// This will also update the region as needed
|
||||
bool didReset = nodeDB.resetRadioConfig(); // Don't let the phone send us fatally bad settings
|
||||
|
||||
configChanged.notifyObservers(NULL);
|
||||
nodeDB.saveToDisk();
|
||||
|
||||
@@ -208,7 +135,8 @@ bool MeshService::reloadConfig()
|
||||
/// The owner User record just got updated, update our node DB and broadcast the info into the mesh
|
||||
void MeshService::reloadOwner()
|
||||
{
|
||||
sendOurOwner();
|
||||
assert(nodeInfoPlugin);
|
||||
nodeInfoPlugin->sendOurNodeInfo();
|
||||
nodeDB.saveToDisk();
|
||||
}
|
||||
|
||||
@@ -219,8 +147,6 @@ void MeshService::reloadOwner()
|
||||
*/
|
||||
void MeshService::handleToRadio(MeshPacket &p)
|
||||
{
|
||||
handleIncomingPosition(&p); // If it is a position packet, perhaps set our clock
|
||||
|
||||
if (p.from == 0) // If the phone didn't set a sending node ID, use ours
|
||||
p.from = nodeDB.getNodeNum();
|
||||
|
||||
@@ -250,7 +176,8 @@ void MeshService::sendToMesh(MeshPacket *p)
|
||||
// Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other
|
||||
// nodes shouldn't trust it anyways) Note: we allow a device with a local GPS to include the time, so that gpsless
|
||||
// devices can get time.
|
||||
if (p->which_payload == MeshPacket_decoded_tag && p->decoded.which_payload == SubPacket_position_tag) {
|
||||
if (p->which_payload == MeshPacket_decoded_tag && p->decoded.which_payload == SubPacket_position_tag &&
|
||||
p->decoded.position.time) {
|
||||
if (getRTCQuality() < RTCQualityGPS) {
|
||||
DEBUG_MSG("Stripping time %u from position send\n", p->decoded.position.time);
|
||||
p->decoded.position.time = 0;
|
||||
@@ -268,37 +195,40 @@ void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
|
||||
assert(node);
|
||||
|
||||
DEBUG_MSG("Sending network ping to 0x%x, with position=%d, wantReplies=%d\n", dest, node->has_position, wantReplies);
|
||||
assert(positionPlugin && nodeInfoPlugin);
|
||||
if (node->has_position)
|
||||
sendOurPosition(dest, wantReplies);
|
||||
positionPlugin->sendOurPosition(dest, wantReplies);
|
||||
else
|
||||
sendOurOwner(dest, wantReplies);
|
||||
nodeInfoPlugin->sendOurNodeInfo(dest, wantReplies);
|
||||
}
|
||||
|
||||
void MeshService::sendOurPosition(NodeNum dest, bool wantReplies)
|
||||
{
|
||||
|
||||
NodeInfo *MeshService::refreshMyNodeInfo() {
|
||||
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
|
||||
assert(node);
|
||||
assert(node->has_position);
|
||||
|
||||
// We might not have a position yet for our local node, in that case, at least try to send the time
|
||||
if(!node->has_position) {
|
||||
memset(&node->position, 0, sizeof(node->position));
|
||||
node->has_position = true;
|
||||
}
|
||||
|
||||
Position &position = node->position;
|
||||
|
||||
// Update our local node info with our position (even if we don't decide to update anyone else)
|
||||
MeshPacket *p = router->allocForSending();
|
||||
p->to = dest;
|
||||
p->decoded.which_payload = SubPacket_position_tag;
|
||||
p->decoded.position = node->position;
|
||||
p->decoded.want_response = wantReplies;
|
||||
p->decoded.position.time =
|
||||
getValidTime(RTCQualityGPS); // This nodedb timestamp might be stale, so update it if our clock is valid.
|
||||
sendToMesh(p);
|
||||
position.time = getValidTime(RTCQualityGPS); // This nodedb timestamp might be stale, so update it if our clock is valid.
|
||||
|
||||
position.battery_level = powerStatus->getBatteryChargePercent();
|
||||
updateBatteryLevel(position.battery_level);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
|
||||
{
|
||||
|
||||
// Update our local node info with our position (even if we don't decide to update anyone else)
|
||||
MeshPacket *p = router->allocForSending();
|
||||
p->decoded.which_payload = SubPacket_position_tag;
|
||||
|
||||
Position &pos = p->decoded.position;
|
||||
NodeInfo *node = refreshMyNodeInfo();
|
||||
Position pos = node->position;
|
||||
|
||||
if (gps->hasLock()) {
|
||||
if (gps->altitude != 0)
|
||||
@@ -306,28 +236,39 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
|
||||
pos.latitude_i = gps->latitude;
|
||||
pos.longitude_i = gps->longitude;
|
||||
}
|
||||
else {
|
||||
// The GPS has lost lock, if we are fixed position we should just keep using
|
||||
// the old position
|
||||
if(!radioConfig.preferences.fixed_position) {
|
||||
DEBUG_MSG("WARNING: Using fixed position\n");
|
||||
} else {
|
||||
// throw away old position
|
||||
pos.latitude_i = 0;
|
||||
pos.longitude_i = 0;
|
||||
pos.altitude = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pos.time = getValidTime(RTCQualityGPS);
|
||||
DEBUG_MSG("got gps notify time=%u, lat=%d, bat=%d\n", pos.latitude_i, pos.time, pos.battery_level);
|
||||
|
||||
// Include our current battery voltage in our position announcement
|
||||
pos.battery_level = powerStatus->getBatteryChargePercent();
|
||||
updateBatteryLevel(pos.battery_level);
|
||||
|
||||
// DEBUG_MSG("got gps notify time=%u, lat=%d, bat=%d\n", pos.latitude_i, pos.time, pos.battery_level);
|
||||
// Update our current position in the local DB
|
||||
nodeDB.updatePosition(nodeDB.getNodeNum(), pos);
|
||||
|
||||
// We limit our GPS broadcasts to a max rate
|
||||
static uint32_t lastGpsSend;
|
||||
uint32_t now = millis();
|
||||
if (lastGpsSend == 0 || now - lastGpsSend > getPref_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);
|
||||
static uint32_t currentGeneration;
|
||||
|
||||
releaseToPool(p);
|
||||
// If we changed channels, ask everyone else for their latest info
|
||||
bool requestReplies = currentGeneration != radioGeneration;
|
||||
currentGeneration = radioGeneration;
|
||||
|
||||
DEBUG_MSG("Sending position to mesh (wantReplies=%d)\n", requestReplies);
|
||||
assert(positionPlugin);
|
||||
positionPlugin->sendOurPosition(NODENUM_BROADCAST, requestReplies);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "MeshTypes.h"
|
||||
#include "Observer.h"
|
||||
#include "PointerQueue.h"
|
||||
#include "mesh.pb.h"
|
||||
|
||||
/**
|
||||
* Top level app for this service. keeps the mesh, the radio config and the queue of received packets.
|
||||
@@ -75,18 +74,16 @@ class MeshService
|
||||
/// sends our owner
|
||||
void sendNetworkPing(NodeNum dest, bool wantReplies = false);
|
||||
|
||||
/// Send our owner info to a particular node
|
||||
void sendOurOwner(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
|
||||
|
||||
private:
|
||||
/// Broadcasts our last known position
|
||||
void sendOurPosition(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
|
||||
|
||||
/// Send a packet into the mesh - note p must have been allocated from packetPool. We will return it to that pool after
|
||||
/// sending. This is the ONLY function you should use for sending messages into the mesh, because it also updates the nodedb
|
||||
/// cache
|
||||
void sendToMesh(MeshPacket *p);
|
||||
|
||||
/// Pull the latest power and time info into my nodeinfo
|
||||
NodeInfo *refreshMyNodeInfo();
|
||||
|
||||
private:
|
||||
|
||||
/// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh
|
||||
/// returns 0 to allow futher processing
|
||||
int onGPSChanged(const meshtastic::GPSStatus *arg);
|
||||
@@ -94,12 +91,6 @@ class MeshService
|
||||
/// Handle a packet that just arrived from the radio. This method does _not_ free the provided packet. If it needs
|
||||
/// to keep the packet around it makes a copy
|
||||
int handleFromRadio(const MeshPacket *p);
|
||||
|
||||
/// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all
|
||||
const MeshPacket *handleFromRadioUser(const MeshPacket *mp);
|
||||
|
||||
/// look at inbound packets and if they contain a position with time, possibly set our clock
|
||||
void handleIncomingPosition(const MeshPacket *mp);
|
||||
};
|
||||
|
||||
extern MeshService service;
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
// low level types
|
||||
|
||||
#include "MemoryPool.h"
|
||||
#include "mesh.pb.h"
|
||||
#include "mesh/mesh-pb-constants.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
typedef uint32_t NodeNum;
|
||||
typedef uint32_t PacketId; // A packet sequence number
|
||||
|
||||
#define NODENUM_BROADCAST (sizeof(NodeNum) == 4 ? UINT32_MAX : UINT8_MAX)
|
||||
#define NODENUM_BROADCAST UINT32_MAX
|
||||
#define ERRNO_OK 0
|
||||
#define ERRNO_NO_INTERFACES 33
|
||||
#define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "FS.h"
|
||||
|
||||
#include "CryptoEngine.h"
|
||||
#include "FSCommon.h"
|
||||
#include "GPS.h"
|
||||
#include "MeshRadio.h"
|
||||
#include "NodeDB.h"
|
||||
@@ -15,10 +16,13 @@
|
||||
#include "configuration.h"
|
||||
#include "error.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "meshwifi/meshwifi.h"
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
|
||||
#ifndef NO_ESP32
|
||||
#include "mesh/http/WiFiAPClient.h"
|
||||
#endif
|
||||
|
||||
NodeDB nodeDB;
|
||||
|
||||
// we have plenty of ram so statically alloc this tempbuf (for now)
|
||||
@@ -27,6 +31,11 @@ MyNodeInfo &myNodeInfo = devicestate.my_node;
|
||||
RadioConfig &radioConfig = devicestate.radio;
|
||||
ChannelSettings &channelSettings = radioConfig.channel_settings;
|
||||
|
||||
/** The current change # for radio settings. Starts at 0 on boot and any time the radio settings
|
||||
* might have changed is incremented. Allows others to detect they might now be on a new channel.
|
||||
*/
|
||||
uint32_t radioGeneration;
|
||||
|
||||
/*
|
||||
DeviceState versions used to be defined in the .proto file but really only this function cares. So changed to a
|
||||
#define here.
|
||||
@@ -35,28 +44,6 @@ DeviceState versions used to be defined in the .proto file but really only this
|
||||
#define DEVICESTATE_CUR_VER 11
|
||||
#define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER
|
||||
|
||||
#ifdef PORTDUINO
|
||||
// Portduino version
|
||||
#include "PortduinoFS.h"
|
||||
#define FS PortduinoFS
|
||||
#define FSBegin() true
|
||||
#define FILE_O_WRITE "w"
|
||||
#define FILE_O_READ "r"
|
||||
#elif !defined(NO_ESP32)
|
||||
// ESP32 version
|
||||
#include "SPIFFS.h"
|
||||
#define FS SPIFFS
|
||||
#define FSBegin() FS.begin(true)
|
||||
#define FILE_O_WRITE "w"
|
||||
#define FILE_O_READ "r"
|
||||
#else
|
||||
// NRF52 version
|
||||
#include "InternalFileSystem.h"
|
||||
#define FS InternalFS
|
||||
#define FSBegin() FS.begin()
|
||||
using namespace Adafruit_LittleFS_Namespace;
|
||||
#endif
|
||||
|
||||
// FIXME - move this somewhere else
|
||||
extern void getMacAddr(uint8_t *dmac);
|
||||
|
||||
@@ -76,16 +63,24 @@ static uint8_t ourMacAddr[6];
|
||||
*/
|
||||
NodeNum displayedNodeNum;
|
||||
|
||||
/// A usable (but bigger) version of the channel name in the channelSettings object
|
||||
const char *channelName;
|
||||
|
||||
/// A usable psk - which has been constructed based on the (possibly short psk) in channelSettings
|
||||
static uint8_t activePSK[32];
|
||||
static uint8_t activePSKSize;
|
||||
|
||||
/**
|
||||
* Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different PSKs.
|
||||
* The ideas is that the PSK changing should be visible to the user so that they see they probably messed up and that's why they
|
||||
their nodes
|
||||
* aren't talking to each other.
|
||||
*
|
||||
* This string is of the form "#name-XY".
|
||||
* This string is of the form "#name-X".
|
||||
*
|
||||
* Where X is a letter from A to Z (base26), and formed by xoring all the bytes of the PSK together.
|
||||
* Y is not yet used but should eventually indicate 'speed/range' of the link
|
||||
* Where X is either:
|
||||
* (for custom PSKS) a letter from A to Z (base26), and formed by xoring all the bytes of the PSK together,
|
||||
* OR (for the standard minimially secure PSKs) a number from 0 to 9.
|
||||
*
|
||||
* This function will also need to be implemented in GUI apps that talk to the radio.
|
||||
*
|
||||
@@ -95,11 +90,19 @@ const char *getChannelName()
|
||||
{
|
||||
static char buf[32];
|
||||
|
||||
uint8_t code = 0;
|
||||
for (int i = 0; i < channelSettings.psk.size; i++)
|
||||
code ^= channelSettings.psk.bytes[i];
|
||||
char suffix;
|
||||
if (channelSettings.psk.size != 1) {
|
||||
// We have a standard PSK, so generate a letter based hash.
|
||||
uint8_t code = 0;
|
||||
for (int i = 0; i < activePSKSize; i++)
|
||||
code ^= activePSK[i];
|
||||
|
||||
snprintf(buf, sizeof(buf), "#%s-%c", channelSettings.name, 'A' + (code % 26));
|
||||
suffix = 'A' + (code % 26);
|
||||
} else {
|
||||
suffix = '0' + channelSettings.psk.bytes[0];
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "#%s-%c", channelName, suffix);
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -109,6 +112,8 @@ bool NodeDB::resetRadioConfig()
|
||||
{
|
||||
bool didFactoryReset = false;
|
||||
|
||||
radioGeneration++;
|
||||
|
||||
/// 16 bytes of random PSK for our _public_ default channel that all devices power up on (AES128)
|
||||
static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59,
|
||||
0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf};
|
||||
@@ -129,13 +134,70 @@ bool NodeDB::resetRadioConfig()
|
||||
channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range
|
||||
|
||||
channelSettings.tx_power = 0; // default
|
||||
memcpy(&channelSettings.psk.bytes, defaultpsk, sizeof(channelSettings.psk));
|
||||
channelSettings.psk.size = sizeof(defaultpsk);
|
||||
strcpy(channelSettings.name, "Default");
|
||||
uint8_t defaultpskIndex = 1;
|
||||
channelSettings.psk.bytes[0] = defaultpskIndex;
|
||||
channelSettings.psk.size = 1;
|
||||
strcpy(channelSettings.name, "");
|
||||
}
|
||||
|
||||
// Convert the old string "Default" to our new short representation
|
||||
if (strcmp(channelSettings.name, "Default") == 0)
|
||||
*channelSettings.name = '\0';
|
||||
|
||||
// Convert the short "" representation for Default into a usable string
|
||||
channelName = channelSettings.name;
|
||||
if (!*channelName) { // emptystring
|
||||
// Per mesh.proto spec, if bandwidth is specified we must ignore modemConfig enum, we assume that in that case
|
||||
// the app fucked up and forgot to set channelSettings.name
|
||||
|
||||
if (channelSettings.bandwidth != 0)
|
||||
channelName = "Unset";
|
||||
else
|
||||
switch (channelSettings.modem_config) {
|
||||
case ChannelSettings_ModemConfig_Bw125Cr45Sf128:
|
||||
channelName = "Medium";
|
||||
break;
|
||||
case ChannelSettings_ModemConfig_Bw500Cr45Sf128:
|
||||
channelName = "ShortFast";
|
||||
break;
|
||||
case ChannelSettings_ModemConfig_Bw31_25Cr48Sf512:
|
||||
channelName = "LongAlt";
|
||||
break;
|
||||
case ChannelSettings_ModemConfig_Bw125Cr48Sf4096:
|
||||
channelName = "LongSlow";
|
||||
break;
|
||||
default:
|
||||
channelName = "Invalid";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert any old usage of the defaultpsk into our new short representation.
|
||||
if (channelSettings.psk.size == sizeof(defaultpsk) &&
|
||||
memcmp(channelSettings.psk.bytes, defaultpsk, sizeof(defaultpsk)) == 0) {
|
||||
*channelSettings.psk.bytes = 1;
|
||||
channelSettings.psk.size = 1;
|
||||
}
|
||||
|
||||
// Convert the short single byte variants of psk into variant that can be used more generally
|
||||
memcpy(activePSK, channelSettings.psk.bytes, channelSettings.psk.size);
|
||||
activePSKSize = channelSettings.psk.size;
|
||||
if (activePSKSize == 1) {
|
||||
uint8_t pskIndex = activePSK[0];
|
||||
DEBUG_MSG("Expanding short PSK #%d\n", pskIndex);
|
||||
if (pskIndex == 0)
|
||||
activePSKSize = 0; // Turn off encryption
|
||||
else {
|
||||
memcpy(activePSK, defaultpsk, sizeof(defaultpsk));
|
||||
activePSKSize = sizeof(defaultpsk);
|
||||
// Bump up the last byte of PSK as needed
|
||||
uint8_t *last = activePSK + sizeof(defaultpsk) - 1;
|
||||
*last = *last + pskIndex - 1; // index of 1 means no change vs defaultPSK
|
||||
}
|
||||
}
|
||||
|
||||
// Tell our crypto engine about the psk
|
||||
crypto->setKey(channelSettings.psk.size, channelSettings.psk.bytes);
|
||||
crypto->setKey(activePSKSize, activePSK);
|
||||
|
||||
// temp hack for quicker testing
|
||||
// devicestate.no_save = true;
|
||||
@@ -143,12 +205,20 @@ bool NodeDB::resetRadioConfig()
|
||||
DEBUG_MSG("***** DEVELOPMENT MODE - DO NOT RELEASE *****\n");
|
||||
|
||||
// Sleep quite frequently to stress test the BLE comms, broadcast position every 6 mins
|
||||
radioConfig.preferences.screen_on_secs = 30;
|
||||
radioConfig.preferences.wait_bluetooth_secs = 30;
|
||||
radioConfig.preferences.screen_on_secs = 10;
|
||||
radioConfig.preferences.wait_bluetooth_secs = 10;
|
||||
radioConfig.preferences.position_broadcast_secs = 6 * 60;
|
||||
radioConfig.preferences.ls_secs = 60;
|
||||
radioConfig.preferences.region = RegionCode_TW;
|
||||
|
||||
// Enter super deep sleep soon and stay there not very long
|
||||
// radioConfig.preferences.mesh_sds_timeout_secs = 10;
|
||||
// radioConfig.preferences.sds_secs = 60;
|
||||
}
|
||||
|
||||
// Update the global myRegion
|
||||
initRegion();
|
||||
|
||||
return didFactoryReset;
|
||||
}
|
||||
|
||||
@@ -176,7 +246,6 @@ void NodeDB::installDefaultDeviceState()
|
||||
// default to no GPS, until one has been found by probing
|
||||
myNodeInfo.has_gps = false;
|
||||
myNodeInfo.message_timeout_msec = FLOOD_EXPIRE_TIME;
|
||||
myNodeInfo.min_app_version = 172;
|
||||
generatePacketId(); // FIXME - ugly way to init current_packet_id;
|
||||
|
||||
// Init our blank owner info to reasonable defaults
|
||||
@@ -202,12 +271,6 @@ void NodeDB::init()
|
||||
{
|
||||
installDefaultDeviceState();
|
||||
|
||||
if (!FSBegin()) // FIXME - do this in main?
|
||||
{
|
||||
DEBUG_MSG("ERROR filesystem mount Failed\n");
|
||||
assert(0); // FIXME - report failure to phone
|
||||
}
|
||||
|
||||
// saveToDisk();
|
||||
loadFromDisk();
|
||||
// saveToDisk();
|
||||
@@ -217,6 +280,13 @@ void NodeDB::init()
|
||||
myNodeInfo.node_num_bits = sizeof(NodeNum) * 8;
|
||||
myNodeInfo.packet_id_bits = sizeof(PacketId) * 8;
|
||||
|
||||
myNodeInfo.error_code =
|
||||
CriticalErrorCode_None; // For the error code, only show values from this boot (discard value from flash)
|
||||
myNodeInfo.error_address = 0;
|
||||
|
||||
// likewise - we always want the app requirements to come from the running appload
|
||||
myNodeInfo.min_app_version = 20120; // format is Mmmss (where M is 1+the numeric major number. i.e. 20120 means 1.1.20
|
||||
|
||||
// Note! We do this after loading saved settings, so that if somehow an invalid nodenum was stored in preferences we won't
|
||||
// keep using that nodenum forever. Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts)
|
||||
pickNewNodeNum();
|
||||
@@ -265,8 +335,7 @@ void NodeDB::pickNewNodeNum()
|
||||
|
||||
// If we don't have a nodenum at app - pick an initial nodenum based on the macaddr
|
||||
if (r == 0)
|
||||
r = sizeof(NodeNum) == 1 ? ourMacAddr[5]
|
||||
: ((ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5]);
|
||||
r = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5];
|
||||
|
||||
if (r == NODENUM_BROADCAST || r < NUM_RESERVED)
|
||||
r = NUM_RESERVED; // don't pick a reserved node number
|
||||
@@ -396,6 +465,48 @@ size_t NodeDB::getNumOnlineNodes()
|
||||
return numseen;
|
||||
}
|
||||
|
||||
#include "MeshPlugin.h"
|
||||
|
||||
/** Update position info for this node based on received position data
|
||||
*/
|
||||
void NodeDB::updatePosition(uint32_t nodeId, const Position &p)
|
||||
{
|
||||
NodeInfo *info = getOrCreateNode(nodeId);
|
||||
|
||||
DEBUG_MSG("DB update position node=0x%x time=%u, latI=%d, lonI=%d\n", nodeId, p.time, p.latitude_i, p.longitude_i);
|
||||
|
||||
info->position = p;
|
||||
info->has_position = true;
|
||||
updateGUIforNode = info;
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
}
|
||||
|
||||
/** Update user info for this node based on received user data
|
||||
*/
|
||||
void NodeDB::updateUser(uint32_t nodeId, const User &p)
|
||||
{
|
||||
NodeInfo *info = getOrCreateNode(nodeId);
|
||||
|
||||
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,
|
||||
sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay
|
||||
|
||||
info->user = p;
|
||||
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);
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
|
||||
// Not really needed - we will save anyways when we go to sleep
|
||||
// We just changed something important about the user, store our DB
|
||||
// saveToDisk();
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
@@ -415,58 +526,21 @@ void NodeDB::updateFrom(const MeshPacket &mp)
|
||||
|
||||
switch (p.which_payload) {
|
||||
case SubPacket_position_tag: {
|
||||
// we always trust our local timestamps more
|
||||
info->position = p.position;
|
||||
if (mp.rx_time)
|
||||
info->position.time = mp.rx_time;
|
||||
info->has_position = true;
|
||||
updateGUIforNode = info;
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
// handle a legacy position packet
|
||||
DEBUG_MSG("WARNING: Processing a (deprecated) position packet from %d\n", mp.from);
|
||||
updatePosition(mp.from, p.position);
|
||||
break;
|
||||
}
|
||||
|
||||
case SubPacket_data_tag: {
|
||||
// Keep a copy of the most recent text message.
|
||||
if (p.data.typ == Data_Type_CLEAR_TEXT) {
|
||||
DEBUG_MSG("Received text msg from=0x%0x, id=%d, msg=%.*s\n", mp.from, mp.id, p.data.payload.size,
|
||||
p.data.payload.bytes);
|
||||
if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum()) {
|
||||
// We only store/display messages destined for us.
|
||||
devicestate.rx_text_message = mp;
|
||||
devicestate.has_rx_text_message = true;
|
||||
updateTextMessage = true;
|
||||
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG);
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
|
||||
// This is going into the wifidev feature branch
|
||||
// Only update the WebUI if WiFi is enabled
|
||||
//#if WiFi_MODE != 0
|
||||
// notifyWebUI();
|
||||
//#endif
|
||||
}
|
||||
}
|
||||
if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum())
|
||||
MeshPlugin::callPlugins(mp);
|
||||
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.user,
|
||||
sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay
|
||||
|
||||
info->user = p.user;
|
||||
DEBUG_MSG("updating changed=%d user %s/%s/%s\n", changed, info->user.id, info->user.long_name, info->user.short_name);
|
||||
info->has_user = true;
|
||||
|
||||
if (changed) {
|
||||
updateGUIforNode = info;
|
||||
powerFSM.trigger(EVENT_NODEDB_UPDATED);
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
|
||||
// Not really needed - we will save anyways when we go to sleep
|
||||
// We just changed something important about the user, store our DB
|
||||
// saveToDisk();
|
||||
}
|
||||
DEBUG_MSG("WARNING: Processing a (deprecated) user packet from %d\n", mp.from);
|
||||
updateUser(mp.from, p.user);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ extern MyNodeInfo &myNodeInfo;
|
||||
extern RadioConfig &radioConfig;
|
||||
extern ChannelSettings &channelSettings;
|
||||
extern User &owner;
|
||||
extern const char *channelName;
|
||||
|
||||
/// Given a node, return how many seconds in the past (vs now) that we last heard from it
|
||||
uint32_t sinceLastSeen(const NodeInfo *n);
|
||||
@@ -33,7 +34,6 @@ class NodeDB
|
||||
public:
|
||||
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
|
||||
NodeInfo *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
|
||||
bool updateTextMessage = false; // if true, the GUI should show a new text message
|
||||
Observable<const meshtastic::NodeStatus *> newStatus;
|
||||
|
||||
/// don't do mesh based algoritm for node id assignment (initially)
|
||||
@@ -58,6 +58,14 @@ class NodeDB
|
||||
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
|
||||
void updateFrom(const MeshPacket &p);
|
||||
|
||||
/** Update position info for this node based on received position data
|
||||
*/
|
||||
void updatePosition(uint32_t nodeId, const Position &p);
|
||||
|
||||
/** Update user info for this node based on received user data
|
||||
*/
|
||||
void updateUser(uint32_t nodeId, const User &p);
|
||||
|
||||
/// @return our node number
|
||||
NodeNum getNodeNum() { return myNodeInfo.my_node_num; }
|
||||
|
||||
@@ -139,16 +147,58 @@ their nodes
|
||||
*/
|
||||
const char *getChannelName();
|
||||
|
||||
/*
|
||||
If is_router is set, we use a number of different default values
|
||||
|
||||
# FIXME - after tuning, move these params into the on-device defaults based on is_router and is_low_power
|
||||
|
||||
# prefs.position_broadcast_secs = FIXME possibly broadcast only once an hr
|
||||
prefs.wait_bluetooth_secs = 1 # Don't stay in bluetooth mode
|
||||
prefs.mesh_sds_timeout_secs = never
|
||||
prefs.phone_sds_timeout_sec = never
|
||||
# try to stay in light sleep one full day, then briefly wake and sleep again
|
||||
|
||||
prefs.ls_secs = oneday
|
||||
|
||||
prefs.send_owner_interval = 2 # Send an owner packet every other network ping
|
||||
prefs.position_broadcast_secs = 12 hours # send either position or owner every 12hrs
|
||||
|
||||
# get a new GPS position once per day
|
||||
prefs.gps_update_interval = oneday
|
||||
|
||||
prefs.is_low_power = True
|
||||
|
||||
# allow up to five minutes for each new GPS lock attempt
|
||||
prefs.gps_attempt_time = 300
|
||||
*/
|
||||
|
||||
// Our delay functions check for this for times that should never expire
|
||||
#define DELAY_FOREVER 0xffffffff
|
||||
|
||||
#define IF_ROUTER(routerVal, normalVal) (radioConfig.preferences.is_router ? (routerVal) : (normalVal))
|
||||
|
||||
#define PREF_GET(name, defaultVal) \
|
||||
inline uint32_t getPref_##name() { return radioConfig.preferences.name ? radioConfig.preferences.name : (defaultVal); }
|
||||
|
||||
PREF_GET(send_owner_interval, 4)
|
||||
PREF_GET(position_broadcast_secs, 15 * 60)
|
||||
PREF_GET(wait_bluetooth_secs, 120)
|
||||
PREF_GET(send_owner_interval, IF_ROUTER(2, 4))
|
||||
PREF_GET(position_broadcast_secs, IF_ROUTER(12 * 60 * 60, 15 * 60))
|
||||
|
||||
// Each time we wake into the DARK state allow 1 minute to send and receive BLE packets to the phone
|
||||
PREF_GET(wait_bluetooth_secs, IF_ROUTER(1, 60))
|
||||
|
||||
PREF_GET(screen_on_secs, 60)
|
||||
PREF_GET(mesh_sds_timeout_secs, 2 * 60 * 60)
|
||||
PREF_GET(phone_sds_timeout_sec, 2 * 60 * 60)
|
||||
PREF_GET(mesh_sds_timeout_secs, IF_ROUTER(DELAY_FOREVER, 2 * 60 * 60))
|
||||
PREF_GET(phone_sds_timeout_sec, IF_ROUTER(DELAY_FOREVER, 2 * 60 * 60))
|
||||
PREF_GET(sds_secs, 365 * 24 * 60 * 60)
|
||||
PREF_GET(ls_secs, 60 * 60)
|
||||
|
||||
// We default to sleeping (with bluetooth off for 5 minutes at a time). This seems to be a good tradeoff between
|
||||
// latency for the user sending messages and power savings because of not having to run (expensive) ESP32 bluetooth
|
||||
PREF_GET(ls_secs, IF_ROUTER(24 * 60 * 60, 5 * 60))
|
||||
|
||||
PREF_GET(phone_timeout_secs, 15 * 60)
|
||||
PREF_GET(min_wake_secs, 10)
|
||||
|
||||
/** The current change # for radio settings. Starts at 0 on boot and any time the radio settings
|
||||
* might have changed is incremented. Allows others to detect they might now be on a new channel.
|
||||
*/
|
||||
extern uint32_t radioGeneration;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user