mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-14 14:52:32 +00:00
Compare commits
758 Commits
arduino-es
...
94aedff6ae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94aedff6ae | ||
|
|
2ae391197f | ||
|
|
2a17c3b5d4 | ||
|
|
8060134224 | ||
|
|
2f4eb25b2f | ||
|
|
aa85fbbcc4 | ||
|
|
61e41a8beb | ||
|
|
525c048354 | ||
|
|
41cbd77db3 | ||
|
|
f3e38a425f | ||
|
|
a11152e545 | ||
|
|
859ae4d3d2 | ||
|
|
03600b1252 | ||
|
|
a3d3e1c912 | ||
|
|
0e653056e7 | ||
|
|
eba6e4ed75 | ||
|
|
80e8745714 | ||
|
|
ee6c9101c7 | ||
|
|
34f8300288 | ||
|
|
09bbfce625 | ||
|
|
5b1b420cad | ||
|
|
8899487c2f | ||
|
|
430d55e5e8 | ||
|
|
5ef3ff7116 | ||
|
|
1abf8ddb30 | ||
|
|
5a595a3ae7 | ||
|
|
bcd4a1176a | ||
|
|
0081cec207 | ||
|
|
94db3506bd | ||
|
|
2f0fe4e5da | ||
|
|
a59723030a | ||
|
|
de26dfe468 | ||
|
|
a6cdf2c50b | ||
|
|
1c43d71067 | ||
|
|
1523368c53 | ||
|
|
bc3ed4a7f3 | ||
|
|
7cb7a6cd3e | ||
|
|
d0c6ec28db | ||
|
|
a6d1ce2048 | ||
|
|
f7ae7aa2c1 | ||
|
|
9bfef80e30 | ||
|
|
c3a7ad2865 | ||
|
|
f10aa3daa2 | ||
|
|
06dac12a73 | ||
|
|
d60b263a00 | ||
|
|
654abe5b2c | ||
|
|
79e8fc94bc | ||
|
|
486fa74549 | ||
|
|
66193e1776 | ||
|
|
bacff5c1f0 | ||
|
|
faa6af74af | ||
|
|
81439f16d0 | ||
|
|
592a8f23db | ||
|
|
2baa9ccbe0 | ||
|
|
0336331411 | ||
|
|
ed4a798c60 | ||
|
|
5d7da6868e | ||
|
|
1bfa9ed4c4 | ||
|
|
b18794e98d | ||
|
|
f4e260e0f1 | ||
|
|
14463043bd | ||
|
|
376dc7ef3a | ||
|
|
c051c56544 | ||
|
|
d3976e7461 | ||
|
|
a4c92d9fd5 | ||
|
|
186cbe61bb | ||
|
|
0e3e8b7607 | ||
|
|
451e52b541 | ||
|
|
d743ba8e75 | ||
|
|
626dce8323 | ||
|
|
0100eeea67 | ||
|
|
5640179ce2 | ||
|
|
066da492d5 | ||
|
|
2b8806486d | ||
|
|
9ae545918c | ||
|
|
5374291c3c | ||
|
|
38b0c1588a | ||
|
|
f329de04c4 | ||
|
|
b09fa31492 | ||
|
|
a2a0150ee8 | ||
|
|
9ae92724a9 | ||
|
|
9cf369c5d0 | ||
|
|
441a7c5b20 | ||
|
|
2ca03fbf4b | ||
|
|
ef298814f2 | ||
|
|
8d31fc5ec6 | ||
|
|
f9433a31d1 | ||
|
|
7232dddd69 | ||
|
|
10de230dba | ||
|
|
d18f3f7a65 | ||
|
|
567b8ea1c2 | ||
|
|
d39d1917ad | ||
|
|
b202559d37 | ||
|
|
85ea22ac38 | ||
|
|
15257b017c | ||
|
|
59864dd09d | ||
|
|
edcdb2dcb2 | ||
|
|
6c09cf9d3d | ||
|
|
ef4cb2abfb | ||
|
|
c34f94abda | ||
|
|
a8d1a90e16 | ||
|
|
501c296e75 | ||
|
|
79a91578b7 | ||
|
|
ec5e79585b | ||
|
|
438e170b03 | ||
|
|
17cd83085b | ||
|
|
43e0c35466 | ||
|
|
b7bdcbe43e | ||
|
|
df063f40ff | ||
|
|
6e3be132f2 | ||
|
|
84bb1e33a6 | ||
|
|
955347bf50 | ||
|
|
4284fc2aec | ||
|
|
034aaa376a | ||
|
|
0aa11d810c | ||
|
|
4df6627ab1 | ||
|
|
7212fb9caa | ||
|
|
4118e1c0f6 | ||
|
|
a62fed3289 | ||
|
|
e9590003f4 | ||
|
|
7d2744fae0 | ||
|
|
beaebda4de | ||
|
|
36c2178570 | ||
|
|
1c0c6b2736 | ||
|
|
602945f66b | ||
|
|
50f9be9a2b | ||
|
|
b86827967e | ||
|
|
8fe98db5dd | ||
|
|
531cad5e88 | ||
|
|
b707001873 | ||
|
|
85afd706fd | ||
|
|
e76013fb60 | ||
|
|
b25797e1b3 | ||
|
|
bdb3fb1477 | ||
|
|
7eca061f01 | ||
|
|
77e0a24838 | ||
|
|
6cad393688 | ||
|
|
0725b46744 | ||
|
|
4d86bbafe6 | ||
|
|
112b294ef6 | ||
|
|
5ba04ade2d | ||
|
|
6a6c409b9a | ||
|
|
69db3bd11c | ||
|
|
7b14b173d9 | ||
|
|
45bf2468a9 | ||
|
|
ce2e08e0d8 | ||
|
|
3e40d7896b | ||
|
|
a579a9d011 | ||
|
|
6b55ec6603 | ||
|
|
f2400c9dc6 | ||
|
|
0a13bcaabf | ||
|
|
03f69b3b77 | ||
|
|
3ed831b8a3 | ||
|
|
83954293d8 | ||
|
|
91621427f1 | ||
|
|
cf716fe5ef | ||
|
|
8d1b9c9dce | ||
|
|
538c05ad6c | ||
|
|
f6370bea8f | ||
|
|
468247fb94 | ||
|
|
3ae7e54681 | ||
|
|
0a124b7f3d | ||
|
|
7def82595d | ||
|
|
597fa0b382 | ||
|
|
b5b9dc310f | ||
|
|
3a67204f6d | ||
|
|
d1b66782d1 | ||
|
|
a7796fc7b4 | ||
|
|
718fd118b0 | ||
|
|
75f7ded12c | ||
|
|
bca0e1abde | ||
|
|
c46abe125c | ||
|
|
7f78a624cd | ||
|
|
17324fa725 | ||
|
|
001654e90a | ||
|
|
16b1280804 | ||
|
|
d00fda2f4d | ||
|
|
4f817d69eb | ||
|
|
de83b448f9 | ||
|
|
c145be8e05 | ||
|
|
756efa7f00 | ||
|
|
0dfa11a909 | ||
|
|
c330bfe848 | ||
|
|
28f53d132a | ||
|
|
7d3e529b2f | ||
|
|
f045ca2303 | ||
|
|
b6830a68a0 | ||
|
|
dd51de85f3 | ||
|
|
580fa292ac | ||
|
|
664d17c519 | ||
|
|
13c4c2037d | ||
|
|
95d3ecb239 | ||
|
|
799cf0e8b3 | ||
|
|
35fa418739 | ||
|
|
585d9d36a8 | ||
|
|
b682ab3967 | ||
|
|
49b9d5151d | ||
|
|
3f8707cafe | ||
|
|
39780656ef | ||
|
|
153cf65214 | ||
|
|
07d354fa02 | ||
|
|
f4e93b4a2d | ||
|
|
18c4956aba | ||
|
|
15ee1c2819 | ||
|
|
f4ff210311 | ||
|
|
26747038bb | ||
|
|
8e082686a3 | ||
|
|
871986d183 | ||
|
|
821d8aa15d | ||
|
|
c4656dacf7 | ||
|
|
64d92679d0 | ||
|
|
b5aa16bade | ||
|
|
c740550d16 | ||
|
|
cb3ce1b1a8 | ||
|
|
2ad52812c0 | ||
|
|
5b9563a357 | ||
|
|
126954c2ed | ||
|
|
f6eede8597 | ||
|
|
6f2241751e | ||
|
|
1d4134c08c | ||
|
|
ffb168be00 | ||
|
|
27dc3be14a | ||
|
|
f2a63faddd | ||
|
|
d2403437ff | ||
|
|
05c176c16a | ||
|
|
7afc6ef833 | ||
|
|
68e739359f | ||
|
|
2357ea0042 | ||
|
|
cbdbaf62f1 | ||
|
|
e1c259ae32 | ||
|
|
b4dea63f44 | ||
|
|
30022c9377 | ||
|
|
0283e0658b | ||
|
|
4f142e6766 | ||
|
|
af8407aca9 | ||
|
|
e5d67310d6 | ||
|
|
47df9d4a79 | ||
|
|
4df79374b0 | ||
|
|
0bfc342b48 | ||
|
|
d9905f3c31 | ||
|
|
ee2ed0a8fb | ||
|
|
acab814b6f | ||
|
|
073c35c782 | ||
|
|
32ebc70bca | ||
|
|
5953b4704e | ||
|
|
a34c584028 | ||
|
|
865b46ceef | ||
|
|
cd3b31c5da | ||
|
|
51b3b937dc | ||
|
|
ec5a54c523 | ||
|
|
4e0a4cc45f | ||
|
|
858e8c6fef | ||
|
|
a6df18e60a | ||
|
|
9a8aeb25ab | ||
|
|
e8f4d07e9f | ||
|
|
034d2dd025 | ||
|
|
b8bfed2810 | ||
|
|
dbb439f121 | ||
|
|
c4d7ad2190 | ||
|
|
5814f3e7d2 | ||
|
|
910fe911f8 | ||
|
|
9ab9650248 | ||
|
|
37a0f774a2 | ||
|
|
a71b47b5bb | ||
|
|
9df5aa8c70 | ||
|
|
130833b5be | ||
|
|
fe6509a0f2 | ||
|
|
1212c2c11b | ||
|
|
fcaa168d2d | ||
|
|
e24e2ccf62 | ||
|
|
7537d28419 | ||
|
|
26f25069dd | ||
|
|
5eeffdb290 | ||
|
|
5d71776527 | ||
|
|
661e596dbb | ||
|
|
a6732682de | ||
|
|
f0126d44e2 | ||
|
|
fb08e17c39 | ||
|
|
11aff46af1 | ||
|
|
cb11e6b720 | ||
|
|
464663b496 | ||
|
|
981d058e9f | ||
|
|
9056915e7b | ||
|
|
554112ceb5 | ||
|
|
29458cd8c4 | ||
|
|
8bf32dc042 | ||
|
|
73cadce581 | ||
|
|
eee80ce636 | ||
|
|
30d6962e79 | ||
|
|
f99747180e | ||
|
|
91d928d4c5 | ||
|
|
64bfe73c06 | ||
|
|
fca5343460 | ||
|
|
cafb007ec4 | ||
|
|
694b669eb7 | ||
|
|
7899340131 | ||
|
|
0bb1c1fe6f | ||
|
|
05febc25e1 | ||
|
|
e5a2ce54e7 | ||
|
|
45f15b8fe6 | ||
|
|
2a14696525 | ||
|
|
05edcc5d6c | ||
|
|
828e11cc48 | ||
|
|
adae68fbfe | ||
|
|
7822f28152 | ||
|
|
d332dfa19b | ||
|
|
fe2e2753aa | ||
|
|
fcb1d64eb9 | ||
|
|
0c2673ee2f | ||
|
|
9c5513dcfe | ||
|
|
74e6723ad9 | ||
|
|
e8e8ee0993 | ||
|
|
a7f15097da | ||
|
|
e4ec719e6f | ||
|
|
9b7b8ffb21 | ||
|
|
f0e4ea7664 | ||
|
|
468b40e8db | ||
|
|
bd9076b740 | ||
|
|
81a5aeff74 | ||
|
|
f13e7c20ba | ||
|
|
5bcc47dddb | ||
|
|
668cc9fd64 | ||
|
|
1d5b343836 | ||
|
|
8023f475ee | ||
|
|
b696e083f3 | ||
|
|
b214f09ca1 | ||
|
|
68a2c4adda | ||
|
|
518680514f | ||
|
|
fc1737c949 | ||
|
|
735784e6e4 | ||
|
|
87e3540f48 | ||
|
|
329a494ce2 | ||
|
|
627c0145e7 | ||
|
|
29f4d99bf6 | ||
|
|
036a58735e | ||
|
|
18ca9e80d5 | ||
|
|
5c2997ef53 | ||
|
|
c147ce9a85 | ||
|
|
27f316b931 | ||
|
|
f7cf5e6b0a | ||
|
|
7c373b76c4 | ||
|
|
de6a02756d | ||
|
|
d708ed5908 | ||
|
|
7c4367cddc | ||
|
|
6022b749ba | ||
|
|
cbd30f95f3 | ||
|
|
9ded6a5215 | ||
|
|
1e4bcb04d5 | ||
|
|
c4dff21e5b | ||
|
|
888692a373 | ||
|
|
fe4fb085e6 | ||
|
|
7c5e2bc95a | ||
|
|
ed32650b9b | ||
|
|
1b97cf57ad | ||
|
|
e8296914a5 | ||
|
|
0e38fef5bf | ||
|
|
b7f6a2acb6 | ||
|
|
0c2283e19e | ||
|
|
78d010fd29 | ||
|
|
037e56b1fd | ||
|
|
c7208ca05b | ||
|
|
f72a4c50bd | ||
|
|
775595cb37 | ||
|
|
047600d088 | ||
|
|
560eb2c455 | ||
|
|
1be3820152 | ||
|
|
da98622f59 | ||
|
|
03baad2c11 | ||
|
|
0ddaf710e4 | ||
|
|
50cfe7c705 | ||
|
|
76c1d69560 | ||
|
|
17863e96e2 | ||
|
|
c48a64e183 | ||
|
|
e954591ca5 | ||
|
|
305f513834 | ||
|
|
0860fee209 | ||
|
|
878ac3ec84 | ||
|
|
a62e1cfa3c | ||
|
|
ca02808c5d | ||
|
|
b978c6c86c | ||
|
|
51ad9d0244 | ||
|
|
76d4807130 | ||
|
|
5ec09783c5 | ||
|
|
2eb0fcbcaf | ||
|
|
9bb7bb467b | ||
|
|
de67714248 | ||
|
|
e0cf9130be | ||
|
|
f82667d71e | ||
|
|
1d283523f2 | ||
|
|
ec28c383af | ||
|
|
641a2fc63d | ||
|
|
f7469159cf | ||
|
|
af83670376 | ||
|
|
849bbad279 | ||
|
|
b28d095096 | ||
|
|
17afdb9ccf | ||
|
|
d5164b4fbf | ||
|
|
ad44940732 | ||
|
|
8b466b1db3 | ||
|
|
f9937967fa | ||
|
|
4fd568f384 | ||
|
|
dae9b1c024 | ||
|
|
a8a6644192 | ||
|
|
34a595b88e | ||
|
|
e32ce3fafe | ||
|
|
69c61f8247 | ||
|
|
b08e4efb78 | ||
|
|
ee6857511a | ||
|
|
9df4d57168 | ||
|
|
500e7920ae | ||
|
|
ee8fa9f328 | ||
|
|
02efef3aaf | ||
|
|
0f6131d2c8 | ||
|
|
8d323a1cf1 | ||
|
|
a3e6f16378 | ||
|
|
85fe7d26ed | ||
|
|
a1c658a467 | ||
|
|
777e11bad9 | ||
|
|
a15d654767 | ||
|
|
033fc0c8f3 | ||
|
|
7633ddcfd1 | ||
|
|
8717c60f13 | ||
|
|
067939ca24 | ||
|
|
abc011aeb9 | ||
|
|
a4a6ee1df4 | ||
|
|
6448f069f8 | ||
|
|
f6a28e15d2 | ||
|
|
2b60bae61c | ||
|
|
bc516ebbac | ||
|
|
045176789e | ||
|
|
667b7c50e2 | ||
|
|
9b1a118103 | ||
|
|
64c268f055 | ||
|
|
806bf6ce2c | ||
|
|
7eb0109e33 | ||
|
|
4dec912a39 | ||
|
|
73147c4028 | ||
|
|
e8627b2d01 | ||
|
|
ab00e991f6 | ||
|
|
a2d86454d3 | ||
|
|
bc3db1b5c1 | ||
|
|
2f1198ddf3 | ||
|
|
0624059683 | ||
|
|
52ee655fd2 | ||
|
|
9980c56d81 | ||
|
|
fc9f1ac056 | ||
|
|
c65dbe490e | ||
|
|
44636cc9f5 | ||
|
|
aa876ba42f | ||
|
|
12c3ddf457 | ||
|
|
d9f0590f8e | ||
|
|
191d20ed04 | ||
|
|
79bc286b35 | ||
|
|
8d9fda38d6 | ||
|
|
3c25652cdf | ||
|
|
9b3d76967b | ||
|
|
fd5ca8b73c | ||
|
|
18058ef507 | ||
|
|
d9ba0633f6 | ||
|
|
68fc931518 | ||
|
|
0ad6b813fc | ||
|
|
d41fb7bcb5 | ||
|
|
fef4a2987d | ||
|
|
1fc8d54d4c | ||
|
|
47a82bdb98 | ||
|
|
8ed6514771 | ||
|
|
17ecd69416 | ||
|
|
bb6f19dddf | ||
|
|
2fdc0d0928 | ||
|
|
85cdcad194 | ||
|
|
0b4a28866b | ||
|
|
91e2e3f0e8 | ||
|
|
14e64d6b9e | ||
|
|
58602d59bd | ||
|
|
d461eb35fc | ||
|
|
371313080b | ||
|
|
db55d8a59d | ||
|
|
949f881ae8 | ||
|
|
ca3c45a2f3 | ||
|
|
c33c368315 | ||
|
|
1835ff2d78 | ||
|
|
8e04f9f631 | ||
|
|
83be632a1a | ||
|
|
1ed7aad976 | ||
|
|
edb5c0f88e | ||
|
|
060a129995 | ||
|
|
a1ca553bc0 | ||
|
|
189aec9fe3 | ||
|
|
94d4bdf05c | ||
|
|
1968a009dd | ||
|
|
8e608e8186 | ||
|
|
d998f70b56 | ||
|
|
f55db903b2 | ||
|
|
91efaba389 | ||
|
|
a8c66547cc | ||
|
|
f77ca2533b | ||
|
|
07b58a82d5 | ||
|
|
e1485b530f | ||
|
|
2fbfb19304 | ||
|
|
e7840122e8 | ||
|
|
a4e09aa9da | ||
|
|
db941bff3b | ||
|
|
13e1f99c7e | ||
|
|
319cd6fa7b | ||
|
|
0db2e40ee3 | ||
|
|
59f9e2a00b | ||
|
|
97d0f3286e | ||
|
|
388c821028 | ||
|
|
3d51287ba7 | ||
|
|
1e1f2a69b7 | ||
|
|
da4bc0f97c | ||
|
|
b3df32c6c5 | ||
|
|
cea9e1238b | ||
|
|
11eb4a5b90 | ||
|
|
d1fd102952 | ||
|
|
27b07cd1c5 | ||
|
|
5701755608 | ||
|
|
d09baddce5 | ||
|
|
c42513d7c8 | ||
|
|
2010871e4b | ||
|
|
c811e4c573 | ||
|
|
040b3b8c7f | ||
|
|
d558df8a3a | ||
|
|
4100ba83a3 | ||
|
|
b49496d99d | ||
|
|
34c2191f63 | ||
|
|
52527e281d | ||
|
|
9b6cf53730 | ||
|
|
6a3b2ceafe | ||
|
|
a76cc88dc2 | ||
|
|
3463006f73 | ||
|
|
db2f79b6c4 | ||
|
|
1d3c47c5fa | ||
|
|
44968415a5 | ||
|
|
8db9b24934 | ||
|
|
1fc07607cb | ||
|
|
22b71a1e95 | ||
|
|
2ccf91f443 | ||
|
|
58e4dcea61 | ||
|
|
6f56ccd283 | ||
|
|
bfb03b422a | ||
|
|
cc3ff1504a | ||
|
|
9b6a7ed3bb | ||
|
|
fdc8796052 | ||
|
|
787642ad4c | ||
|
|
54f9f7a591 | ||
|
|
0e26702c46 | ||
|
|
6677255f6c | ||
|
|
3a63a56cff | ||
|
|
edb250e782 | ||
|
|
f32e06a321 | ||
|
|
8095261dfd | ||
|
|
72b9a02f3e | ||
|
|
af26408d73 | ||
|
|
c8f69913d6 | ||
|
|
42fbb62f18 | ||
|
|
e6adb197e4 | ||
|
|
cfb34a8816 | ||
|
|
8264d4d65e | ||
|
|
c11680fcc0 | ||
|
|
479c1f5346 | ||
|
|
a1cf305336 | ||
|
|
7b2ff7e196 | ||
|
|
b1d314db1e | ||
|
|
cc579dd0bd | ||
|
|
1ac2382d7c | ||
|
|
2ef5b968f9 | ||
|
|
6a92358b68 | ||
|
|
e20a91b945 | ||
|
|
3fbe7fd8b2 | ||
|
|
8841c1540d | ||
|
|
e2ce369782 | ||
|
|
901bcc24ee | ||
|
|
b14e5770d5 | ||
|
|
68ba3b315c | ||
|
|
2bafac242e | ||
|
|
39648e609a | ||
|
|
dcd53eb7cb | ||
|
|
7821919fae | ||
|
|
f083864f1f | ||
|
|
8e1da8561e | ||
|
|
953fcca304 | ||
|
|
20bd237ff6 | ||
|
|
c73fe85ec8 | ||
|
|
b13d023d58 | ||
|
|
9345bdcb22 | ||
|
|
902405a985 | ||
|
|
ec29100a88 | ||
|
|
017d07e108 | ||
|
|
89cccdbbe2 | ||
|
|
8f0e17a653 | ||
|
|
e3772858b3 | ||
|
|
c71c1f2d15 | ||
|
|
2567c03a3f | ||
|
|
d8381aa905 | ||
|
|
188283b382 | ||
|
|
953fdc9eed | ||
|
|
ec7415b3fd | ||
|
|
a70ffae82c | ||
|
|
6a8732bbaa | ||
|
|
ba18467bd1 | ||
|
|
d427b477e3 | ||
|
|
46317f413a | ||
|
|
f16aa730d3 | ||
|
|
22fcd102a0 | ||
|
|
c9cb2cfd94 | ||
|
|
d31e3839fb | ||
|
|
43078a40eb | ||
|
|
4ac99c5df1 | ||
|
|
c9702fe4d0 | ||
|
|
e0f88be2d7 | ||
|
|
1c256ccfd7 | ||
|
|
b9d53d667e | ||
|
|
5d3c92f1a2 | ||
|
|
6c932d51ec | ||
|
|
f0b7aab030 | ||
|
|
5fca3a30ec | ||
|
|
a76f591231 | ||
|
|
20f68929c8 | ||
|
|
42e4759634 | ||
|
|
3d86c99c25 | ||
|
|
09de0e3edb | ||
|
|
00772996b6 | ||
|
|
de3a65579d | ||
|
|
90ddbf6f2c | ||
|
|
566c2c3fdf | ||
|
|
bfadd9c866 | ||
|
|
f8d44f8f6c | ||
|
|
ccff2769fe | ||
|
|
e6bfc4a97a | ||
|
|
a297d21707 | ||
|
|
a8cf4dfe2d | ||
|
|
ead67446a3 | ||
|
|
43cf12edfb | ||
|
|
106a052950 | ||
|
|
0fc33c352a | ||
|
|
e0890b2a13 | ||
|
|
5579d87845 | ||
|
|
35340fc6e2 | ||
|
|
4ab125bbf7 | ||
|
|
87eff2c4a9 | ||
|
|
527e88ca46 | ||
|
|
4140ecfb49 | ||
|
|
27cdd464d1 | ||
|
|
5a463373f2 | ||
|
|
b768860866 | ||
|
|
c63102a312 | ||
|
|
b1f55ef6e8 | ||
|
|
b305acf7e5 | ||
|
|
ab5332950c | ||
|
|
484b4cd848 | ||
|
|
3cc2b70e4f | ||
|
|
7e00054fd7 | ||
|
|
693181b2be | ||
|
|
39c663f203 | ||
|
|
71f659cba6 | ||
|
|
4e879a7b26 | ||
|
|
8c9c00172c | ||
|
|
d5300a1141 | ||
|
|
83ae72cbb2 | ||
|
|
a31fdf01ce | ||
|
|
1594421214 | ||
|
|
02cb306bb1 | ||
|
|
6b7ad9c4e1 | ||
|
|
fa1ccf4779 | ||
|
|
67ecb60bcd | ||
|
|
9da92626e5 | ||
|
|
6f7149e9a2 | ||
|
|
95dc61f57b | ||
|
|
0aa48c9c22 | ||
|
|
088318512a | ||
|
|
ca4b98f2b1 | ||
|
|
1643249db7 | ||
|
|
2191fe465c | ||
|
|
b75e8913e0 | ||
|
|
87a1449f76 | ||
|
|
569a911455 | ||
|
|
c5b95f5a4b | ||
|
|
15f4aebcd5 | ||
|
|
2354c52b16 | ||
|
|
227d0fa7dc | ||
|
|
7c1eff54fb | ||
|
|
c92fa6aa8a | ||
|
|
77acbc6814 | ||
|
|
81cb1e427f | ||
|
|
f6ba9604a7 | ||
|
|
9c6544ebfa | ||
|
|
b6eeccadeb | ||
|
|
e7b7479589 | ||
|
|
e1634076f2 | ||
|
|
d6df664102 | ||
|
|
50a5b36498 | ||
|
|
4d6fe936ae | ||
|
|
f825e61b89 | ||
|
|
64cd62d6af | ||
|
|
f31fd34ce0 | ||
|
|
26bcc9627d | ||
|
|
cc37535b2d | ||
|
|
ced334d13b | ||
|
|
521fbc44b4 | ||
|
|
361771c9bb | ||
|
|
fa45660b7d | ||
|
|
2e8f4ad6af | ||
|
|
18550ea80c | ||
|
|
1c1c0cc791 | ||
|
|
789c1ab59d | ||
|
|
5850a7cd6b | ||
|
|
6c89ea7cee | ||
|
|
c62f262f63 | ||
|
|
798040b5b8 | ||
|
|
ba582d6ef4 | ||
|
|
bbf6f01d42 | ||
|
|
142abb2a4e | ||
|
|
0952007805 | ||
|
|
7612799ef6 | ||
|
|
c5fad6cca1 | ||
|
|
b8d7222423 | ||
|
|
16d7de5989 | ||
|
|
102c447fe3 | ||
|
|
d66665b96e | ||
|
|
088be6bf6a | ||
|
|
ca79760372 | ||
|
|
4a669032dc | ||
|
|
b53dd2ec90 | ||
|
|
a0e14439cb | ||
|
|
10c6836263 | ||
|
|
9b41131af8 | ||
|
|
fb34dac08d | ||
|
|
5f8503c62d | ||
|
|
dd2f77ea0c | ||
|
|
46f797c40d | ||
|
|
75b01e17bc | ||
|
|
8685436cbb | ||
|
|
67e3a17b28 | ||
|
|
24204feb71 | ||
|
|
4ace2638e1 | ||
|
|
5aa486d6c2 | ||
|
|
ba26d03b1b | ||
|
|
9a1c2c9b61 | ||
|
|
5b9db81819 | ||
|
|
f2ba7d7851 | ||
|
|
1eafdfcbc8 | ||
|
|
103ea2f168 | ||
|
|
4fef890466 | ||
|
|
35f5b7ec03 | ||
|
|
1037fa5622 | ||
|
|
1c329d9ffa | ||
|
|
093a37a2b0 | ||
|
|
4dfcd61d46 | ||
|
|
9d560fe9e1 | ||
|
|
8e32d58077 | ||
|
|
7b24d31636 | ||
|
|
35d9e68053 | ||
|
|
caf2180075 | ||
|
|
236d2b92dc | ||
|
|
e6a2df5b6d | ||
|
|
f6bb1977bc | ||
|
|
9b0fbcf1d9 |
@@ -76,7 +76,7 @@ bool loopCanSleep()
|
|||||||
// Called just prior to starting Meshtastic. Allows for setting config values before startup.
|
// Called just prior to starting Meshtastic. Allows for setting config values before startup.
|
||||||
void lateInitVariant()
|
void lateInitVariant()
|
||||||
{
|
{
|
||||||
settingsMap[logoutputlevel] = level_error;
|
portduino_config.logoutputlevel = level_error;
|
||||||
channelFile.channels[0] = meshtastic_Channel{
|
channelFile.channels[0] = meshtastic_Channel{
|
||||||
.has_settings = true,
|
.has_settings = true,
|
||||||
.settings =
|
.settings =
|
||||||
@@ -132,7 +132,7 @@ int portduino_main(int argc, char **argv); // Renamed "main" function from Mesht
|
|||||||
// Start Meshtastic in a thread and wait till it has reached the ON state.
|
// Start Meshtastic in a thread and wait till it has reached the ON state.
|
||||||
int LLVMFuzzerInitialize(int *argc, char ***argv)
|
int LLVMFuzzerInitialize(int *argc, char ***argv)
|
||||||
{
|
{
|
||||||
settingsMap[maxtophone] = 5;
|
portduino_config.maxtophone = 5;
|
||||||
|
|
||||||
meshtasticThread = std::thread([program = *argv[0]]() {
|
meshtasticThread = std::thread([program = *argv[0]]() {
|
||||||
char nodeIdStr[12];
|
char nodeIdStr[12];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# trunk-ignore-all(terrascan/AC_DOCKER_0002): Known terrascan issue
|
# trunk-ignore-all(terrascan/AC_DOCKER_0002): Known terrascan issue
|
||||||
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
|
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
|
||||||
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
|
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
|
||||||
FROM mcr.microsoft.com/devcontainers/cpp:1-debian-12
|
FROM mcr.microsoft.com/devcontainers/cpp:2-debian-13
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/python:1": {
|
"ghcr.io/devcontainers/features/python:1": {
|
||||||
"installTools": true,
|
"installTools": true,
|
||||||
"version": "latest"
|
"version": "3.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"customizations": {
|
"customizations": {
|
||||||
|
|||||||
2
.github/actions/build-variant/action.yml
vendored
2
.github/actions/build-variant/action.yml
vendored
@@ -100,7 +100,7 @@ runs:
|
|||||||
id: version
|
id: version
|
||||||
|
|
||||||
- name: Store binaries as an artifact
|
- name: Store binaries as an artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
|
name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|||||||
2
.github/actions/setup-base/action.yml
vendored
2
.github/actions/setup-base/action.yml
vendored
@@ -5,7 +5,7 @@ runs:
|
|||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
|
|||||||
4
.github/workflows/build_debian_src.yml
vendored
4
.github/workflows/build_debian_src.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
path: meshtasticd
|
path: meshtasticd
|
||||||
@@ -64,7 +64,7 @@ jobs:
|
|||||||
PKG_VERSION: ${{ steps.version.outputs.deb }}
|
PKG_VERSION: ${{ steps.version.outputs.deb }}
|
||||||
|
|
||||||
- name: Store binaries as an artifact
|
- name: Store binaries as an artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|||||||
7
.github/workflows/build_firmware.yml
vendored
7
.github/workflows/build_firmware.yml
vendored
@@ -19,8 +19,10 @@ jobs:
|
|||||||
pio-build:
|
pio-build:
|
||||||
name: build-${{ inputs.platform }}
|
name: build-${{ inputs.platform }}
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
|
outputs:
|
||||||
|
artifact-id: ${{ steps.upload.outputs.artifact-id }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
@@ -54,7 +56,8 @@ jobs:
|
|||||||
ota_firmware_target: ${{ steps.ota_dir.outputs.tgt || '' }}
|
ota_firmware_target: ${{ steps.ota_dir.outputs.tgt || '' }}
|
||||||
|
|
||||||
- name: Store binaries as an artifact
|
- name: Store binaries as an artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
|
id: upload
|
||||||
with:
|
with:
|
||||||
name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}.zip
|
name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}.zip
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|||||||
161
.github/workflows/build_one_target.yml
vendored
Normal file
161
.github/workflows/build_one_target.yml
vendored
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
name: Build One Target
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
# trunk-ignore(checkov/CKV_GHA_7)
|
||||||
|
arch:
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- esp32
|
||||||
|
- esp32s3
|
||||||
|
- esp32c3
|
||||||
|
- esp32c6
|
||||||
|
- nrf52840
|
||||||
|
- rp2040
|
||||||
|
- rp2350
|
||||||
|
- stm32
|
||||||
|
target:
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets.
|
||||||
|
# find-target:
|
||||||
|
# type: boolean
|
||||||
|
# default: true
|
||||||
|
# description: 'Find the available targets'
|
||||||
|
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
find-targets:
|
||||||
|
if: ${{ inputs.target == '' }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
arch:
|
||||||
|
- esp32
|
||||||
|
- esp32s3
|
||||||
|
- esp32c3
|
||||||
|
- esp32c6
|
||||||
|
- nrf52840
|
||||||
|
- rp2040
|
||||||
|
- rp2350
|
||||||
|
- stm32
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: 3.x
|
||||||
|
cache: pip
|
||||||
|
- run: pip install -U platformio
|
||||||
|
- name: Generate matrix
|
||||||
|
id: jsonStep
|
||||||
|
run: |
|
||||||
|
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level extra)
|
||||||
|
echo "Name: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Base: $GITHUB_BASE_REF" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Targets:" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo $TARGETS | jq -r 'sort_by(.board) |.[] | "- " + .board' >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
version:
|
||||||
|
if: ${{ inputs.target != '' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- name: Get release version string
|
||||||
|
run: |
|
||||||
|
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||||
|
echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT
|
||||||
|
id: version
|
||||||
|
env:
|
||||||
|
BUILD_LOCATION: local
|
||||||
|
outputs:
|
||||||
|
long: ${{ steps.version.outputs.long }}
|
||||||
|
deb: ${{ steps.version.outputs.deb }}
|
||||||
|
|
||||||
|
build:
|
||||||
|
if: ${{ inputs.target != '' && inputs.arch != 'native' }}
|
||||||
|
needs: [version]
|
||||||
|
uses: ./.github/workflows/build_firmware.yml
|
||||||
|
with:
|
||||||
|
version: ${{ needs.version.outputs.long }}
|
||||||
|
pio_env: ${{ inputs.target }}
|
||||||
|
platform: ${{ inputs.arch }}
|
||||||
|
|
||||||
|
gather-artifacts:
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [version, build]
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v6
|
||||||
|
with:
|
||||||
|
path: ./
|
||||||
|
pattern: firmware-*-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Display structure of downloaded files
|
||||||
|
run: ls -R
|
||||||
|
|
||||||
|
- name: Move files up
|
||||||
|
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
|
||||||
|
|
||||||
|
- name: Repackage in single firmware zip
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
with:
|
||||||
|
name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
|
||||||
|
overwrite: true
|
||||||
|
path: |
|
||||||
|
./firmware-*.bin
|
||||||
|
./firmware-*.uf2
|
||||||
|
./firmware-*.hex
|
||||||
|
./firmware-*-ota.zip
|
||||||
|
./device-*.sh
|
||||||
|
./device-*.bat
|
||||||
|
./littlefs-*.bin
|
||||||
|
./bleota*bin
|
||||||
|
./Meshtastic_nRF52_factory_erase*.uf2
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v6
|
||||||
|
with:
|
||||||
|
pattern: firmware-*-${{ needs.version.outputs.long }}
|
||||||
|
merge-multiple: true
|
||||||
|
path: ./output
|
||||||
|
|
||||||
|
# For diagnostics
|
||||||
|
- name: Show artifacts
|
||||||
|
run: ls -lR
|
||||||
|
|
||||||
|
- name: Device scripts permissions
|
||||||
|
run: |
|
||||||
|
chmod +x ./output/device-install.sh
|
||||||
|
chmod +x ./output/device-update.sh
|
||||||
|
|
||||||
|
- name: Zip firmware
|
||||||
|
run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
|
||||||
|
|
||||||
|
- name: Repackage in single elfs zip
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
with:
|
||||||
|
name: debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
|
||||||
|
overwrite: true
|
||||||
|
path: ./*.elf
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- uses: scruplelesswizard/comment-artifact@main
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
with:
|
||||||
|
name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
|
||||||
|
description: "Download firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
8
.github/workflows/daily_packaging.yml
vendored
8
.github/workflows/daily_packaging.yml
vendored
@@ -21,18 +21,20 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker-multiarch:
|
docker-multiarch:
|
||||||
|
if: github.repository == 'meshtastic/firmware'
|
||||||
uses: ./.github/workflows/docker_manifest.yml
|
uses: ./.github/workflows/docker_manifest.yml
|
||||||
with:
|
with:
|
||||||
release_channel: daily
|
release_channel: daily
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
package-ppa:
|
package-ppa:
|
||||||
|
if: github.repository == 'meshtastic/firmware'
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
series:
|
series:
|
||||||
- jammy # 22.04
|
- jammy # 22.04 LTS
|
||||||
- noble # 24.04
|
- noble # 24.04 LTS
|
||||||
- plucky # 25.04
|
- plucky # 25.04
|
||||||
- questing # 25.10
|
- questing # 25.10
|
||||||
uses: ./.github/workflows/package_ppa.yml
|
uses: ./.github/workflows/package_ppa.yml
|
||||||
@@ -42,6 +44,7 @@ jobs:
|
|||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
package-obs:
|
package-obs:
|
||||||
|
if: github.repository == 'meshtastic/firmware'
|
||||||
uses: ./.github/workflows/package_obs.yml
|
uses: ./.github/workflows/package_obs.yml
|
||||||
with:
|
with:
|
||||||
obs_project: network:Meshtastic:daily
|
obs_project: network:Meshtastic:daily
|
||||||
@@ -49,6 +52,7 @@ jobs:
|
|||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
hook-copr:
|
hook-copr:
|
||||||
|
if: github.repository == 'meshtastic/firmware'
|
||||||
uses: ./.github/workflows/hook_copr.yml
|
uses: ./.github/workflows/hook_copr.yml
|
||||||
with:
|
with:
|
||||||
copr_project: daily
|
copr_project: daily
|
||||||
|
|||||||
2
.github/workflows/docker_build.yml
vendored
2
.github/workflows/docker_build.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
|||||||
runs-on: ${{ inputs.runs-on }}
|
runs-on: ${{ inputs.runs-on }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
|
|||||||
2
.github/workflows/docker_manifest.yml
vendored
2
.github/workflows/docker_manifest.yml
vendored
@@ -83,7 +83,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
|
|||||||
2
.github/workflows/hook_copr.yml
vendored
2
.github/workflows/hook_copr.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{ github.ref }}
|
ref: ${{ github.ref }}
|
||||||
|
|||||||
240
.github/workflows/main_matrix.yml
vendored
240
.github/workflows/main_matrix.yml
vendored
@@ -28,21 +28,14 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
setup:
|
setup:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
arch:
|
arch:
|
||||||
- esp32
|
- all
|
||||||
- esp32s3
|
|
||||||
- esp32c3
|
|
||||||
- esp32c6
|
|
||||||
- nrf52840
|
|
||||||
- rp2040
|
|
||||||
- rp2350
|
|
||||||
- stm32
|
|
||||||
- check
|
- check
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
@@ -54,25 +47,19 @@ jobs:
|
|||||||
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
|
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
|
||||||
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
|
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
|
||||||
else
|
else
|
||||||
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr)
|
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr)
|
||||||
fi
|
fi
|
||||||
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
|
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
|
||||||
echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
|
echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT
|
||||||
|
echo "$TARGETS" >> $GITHUB_STEP_SUMMARY
|
||||||
outputs:
|
outputs:
|
||||||
esp32: ${{ steps.jsonStep.outputs.esp32 }}
|
all: ${{ steps.jsonStep.outputs.all }}
|
||||||
esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
|
|
||||||
esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
|
|
||||||
esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
|
|
||||||
nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
|
|
||||||
rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
|
|
||||||
rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
|
|
||||||
stm32: ${{ steps.jsonStep.outputs.stm32 }}
|
|
||||||
check: ${{ steps.jsonStep.outputs.check }}
|
check: ${{ steps.jsonStep.outputs.check }}
|
||||||
|
|
||||||
version:
|
version:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Get release version string
|
- name: Get release version string
|
||||||
run: |
|
run: |
|
||||||
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||||
@@ -88,105 +75,30 @@ jobs:
|
|||||||
needs: setup
|
needs: setup
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix: ${{ fromJson(needs.setup.outputs.check) }}
|
matrix:
|
||||||
|
check: ${{ fromJson(needs.setup.outputs.check) }}
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Build base
|
- name: Build base
|
||||||
id: base
|
id: base
|
||||||
uses: ./.github/actions/setup-base
|
uses: ./.github/actions/setup-base
|
||||||
- name: Check ${{ matrix.board }}
|
- name: Check ${{ matrix.check.board }}
|
||||||
run: bin/check-all.sh ${{ matrix.board }}
|
run: bin/check-all.sh ${{ matrix.check.board }}
|
||||||
|
|
||||||
build-esp32:
|
build:
|
||||||
needs: [setup, version]
|
needs: [setup, version]
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
|
matrix:
|
||||||
|
build: ${{ fromJson(needs.setup.outputs.all) }}
|
||||||
uses: ./.github/workflows/build_firmware.yml
|
uses: ./.github/workflows/build_firmware.yml
|
||||||
with:
|
with:
|
||||||
version: ${{ needs.version.outputs.long }}
|
version: ${{ needs.version.outputs.long }}
|
||||||
pio_env: ${{ matrix.board }}
|
pio_env: ${{ matrix.build.board }}
|
||||||
platform: esp32
|
platform: ${{ matrix.build.platform }}
|
||||||
|
|
||||||
build-esp32s3:
|
|
||||||
needs: [setup, version]
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
|
|
||||||
uses: ./.github/workflows/build_firmware.yml
|
|
||||||
with:
|
|
||||||
version: ${{ needs.version.outputs.long }}
|
|
||||||
pio_env: ${{ matrix.board }}
|
|
||||||
platform: esp32s3
|
|
||||||
|
|
||||||
build-esp32c3:
|
|
||||||
needs: [setup, version]
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
|
|
||||||
uses: ./.github/workflows/build_firmware.yml
|
|
||||||
with:
|
|
||||||
version: ${{ needs.version.outputs.long }}
|
|
||||||
pio_env: ${{ matrix.board }}
|
|
||||||
platform: esp32c3
|
|
||||||
|
|
||||||
build-esp32c6:
|
|
||||||
needs: [setup, version]
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
|
|
||||||
uses: ./.github/workflows/build_firmware.yml
|
|
||||||
with:
|
|
||||||
version: ${{ needs.version.outputs.long }}
|
|
||||||
pio_env: ${{ matrix.board }}
|
|
||||||
platform: esp32c6
|
|
||||||
|
|
||||||
build-nrf52840:
|
|
||||||
needs: [setup, version]
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
|
|
||||||
uses: ./.github/workflows/build_firmware.yml
|
|
||||||
with:
|
|
||||||
version: ${{ needs.version.outputs.long }}
|
|
||||||
pio_env: ${{ matrix.board }}
|
|
||||||
platform: nrf52840
|
|
||||||
|
|
||||||
build-rp2040:
|
|
||||||
needs: [setup, version]
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
|
|
||||||
uses: ./.github/workflows/build_firmware.yml
|
|
||||||
with:
|
|
||||||
version: ${{ needs.version.outputs.long }}
|
|
||||||
pio_env: ${{ matrix.board }}
|
|
||||||
platform: rp2040
|
|
||||||
|
|
||||||
build-rp2350:
|
|
||||||
needs: [setup, version]
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
|
|
||||||
uses: ./.github/workflows/build_firmware.yml
|
|
||||||
with:
|
|
||||||
version: ${{ needs.version.outputs.long }}
|
|
||||||
pio_env: ${{ matrix.board }}
|
|
||||||
platform: rp2350
|
|
||||||
|
|
||||||
build-stm32:
|
|
||||||
needs: [setup, version]
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
|
|
||||||
uses: ./.github/workflows/build_firmware.yml
|
|
||||||
with:
|
|
||||||
version: ${{ needs.version.outputs.long }}
|
|
||||||
pio_env: ${{ matrix.board }}
|
|
||||||
platform: stm32
|
|
||||||
|
|
||||||
build-debian-src:
|
build-debian-src:
|
||||||
if: github.repository == 'meshtastic/firmware'
|
if: github.repository == 'meshtastic/firmware'
|
||||||
@@ -197,68 +109,41 @@ jobs:
|
|||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
package-pio-deps-native-tft:
|
package-pio-deps-native-tft:
|
||||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
if: ${{ github.repository == 'meshtastic/firmware' && github.event_name == 'workflow_dispatch' }}
|
||||||
uses: ./.github/workflows/package_pio_deps.yml
|
uses: ./.github/workflows/package_pio_deps.yml
|
||||||
with:
|
with:
|
||||||
pio_env: native-tft
|
pio_env: native-tft
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
test-native:
|
test-native:
|
||||||
if: ${{ !contains(github.ref_name, 'event/') }}
|
if: ${{ !contains(github.ref_name, 'event/') && github.repository == 'meshtastic/firmware' }}
|
||||||
uses: ./.github/workflows/test_native.yml
|
uses: ./.github/workflows/test_native.yml
|
||||||
|
|
||||||
docker-deb-amd64:
|
docker:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
distro: [debian, alpine]
|
||||||
|
platform: [linux/amd64, linux/arm64, linux/arm/v7]
|
||||||
|
pio_env: [native, native-tft]
|
||||||
|
exclude:
|
||||||
|
- distro: alpine
|
||||||
|
platform: linux/arm/v7
|
||||||
|
- pio_env: native-tft
|
||||||
|
platform: linux/arm64
|
||||||
|
- pio_env: native-tft
|
||||||
|
platform: linux/arm/v7
|
||||||
uses: ./.github/workflows/docker_build.yml
|
uses: ./.github/workflows/docker_build.yml
|
||||||
with:
|
with:
|
||||||
distro: debian
|
distro: ${{ matrix.distro }}
|
||||||
platform: linux/amd64
|
platform: ${{ matrix.platform }}
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ${{ contains(matrix.platform, 'arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||||
push: false
|
pio_env: ${{ matrix.pio_env }}
|
||||||
|
|
||||||
docker-deb-amd64-tft:
|
|
||||||
uses: ./.github/workflows/docker_build.yml
|
|
||||||
with:
|
|
||||||
distro: debian
|
|
||||||
platform: linux/amd64
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
push: false
|
|
||||||
pio_env: native-tft
|
|
||||||
|
|
||||||
docker-alp-amd64:
|
|
||||||
uses: ./.github/workflows/docker_build.yml
|
|
||||||
with:
|
|
||||||
distro: alpine
|
|
||||||
platform: linux/amd64
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
push: false
|
|
||||||
|
|
||||||
docker-alp-amd64-tft:
|
|
||||||
uses: ./.github/workflows/docker_build.yml
|
|
||||||
with:
|
|
||||||
distro: alpine
|
|
||||||
platform: linux/amd64
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
push: false
|
|
||||||
pio_env: native-tft
|
|
||||||
|
|
||||||
docker-deb-arm64:
|
|
||||||
uses: ./.github/workflows/docker_build.yml
|
|
||||||
with:
|
|
||||||
distro: debian
|
|
||||||
platform: linux/arm64
|
|
||||||
runs-on: ubuntu-24.04-arm
|
|
||||||
push: false
|
|
||||||
|
|
||||||
docker-deb-armv7:
|
|
||||||
uses: ./.github/workflows/docker_build.yml
|
|
||||||
with:
|
|
||||||
distro: debian
|
|
||||||
platform: linux/arm/v7
|
|
||||||
runs-on: ubuntu-24.04-arm
|
|
||||||
push: false
|
push: false
|
||||||
|
|
||||||
gather-artifacts:
|
gather-artifacts:
|
||||||
# trunk-ignore(checkov/CKV2_GHA_1)
|
# trunk-ignore(checkov/CKV2_GHA_1)
|
||||||
|
if: github.repository == 'meshtastic/firmware'
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
@@ -275,26 +160,15 @@ jobs:
|
|||||||
- rp2350
|
- rp2350
|
||||||
- stm32
|
- stm32
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs: [version, build]
|
||||||
[
|
|
||||||
version,
|
|
||||||
build-esp32,
|
|
||||||
build-esp32s3,
|
|
||||||
build-esp32c3,
|
|
||||||
build-esp32c6,
|
|
||||||
build-nrf52840,
|
|
||||||
build-rp2040,
|
|
||||||
build-rp2350,
|
|
||||||
build-stm32,
|
|
||||||
]
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
|
|
||||||
- uses: actions/download-artifact@v5
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
path: ./
|
path: ./
|
||||||
pattern: firmware-${{matrix.arch}}-*
|
pattern: firmware-${{matrix.arch}}-*
|
||||||
@@ -307,7 +181,7 @@ jobs:
|
|||||||
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
|
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
|
||||||
|
|
||||||
- name: Repackage in single firmware zip
|
- name: Repackage in single firmware zip
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
overwrite: true
|
overwrite: true
|
||||||
@@ -323,7 +197,7 @@ jobs:
|
|||||||
./Meshtastic_nRF52_factory_erase*.uf2
|
./Meshtastic_nRF52_factory_erase*.uf2
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
|
||||||
- uses: actions/download-artifact@v5
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -342,7 +216,7 @@ jobs:
|
|||||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||||
|
|
||||||
- name: Repackage in single elfs zip
|
- name: Repackage in single elfs zip
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
||||||
overwrite: true
|
overwrite: true
|
||||||
@@ -358,7 +232,7 @@ jobs:
|
|||||||
|
|
||||||
release-artifacts:
|
release-artifacts:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
|
||||||
outputs:
|
outputs:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
needs:
|
needs:
|
||||||
@@ -368,7 +242,7 @@ jobs:
|
|||||||
- package-pio-deps-native-tft
|
- package-pio-deps-native-tft
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
@@ -387,14 +261,14 @@ jobs:
|
|||||||
Autogenerated by github action, developer should edit as required before publishing...
|
Autogenerated by github action, developer should edit as required before publishing...
|
||||||
|
|
||||||
- name: Download source deb
|
- name: Download source deb
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
|
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
path: ./output/debian-src
|
path: ./output/debian-src
|
||||||
|
|
||||||
- name: Download `native-tft` pio deps
|
- name: Download `native-tft` pio deps
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -433,18 +307,18 @@ jobs:
|
|||||||
- rp2350
|
- rp2350
|
||||||
- stm32
|
- stm32
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware'}}
|
||||||
needs: [release-artifacts, version]
|
needs: [release-artifacts, version]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
- uses: actions/download-artifact@v5
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -461,7 +335,7 @@ jobs:
|
|||||||
- name: Zip firmware
|
- name: Zip firmware
|
||||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||||
|
|
||||||
- uses: actions/download-artifact@v5
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -492,14 +366,14 @@ jobs:
|
|||||||
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
|
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
- uses: actions/download-artifact@v5
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|||||||
238
.github/workflows/merge_queue.yml
vendored
238
.github/workflows/merge_queue.yml
vendored
@@ -7,28 +7,18 @@ on:
|
|||||||
# Merge group is a special trigger that is used to trigger the workflow when a merge group is created.
|
# Merge group is a special trigger that is used to trigger the workflow when a merge group is created.
|
||||||
merge_group:
|
merge_group:
|
||||||
|
|
||||||
env:
|
|
||||||
FAIL_FAST_PER_ARCH: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
setup:
|
setup:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
arch:
|
arch:
|
||||||
- esp32
|
- all
|
||||||
- esp32s3
|
|
||||||
- esp32c3
|
|
||||||
- esp32c6
|
|
||||||
- nrf52840
|
|
||||||
- rp2040
|
|
||||||
- rp2350
|
|
||||||
- stm32
|
|
||||||
- check
|
- check
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
cache: pip
|
cache: pip
|
||||||
@@ -39,25 +29,18 @@ jobs:
|
|||||||
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
|
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
|
||||||
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
|
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
|
||||||
else
|
else
|
||||||
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr)
|
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr)
|
||||||
fi
|
fi
|
||||||
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
|
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
|
||||||
echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
|
echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT
|
||||||
outputs:
|
outputs:
|
||||||
esp32: ${{ steps.jsonStep.outputs.esp32 }}
|
all: ${{ steps.jsonStep.outputs.all }}
|
||||||
esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
|
|
||||||
esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
|
|
||||||
esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
|
|
||||||
nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
|
|
||||||
rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
|
|
||||||
rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
|
|
||||||
stm32: ${{ steps.jsonStep.outputs.stm32 }}
|
|
||||||
check: ${{ steps.jsonStep.outputs.check }}
|
check: ${{ steps.jsonStep.outputs.check }}
|
||||||
|
|
||||||
version:
|
version:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- name: Get release version string
|
- name: Get release version string
|
||||||
run: |
|
run: |
|
||||||
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||||
@@ -73,105 +56,29 @@ jobs:
|
|||||||
needs: setup
|
needs: setup
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix: ${{ fromJson(needs.setup.outputs.check) }}
|
matrix:
|
||||||
|
check: ${{ fromJson(needs.setup.outputs.check) }}
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- name: Build base
|
- name: Build base
|
||||||
id: base
|
id: base
|
||||||
uses: ./.github/actions/setup-base
|
uses: ./.github/actions/setup-base
|
||||||
- name: Check ${{ matrix.board }}
|
- name: Check ${{ matrix.check.board }}
|
||||||
run: bin/check-all.sh ${{ matrix.board }}
|
run: bin/check-all.sh ${{ matrix.check.board }}
|
||||||
|
|
||||||
build-esp32:
|
build:
|
||||||
needs: [setup, version]
|
needs: [setup, version]
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
matrix:
|
||||||
matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
|
build: ${{ fromJson(needs.setup.outputs.all) }}
|
||||||
uses: ./.github/workflows/build_firmware.yml
|
uses: ./.github/workflows/build_firmware.yml
|
||||||
with:
|
with:
|
||||||
version: ${{ needs.version.outputs.long }}
|
version: ${{ needs.version.outputs.long }}
|
||||||
pio_env: ${{ matrix.board }}
|
pio_env: ${{ matrix.build.board }}
|
||||||
platform: esp32
|
platform: ${{ matrix.build.platform }}
|
||||||
|
|
||||||
build-esp32s3:
|
|
||||||
needs: [setup, version]
|
|
||||||
strategy:
|
|
||||||
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
|
||||||
matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
|
|
||||||
uses: ./.github/workflows/build_firmware.yml
|
|
||||||
with:
|
|
||||||
version: ${{ needs.version.outputs.long }}
|
|
||||||
pio_env: ${{ matrix.board }}
|
|
||||||
platform: esp32s3
|
|
||||||
|
|
||||||
build-esp32c3:
|
|
||||||
needs: [setup, version]
|
|
||||||
strategy:
|
|
||||||
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
|
||||||
matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
|
|
||||||
uses: ./.github/workflows/build_firmware.yml
|
|
||||||
with:
|
|
||||||
version: ${{ needs.version.outputs.long }}
|
|
||||||
pio_env: ${{ matrix.board }}
|
|
||||||
platform: esp32c3
|
|
||||||
|
|
||||||
build-esp32c6:
|
|
||||||
needs: [setup, version]
|
|
||||||
strategy:
|
|
||||||
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
|
||||||
matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
|
|
||||||
uses: ./.github/workflows/build_firmware.yml
|
|
||||||
with:
|
|
||||||
version: ${{ needs.version.outputs.long }}
|
|
||||||
pio_env: ${{ matrix.board }}
|
|
||||||
platform: esp32c6
|
|
||||||
|
|
||||||
build-nrf52840:
|
|
||||||
needs: [setup, version]
|
|
||||||
strategy:
|
|
||||||
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
|
||||||
matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
|
|
||||||
uses: ./.github/workflows/build_firmware.yml
|
|
||||||
with:
|
|
||||||
version: ${{ needs.version.outputs.long }}
|
|
||||||
pio_env: ${{ matrix.board }}
|
|
||||||
platform: nrf52840
|
|
||||||
|
|
||||||
build-rp2040:
|
|
||||||
needs: [setup, version]
|
|
||||||
strategy:
|
|
||||||
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
|
||||||
matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
|
|
||||||
uses: ./.github/workflows/build_firmware.yml
|
|
||||||
with:
|
|
||||||
version: ${{ needs.version.outputs.long }}
|
|
||||||
pio_env: ${{ matrix.board }}
|
|
||||||
platform: rp2040
|
|
||||||
|
|
||||||
build-rp2350:
|
|
||||||
needs: [setup, version]
|
|
||||||
strategy:
|
|
||||||
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
|
||||||
matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
|
|
||||||
uses: ./.github/workflows/build_firmware.yml
|
|
||||||
with:
|
|
||||||
version: ${{ needs.version.outputs.long }}
|
|
||||||
pio_env: ${{ matrix.board }}
|
|
||||||
platform: rp2350
|
|
||||||
|
|
||||||
build-stm32:
|
|
||||||
needs: [setup, version]
|
|
||||||
strategy:
|
|
||||||
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
|
||||||
matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
|
|
||||||
uses: ./.github/workflows/build_firmware.yml
|
|
||||||
with:
|
|
||||||
version: ${{ needs.version.outputs.long }}
|
|
||||||
pio_env: ${{ matrix.board }}
|
|
||||||
platform: stm32
|
|
||||||
|
|
||||||
build-debian-src:
|
build-debian-src:
|
||||||
if: github.repository == 'meshtastic/firmware'
|
if: github.repository == 'meshtastic/firmware'
|
||||||
@@ -192,54 +99,26 @@ jobs:
|
|||||||
if: ${{ !contains(github.ref_name, 'event/') }}
|
if: ${{ !contains(github.ref_name, 'event/') }}
|
||||||
uses: ./.github/workflows/test_native.yml
|
uses: ./.github/workflows/test_native.yml
|
||||||
|
|
||||||
docker-deb-amd64:
|
docker:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
distro: [debian, alpine]
|
||||||
|
platform: [linux/amd64, linux/arm64, linux/arm/v7]
|
||||||
|
pio_env: [native, native-tft]
|
||||||
|
exclude:
|
||||||
|
- distro: alpine
|
||||||
|
platform: linux/arm/v7
|
||||||
|
- pio_env: native-tft
|
||||||
|
platform: linux/arm64
|
||||||
|
- pio_env: native-tft
|
||||||
|
platform: linux/arm/v7
|
||||||
uses: ./.github/workflows/docker_build.yml
|
uses: ./.github/workflows/docker_build.yml
|
||||||
with:
|
with:
|
||||||
distro: debian
|
distro: ${{ matrix.distro }}
|
||||||
platform: linux/amd64
|
platform: ${{ matrix.platform }}
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ${{ contains(matrix.platform, 'arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||||
push: false
|
pio_env: ${{ matrix.pio_env }}
|
||||||
|
|
||||||
docker-deb-amd64-tft:
|
|
||||||
uses: ./.github/workflows/docker_build.yml
|
|
||||||
with:
|
|
||||||
distro: debian
|
|
||||||
platform: linux/amd64
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
push: false
|
|
||||||
pio_env: native-tft
|
|
||||||
|
|
||||||
docker-alp-amd64:
|
|
||||||
uses: ./.github/workflows/docker_build.yml
|
|
||||||
with:
|
|
||||||
distro: alpine
|
|
||||||
platform: linux/amd64
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
push: false
|
|
||||||
|
|
||||||
docker-alp-amd64-tft:
|
|
||||||
uses: ./.github/workflows/docker_build.yml
|
|
||||||
with:
|
|
||||||
distro: alpine
|
|
||||||
platform: linux/amd64
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
push: false
|
|
||||||
pio_env: native-tft
|
|
||||||
|
|
||||||
docker-deb-arm64:
|
|
||||||
uses: ./.github/workflows/docker_build.yml
|
|
||||||
with:
|
|
||||||
distro: debian
|
|
||||||
platform: linux/arm64
|
|
||||||
runs-on: ubuntu-24.04-arm
|
|
||||||
push: false
|
|
||||||
|
|
||||||
docker-deb-armv7:
|
|
||||||
uses: ./.github/workflows/docker_build.yml
|
|
||||||
with:
|
|
||||||
distro: debian
|
|
||||||
platform: linux/arm/v7
|
|
||||||
runs-on: ubuntu-24.04-arm
|
|
||||||
push: false
|
push: false
|
||||||
|
|
||||||
gather-artifacts:
|
gather-artifacts:
|
||||||
@@ -260,26 +139,15 @@ jobs:
|
|||||||
- rp2350
|
- rp2350
|
||||||
- stm32
|
- stm32
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs: [version, build]
|
||||||
[
|
|
||||||
version,
|
|
||||||
build-esp32,
|
|
||||||
build-esp32s3,
|
|
||||||
build-esp32c3,
|
|
||||||
build-esp32c6,
|
|
||||||
build-nrf52840,
|
|
||||||
build-rp2040,
|
|
||||||
build-rp2350,
|
|
||||||
build-stm32,
|
|
||||||
]
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
path: ./
|
path: ./
|
||||||
pattern: firmware-${{matrix.arch}}-*
|
pattern: firmware-${{matrix.arch}}-*
|
||||||
@@ -292,7 +160,7 @@ jobs:
|
|||||||
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
|
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
|
||||||
|
|
||||||
- name: Repackage in single firmware zip
|
- name: Repackage in single firmware zip
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
overwrite: true
|
overwrite: true
|
||||||
@@ -308,7 +176,7 @@ jobs:
|
|||||||
./Meshtastic_nRF52_factory_erase*.uf2
|
./Meshtastic_nRF52_factory_erase*.uf2
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -327,7 +195,7 @@ jobs:
|
|||||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||||
|
|
||||||
- name: Repackage in single elfs zip
|
- name: Repackage in single elfs zip
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
||||||
overwrite: true
|
overwrite: true
|
||||||
@@ -353,10 +221,10 @@ jobs:
|
|||||||
- package-pio-deps-native-tft
|
- package-pio-deps-native-tft
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
@@ -372,14 +240,14 @@ jobs:
|
|||||||
Autogenerated by github action, developer should edit as required before publishing...
|
Autogenerated by github action, developer should edit as required before publishing...
|
||||||
|
|
||||||
- name: Download source deb
|
- name: Download source deb
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
|
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
path: ./output/debian-src
|
path: ./output/debian-src
|
||||||
|
|
||||||
- name: Download `native-tft` pio deps
|
- name: Download `native-tft` pio deps
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -422,14 +290,14 @@ jobs:
|
|||||||
needs: [release-artifacts, version]
|
needs: [release-artifacts, version]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -446,7 +314,7 @@ jobs:
|
|||||||
- name: Zip firmware
|
- name: Zip firmware
|
||||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -477,14 +345,14 @@ jobs:
|
|||||||
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
|
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|||||||
4
.github/workflows/nightly.yml
vendored
4
.github/workflows/nightly.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Trunk Check
|
- name: Trunk Check
|
||||||
uses: trunk-io/trunk-action@v1
|
uses: trunk-io/trunk-action@v1
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
pull-requests: write # For trunk to create PRs
|
pull-requests: write # For trunk to create PRs
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Trunk Upgrade
|
- name: Trunk Upgrade
|
||||||
uses: trunk-io/trunk-action/upgrade@v1
|
uses: trunk-io/trunk-action/upgrade@v1
|
||||||
|
|||||||
4
.github/workflows/package_obs.yml
vendored
4
.github/workflows/package_obs.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
|||||||
needs: build-debian-src
|
needs: build-debian-src
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
path: meshtasticd
|
path: meshtasticd
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|||||||
4
.github/workflows/package_pio_deps.yml
vendored
4
.github/workflows/package_pio_deps.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
PLATFORMIO_CORE_DIR: pio/core
|
PLATFORMIO_CORE_DIR: pio/core
|
||||||
|
|
||||||
- name: Store binaries as an artifact
|
- name: Store binaries as an artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: platformio-deps-${{ inputs.pio_env }}-${{ steps.version.outputs.long }}
|
name: platformio-deps-${{ inputs.pio_env }}-${{ steps.version.outputs.long }}
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|||||||
4
.github/workflows/package_ppa.yml
vendored
4
.github/workflows/package_ppa.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
needs: build-debian-src
|
needs: build-debian-src
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
path: meshtasticd
|
path: meshtasticd
|
||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|||||||
2
.github/workflows/pr_enforce_labels.yml
vendored
2
.github/workflows/pr_enforce_labels.yml
vendored
@@ -10,7 +10,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-label:
|
check-label:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check for PR labels
|
- name: Check for PR labels
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
|
|||||||
4
.github/workflows/pr_tests.yml
vendored
4
.github/workflows/pr_tests.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
|||||||
checks: write
|
checks: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Download test artifacts
|
- name: Download test artifacts
|
||||||
if: needs.native-tests.result != 'skipped'
|
if: needs.native-tests.result != 'skipped'
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
|
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|||||||
11
.github/workflows/release_channels.yml
vendored
11
.github/workflows/release_channels.yml
vendored
@@ -21,10 +21,10 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
series:
|
series:
|
||||||
- jammy # 22.04
|
- jammy # 22.04 LTS
|
||||||
- noble # 24.04
|
- noble # 24.04 LTS
|
||||||
- plucky # 25.04
|
- plucky # 25.04
|
||||||
# - questing # 25.10
|
- questing # 25.10
|
||||||
uses: ./.github/workflows/package_ppa.yml
|
uses: ./.github/workflows/package_ppa.yml
|
||||||
with:
|
with:
|
||||||
ppa_repo: |-
|
ppa_repo: |-
|
||||||
@@ -60,7 +60,10 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
# Always use master branch for version bumps
|
||||||
|
ref: master
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
|
|||||||
6
.github/workflows/sec_sast_semgrep_cron.yml
vendored
6
.github/workflows/sec_sast_semgrep_cron.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# step 1
|
# step 1
|
||||||
- name: clone application source code
|
- name: clone application source code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
# step 2
|
# step 2
|
||||||
- name: full scan
|
- name: full scan
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
|
|
||||||
# step 3
|
# step 3
|
||||||
- name: save report as pipeline artifact
|
- name: save report as pipeline artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: report.sarif
|
name: report.sarif
|
||||||
overwrite: true
|
overwrite: true
|
||||||
@@ -41,7 +41,7 @@ jobs:
|
|||||||
|
|
||||||
# step 4
|
# step 4
|
||||||
- name: publish code scanning alerts
|
- name: publish code scanning alerts
|
||||||
uses: github/codeql-action/upload-sarif@v3
|
uses: github/codeql-action/upload-sarif@v4
|
||||||
with:
|
with:
|
||||||
sarif_file: report.sarif
|
sarif_file: report.sarif
|
||||||
category: semgrep
|
category: semgrep
|
||||||
|
|||||||
2
.github/workflows/sec_sast_semgrep_pull.yml
vendored
2
.github/workflows/sec_sast_semgrep_pull.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# step 1
|
# step 1
|
||||||
- name: clone application source code
|
- name: clone application source code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
8
.github/workflows/stale_bot.yml
vendored
8
.github/workflows/stale_bot.yml
vendored
@@ -17,8 +17,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Stale PR+Issues
|
- name: Stale PR+Issues
|
||||||
uses: actions/stale@v10.0.0
|
uses: actions/stale@v10.1.0
|
||||||
with:
|
with:
|
||||||
days-before-stale: 45
|
days-before-stale: 45
|
||||||
exempt-issue-labels: pinned,3.0
|
stale-issue-message: This issue has not had any comment or update in the last month. If it is still relevant, please post update comments. If no comments are made, this issue will be closed automagically in 7 days.
|
||||||
exempt-pr-labels: pinned,3.0
|
close-issue-message: This issue has not had any comment since the last notice. It has been closed automatically. If this is incorrect, or the issue becomes relevant again, please request that it is reopened.
|
||||||
|
exempt-issue-labels: pinned,3.0,triaged,backlog
|
||||||
|
exempt-pr-labels: pinned,3.0,triaged,backlog
|
||||||
|
|||||||
22
.github/workflows/test_native.yml
vendored
22
.github/workflows/test_native.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
name: Native Simulator Tests
|
name: Native Simulator Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Integration test
|
- name: Integration test
|
||||||
run: |
|
run: |
|
||||||
.pio/build/coverage/program &
|
.pio/build/coverage/program -s &
|
||||||
PID=$!
|
PID=$!
|
||||||
timeout 20 bash -c "until ls -al /proc/$PID/fd | grep socket; do sleep 1; done"
|
timeout 20 bash -c "until ls -al /proc/$PID/fd | grep socket; do sleep 1; done"
|
||||||
echo "Simulator started, launching python test..."
|
echo "Simulator started, launching python test..."
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
|
|
||||||
- name: Save coverage information
|
- name: Save coverage information
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
if: always() # run this step even if previous step failed
|
if: always() # run this step even if previous step failed
|
||||||
with:
|
with:
|
||||||
name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }}.zip
|
name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }}.zip
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
name: Native PlatformIO Tests
|
name: Native PlatformIO Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
@@ -94,7 +94,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Save test results
|
- name: Save test results
|
||||||
if: always() # run this step even if previous step failed
|
if: always() # run this step even if previous step failed
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
|
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
|
||||||
overwrite: true
|
overwrite: true
|
||||||
@@ -108,7 +108,7 @@ jobs:
|
|||||||
sed -i -e "s#${PWD}#.#" coverage_tests.info # Make paths relative.
|
sed -i -e "s#${PWD}#.#" coverage_tests.info # Make paths relative.
|
||||||
|
|
||||||
- name: Save coverage information
|
- name: Save coverage information
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
if: always() # run this step even if previous step failed
|
if: always() # run this step even if previous step failed
|
||||||
with:
|
with:
|
||||||
name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }}.zip
|
name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }}.zip
|
||||||
@@ -127,7 +127,7 @@ jobs:
|
|||||||
- platformio-tests
|
- platformio-tests
|
||||||
if: always()
|
if: always()
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
@@ -137,20 +137,20 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
|
|
||||||
- name: Download test artifacts
|
- name: Download test artifacts
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
|
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Test Report
|
- name: Test Report
|
||||||
uses: dorny/test-reporter@v2.1.1
|
uses: dorny/test-reporter@v2.3.0
|
||||||
with:
|
with:
|
||||||
name: PlatformIO Tests
|
name: PlatformIO Tests
|
||||||
path: testreport.xml
|
path: testreport.xml
|
||||||
reporter: java-junit
|
reporter: java-junit
|
||||||
|
|
||||||
- name: Download coverage artifacts
|
- name: Download coverage artifacts
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}.zip
|
pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}.zip
|
||||||
path: code-coverage-report
|
path: code-coverage-report
|
||||||
@@ -163,7 +163,7 @@ jobs:
|
|||||||
genhtml --quiet --legend --prefix "${PWD}" code-coverage-report/coverage_src.info --output-directory code-coverage-report
|
genhtml --quiet --legend --prefix "${PWD}" code-coverage-report/coverage_src.info --output-directory code-coverage-report
|
||||||
|
|
||||||
- name: Save Code Coverage Report
|
- name: Save Code Coverage Report
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: code-coverage-report-${{ steps.version.outputs.long }}.zip
|
name: code-coverage-report-${{ steps.version.outputs.long }}.zip
|
||||||
path: code-coverage-report
|
path: code-coverage-report
|
||||||
|
|||||||
6
.github/workflows/tests.yml
vendored
6
.github/workflows/tests.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: test-runner
|
runs-on: test-runner
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
# - uses: actions/setup-python@v5
|
# - uses: actions/setup-python@v5
|
||||||
# with:
|
# with:
|
||||||
@@ -47,9 +47,9 @@ jobs:
|
|||||||
pio upgrade
|
pio upgrade
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v5
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
|
|||||||
2
.github/workflows/trunk_annotate_pr.yml
vendored
2
.github/workflows/trunk_annotate_pr.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Trunk Check
|
- name: Trunk Check
|
||||||
uses: trunk-io/trunk-action@v1
|
uses: trunk-io/trunk-action@v1
|
||||||
|
|||||||
2
.github/workflows/trunk_check.yml
vendored
2
.github/workflows/trunk_check.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Trunk Check
|
- name: Trunk Check
|
||||||
uses: trunk-io/trunk-action@v1
|
uses: trunk-io/trunk-action@v1
|
||||||
|
|||||||
2
.github/workflows/trunk_format_pr.yml
vendored
2
.github/workflows/trunk_format_pr.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
|
|||||||
2
.github/workflows/update_protobufs.yml
vendored
2
.github/workflows/update_protobufs.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
|
|||||||
@@ -4,31 +4,31 @@ cli:
|
|||||||
plugins:
|
plugins:
|
||||||
sources:
|
sources:
|
||||||
- id: trunk
|
- id: trunk
|
||||||
ref: v1.7.2
|
ref: v1.7.4
|
||||||
uri: https://github.com/trunk-io/plugins
|
uri: https://github.com/trunk-io/plugins
|
||||||
lint:
|
lint:
|
||||||
enabled:
|
enabled:
|
||||||
- checkov@3.2.471
|
- checkov@3.2.495
|
||||||
- renovate@41.115.2
|
- renovate@42.27.1
|
||||||
- prettier@3.6.2
|
- prettier@3.7.3
|
||||||
- trufflehog@3.90.6
|
- trufflehog@3.91.1
|
||||||
- yamllint@1.37.1
|
- yamllint@1.37.1
|
||||||
- bandit@1.8.6
|
- bandit@1.9.2
|
||||||
- trivy@0.66.0
|
- trivy@0.67.2
|
||||||
- taplo@0.10.0
|
- taplo@0.10.0
|
||||||
- ruff@0.13.0
|
- ruff@0.14.7
|
||||||
- isort@6.0.1
|
- isort@7.0.0
|
||||||
- markdownlint@0.45.0
|
- markdownlint@0.46.0
|
||||||
- oxipng@9.1.5
|
- oxipng@9.1.5
|
||||||
- svgo@4.0.0
|
- svgo@4.0.0
|
||||||
- actionlint@1.7.7
|
- actionlint@1.7.9
|
||||||
- flake8@7.3.0
|
- flake8@7.3.0
|
||||||
- hadolint@2.13.1
|
- hadolint@2.14.0
|
||||||
- shfmt@3.6.0
|
- shfmt@3.6.0
|
||||||
- shellcheck@0.11.0
|
- shellcheck@0.11.0
|
||||||
- black@25.1.0
|
- black@25.11.0
|
||||||
- git-diff-check
|
- git-diff-check
|
||||||
- gitleaks@8.28.0
|
- gitleaks@8.30.0
|
||||||
- clang-format@16.0.3
|
- clang-format@16.0.3
|
||||||
ignore:
|
ignore:
|
||||||
- linters: [ALL]
|
- linters: [ALL]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
|
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
|
||||||
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
|
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
|
||||||
|
|
||||||
FROM python:3.13-slim-trixie AS builder
|
FROM python:3.14-slim-trixie AS builder
|
||||||
ARG PIO_ENV=native
|
ARG PIO_ENV=native
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
ENV TZ=Etc/UTC
|
ENV TZ=Etc/UTC
|
||||||
|
|||||||
@@ -37,4 +37,3 @@ Join our community and help improve Meshtastic! 🚀
|
|||||||
## Stats
|
## Stats
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
# trunk-ignore-all(hadolint/DL3018): Do not pin apk package versions
|
# trunk-ignore-all(hadolint/DL3018): Do not pin apk package versions
|
||||||
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
|
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
|
||||||
|
|
||||||
FROM python:3.13-alpine3.22 AS builder
|
FROM python:3.14-alpine3.22 AS builder
|
||||||
ARG PIO_ENV=native
|
ARG PIO_ENV=native
|
||||||
ENV PIP_ROOT_USER_ACTION=ignore
|
ENV PIP_ROOT_USER_ACTION=ignore
|
||||||
|
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
bash g++ libstdc++-dev linux-headers zip git ca-certificates libgpiod-dev yaml-cpp-dev bluez-dev \
|
bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \
|
||||||
|
libgpiod-dev yaml-cpp-dev bluez-dev \
|
||||||
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
|
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
|
||||||
libx11-dev libinput-dev libxkbcommon-dev \
|
libx11-dev libinput-dev libxkbcommon-dev \
|
||||||
&& rm -rf /var/cache/apk/* \
|
&& rm -rf /var/cache/apk/* \
|
||||||
@@ -40,8 +41,8 @@ LABEL org.opencontainers.image.title="Meshtastic" \
|
|||||||
USER root
|
USER root
|
||||||
|
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
shadow libstdc++ libgpiod yaml-cpp libusb i2c-tools libuv \
|
shadow libstdc++ libbsd libgpiod yaml-cpp libusb \
|
||||||
libx11 libinput libxkbcommon \
|
i2c-tools libuv libx11 libinput libxkbcommon \
|
||||||
&& rm -rf /var/cache/apk/* \
|
&& rm -rf /var/cache/apk/* \
|
||||||
&& mkdir -p /var/lib/meshtasticd \
|
&& mkdir -p /var/lib/meshtasticd \
|
||||||
&& mkdir -p /etc/meshtasticd/config.d \
|
&& mkdir -p /etc/meshtasticd/config.d \
|
||||||
|
|||||||
165
bin/analyze_map.py
Normal file
165
bin/analyze_map.py
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Summarise linker map output to highlight heavy object files and libraries.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python bin/analyze_map.py --map .pio/build/rak4631/output.map --top 20
|
||||||
|
|
||||||
|
The script parses GNU ld map files and aggregates section sizes per object file
|
||||||
|
and per archive/library, then prints sortable tables that make it easy to spot
|
||||||
|
modules worth trimming or hiding behind feature flags.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import collections
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from typing import DefaultDict, Dict, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
SECTION_LINE_RE = re.compile(r"^\s+(?P<section>\S+)\s+0x[0-9A-Fa-f]+\s+0x(?P<size>[0-9A-Fa-f]+)\s+(?P<object>.+)$")
|
||||||
|
ARCHIVE_MEMBER_RE = re.compile(r"^(?P<archive>.+)\((?P<object>[^)]+)\)$")
|
||||||
|
|
||||||
|
|
||||||
|
def human_size(num_bytes: int) -> str:
|
||||||
|
"""Return a friendly size string with one decimal place."""
|
||||||
|
if num_bytes < 1024:
|
||||||
|
return f"{num_bytes:,} B"
|
||||||
|
num = float(num_bytes)
|
||||||
|
for unit in ("KB", "MB", "GB"):
|
||||||
|
num /= 1024.0
|
||||||
|
if num < 1024.0:
|
||||||
|
return f"{num:.1f} {unit}"
|
||||||
|
return f"{num:.1f} TB"
|
||||||
|
|
||||||
|
|
||||||
|
def shorten_path(path: str, root: str) -> str:
|
||||||
|
"""Prefer repository-relative paths for readability."""
|
||||||
|
path = path.strip()
|
||||||
|
if not path:
|
||||||
|
return path
|
||||||
|
|
||||||
|
# Normalise Windows archives (backslashes) to POSIX style for consistency.
|
||||||
|
path = path.replace("\\", "/")
|
||||||
|
|
||||||
|
# Attempt to strip the root when an absolute path lives inside the repo.
|
||||||
|
if os.path.isabs(path):
|
||||||
|
try:
|
||||||
|
rel = os.path.relpath(path, root)
|
||||||
|
if not rel.startswith(".."):
|
||||||
|
return rel
|
||||||
|
except ValueError:
|
||||||
|
# relpath can fail on mixed drives on Windows; fall back to basename.
|
||||||
|
pass
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def describe_object(raw_object: str, root: str) -> Tuple[str, str]:
|
||||||
|
"""Return a human friendly object label and the library it belongs to."""
|
||||||
|
raw_object = raw_object.strip()
|
||||||
|
lib_label = "[app]"
|
||||||
|
match = ARCHIVE_MEMBER_RE.match(raw_object)
|
||||||
|
if match:
|
||||||
|
archive = shorten_path(match.group("archive"), root)
|
||||||
|
obj = match.group("object")
|
||||||
|
lib_label = os.path.basename(archive) or archive
|
||||||
|
label = f"{archive}:{obj}"
|
||||||
|
else:
|
||||||
|
label = shorten_path(raw_object, root)
|
||||||
|
# If the object lives under libs, hint at the containing directory.
|
||||||
|
parent = os.path.basename(os.path.dirname(label))
|
||||||
|
if parent:
|
||||||
|
lib_label = parent
|
||||||
|
return label, lib_label
|
||||||
|
|
||||||
|
|
||||||
|
def parse_map(map_path: str, repo_root: str) -> Tuple[Dict[str, int], Dict[str, int], Dict[str, Dict[str, int]]]:
|
||||||
|
per_object: DefaultDict[str, int] = collections.defaultdict(int)
|
||||||
|
per_library: DefaultDict[str, int] = collections.defaultdict(int)
|
||||||
|
per_object_sections: DefaultDict[str, DefaultDict[str, int]] = collections.defaultdict(lambda: collections.defaultdict(int))
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(map_path, "r", encoding="utf-8", errors="ignore") as handle:
|
||||||
|
for line in handle:
|
||||||
|
match = SECTION_LINE_RE.match(line)
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
|
||||||
|
section = match.group("section")
|
||||||
|
if section.startswith("*") or section in {"LOAD", "ORIGIN"}:
|
||||||
|
continue
|
||||||
|
|
||||||
|
size = int(match.group("size"), 16)
|
||||||
|
if size == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
obj_token = match.group("object").strip()
|
||||||
|
if not obj_token or obj_token.startswith("*") or "load address" in obj_token:
|
||||||
|
continue
|
||||||
|
|
||||||
|
label, lib_label = describe_object(obj_token, repo_root)
|
||||||
|
per_object[label] += size
|
||||||
|
per_library[lib_label] += size
|
||||||
|
per_object_sections[label][section] += size
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise SystemExit(f"error: map file '{map_path}' not found. Run a build first.")
|
||||||
|
|
||||||
|
return per_object, per_library, per_object_sections
|
||||||
|
|
||||||
|
|
||||||
|
def format_section_breakdown(section_sizes: Dict[str, int], total: int, limit: int = 3) -> str:
|
||||||
|
items = sorted(section_sizes.items(), key=lambda kv: kv[1], reverse=True)
|
||||||
|
parts = []
|
||||||
|
for section, size in items[:limit]:
|
||||||
|
pct = (size / total) * 100 if total else 0
|
||||||
|
parts.append(f"{section} {pct:.1f}%")
|
||||||
|
if len(items) > limit:
|
||||||
|
remainder = total - sum(size for _, size in items[:limit])
|
||||||
|
pct = (remainder / total) * 100 if total else 0
|
||||||
|
parts.append(f"other {pct:.1f}%")
|
||||||
|
return ", ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def print_report(map_path: str, top_n: int, per_object: Dict[str, int], per_library: Dict[str, int], per_object_sections: Dict[str, Dict[str, int]]):
|
||||||
|
total_bytes = sum(per_object.values())
|
||||||
|
if total_bytes == 0:
|
||||||
|
print("No section data found in map file.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Map file: {map_path}")
|
||||||
|
print(f"Accounted size: {human_size(total_bytes)} across {len(per_object)} object files\n")
|
||||||
|
|
||||||
|
sorted_objects = sorted(per_object.items(), key=lambda kv: kv[1], reverse=True)
|
||||||
|
print(f"Top {min(top_n, len(sorted_objects))} object files by linked size:")
|
||||||
|
for idx, (obj, size) in enumerate(sorted_objects[:top_n], 1):
|
||||||
|
pct = (size / total_bytes) * 100
|
||||||
|
breakdown = format_section_breakdown(per_object_sections[obj], size)
|
||||||
|
print(f"{idx:2}. {human_size(size):>9} ({size:,} B, {pct:5.2f}% of linked size)")
|
||||||
|
print(f" {obj}")
|
||||||
|
if breakdown:
|
||||||
|
print(f" sections: {breakdown}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
sorted_libs = sorted(per_library.items(), key=lambda kv: kv[1], reverse=True)
|
||||||
|
print(f"Top {min(top_n, len(sorted_libs))} libraries or source roots:")
|
||||||
|
for idx, (lib, size) in enumerate(sorted_libs[:top_n], 1):
|
||||||
|
pct = (size / total_bytes) * 100
|
||||||
|
print(f"{idx:2}. {human_size(size):>9} ({size:,} B, {pct:5.2f}% of linked size) {lib}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(description="Highlight heavy object files from a GNU ld map file.")
|
||||||
|
parser.add_argument("--map", default=".pio/build/rak4631/output.map", help="Path to the map file (default: %(default)s)")
|
||||||
|
parser.add_argument("--top", type=int, default=20, help="Number of entries to display per table (default: %(default)s)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
map_path = os.path.abspath(args.map)
|
||||||
|
repo_root = os.path.abspath(os.getcwd())
|
||||||
|
|
||||||
|
per_object, per_library, per_object_sections = parse_map(map_path, repo_root)
|
||||||
|
print_report(os.path.relpath(map_path, repo_root), args.top, per_object, per_library, per_object_sections)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -15,12 +15,12 @@ SET "LOGCOUNTER=0"
|
|||||||
SET "BPS_RESET=0"
|
SET "BPS_RESET=0"
|
||||||
|
|
||||||
@REM FIXME: Determine mcu from PlatformIO variant, this is unmaintainable.
|
@REM FIXME: Determine mcu from PlatformIO variant, this is unmaintainable.
|
||||||
SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone t-eth-elite tlora-pager mesh-tab dreamcatcher ESP32-S3-Pico seeed-sensecap-indicator heltec_capsule_sensor_v3 vision-master icarus tracksenger elecrow-adv"
|
SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone t-eth-elite tlora-pager mesh-tab dreamcatcher ESP32-S3-Pico seeed-sensecap-indicator heltec_capsule_sensor_v3 vision-master icarus tracksenger elecrow-adv heltec-v4"
|
||||||
SET "C3=esp32c3"
|
SET "C3=esp32c3"
|
||||||
@REM FIXME: Determine flash size from PlatformIO variant, this is unmaintainable.
|
@REM FIXME: Determine flash size from PlatformIO variant, this is unmaintainable.
|
||||||
SET "BIGDB_8MB=crowpanel-esp32s3 heltec_capsule_sensor_v3 heltec-v3 heltec-vision-master-e213 heltec-vision-master-e290 heltec-vision-master-t190 heltec-wireless-paper heltec-wireless-tracker heltec-wsl-v3 icarus seeed-xiao-s3 tbeam-s3-core tracksenger"
|
SET "BIGDB_8MB=crowpanel-esp32s3 heltec_capsule_sensor_v3 heltec-v3 heltec-vision-master-e213 heltec-vision-master-e290 heltec-vision-master-t190 heltec-wireless-paper heltec-wireless-tracker heltec-wsl-v3 icarus seeed-xiao-s3 tbeam-s3-core tracksenger"
|
||||||
SET "MUIDB_8MB=picomputer-s3 unphone seeed-sensecap-indicator"
|
SET "MUIDB_8MB=picomputer-s3 unphone seeed-sensecap-indicator"
|
||||||
SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite tlora-pager t-watch-s3 elecrow-adv"
|
SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite tlora-pager t-watch-s3 elecrow-adv heltec-v4"
|
||||||
|
|
||||||
GOTO getopts
|
GOTO getopts
|
||||||
:help
|
:help
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
PYTHON=${PYTHON:-$(which python3 python | head -n 1)}
|
PYTHON=${PYTHON:-$(which python3 python | head -n 1)}
|
||||||
BPS_RESET=false
|
BPS_RESET=false
|
||||||
@@ -31,21 +31,23 @@ MUIDB_8MB=(
|
|||||||
"seeed-sensecap-indicator"
|
"seeed-sensecap-indicator"
|
||||||
)
|
)
|
||||||
BIGDB_16MB=(
|
BIGDB_16MB=(
|
||||||
"t-deck"
|
|
||||||
"mesh-tab"
|
|
||||||
"t-energy-s3"
|
|
||||||
"dreamcatcher"
|
"dreamcatcher"
|
||||||
"ESP32-S3-Pico"
|
|
||||||
"m5stack-cores3"
|
|
||||||
"station-g2"
|
|
||||||
"t-eth-elite"
|
|
||||||
"tlora-pager"
|
|
||||||
"t-watch-s3"
|
|
||||||
"elecrow-adv"
|
"elecrow-adv"
|
||||||
|
"ESP32-S3-Pico"
|
||||||
|
"heltec-v4"
|
||||||
|
"m5stack-cores3"
|
||||||
|
"mesh-tab"
|
||||||
|
"station-g2"
|
||||||
|
"t-deck"
|
||||||
|
"t-energy-s3"
|
||||||
|
"t-eth-elite"
|
||||||
|
"t-watch-s3"
|
||||||
|
"tlora-pager"
|
||||||
)
|
)
|
||||||
S3_VARIANTS=(
|
S3_VARIANTS=(
|
||||||
"s3"
|
"s3"
|
||||||
"-v3"
|
"-v3"
|
||||||
|
"-v4"
|
||||||
"t-deck"
|
"t-deck"
|
||||||
"wireless-paper"
|
"wireless-paper"
|
||||||
"wireless-tracker"
|
"wireless-tracker"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
|
PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
|
||||||
CHANGE_MODE=false
|
CHANGE_MODE=false
|
||||||
|
|||||||
@@ -1,28 +1,32 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
"""Generate the CI matrix."""
|
"""Generate the CI matrix."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
import json
|
import json
|
||||||
import sys
|
|
||||||
import random
|
|
||||||
import re
|
import re
|
||||||
from platformio.project.config import ProjectConfig
|
from platformio.project.config import ProjectConfig
|
||||||
|
|
||||||
options = sys.argv[1:]
|
parser = argparse.ArgumentParser(description="Generate the CI matrix")
|
||||||
|
parser.add_argument("platform", help="Platform to build for")
|
||||||
|
parser.add_argument(
|
||||||
|
"--level",
|
||||||
|
choices=["extra", "pr"],
|
||||||
|
nargs="*",
|
||||||
|
default=[],
|
||||||
|
help="Board level to build for (omit for full release boards)",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
outlist = []
|
outlist = []
|
||||||
|
|
||||||
if len(options) < 1:
|
|
||||||
print(json.dumps(outlist))
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
cfg = ProjectConfig.get_instance()
|
cfg = ProjectConfig.get_instance()
|
||||||
pio_envs = cfg.envs()
|
pio_envs = cfg.envs()
|
||||||
|
|
||||||
# Gather all PlatformIO environments for filtering later
|
# Gather all PlatformIO environments for filtering later
|
||||||
all_envs = []
|
all_envs = []
|
||||||
for pio_env in pio_envs:
|
for pio_env in pio_envs:
|
||||||
env_build_flags = cfg.get(f"env:{pio_env}", 'build_flags')
|
env_build_flags = cfg.get(f"env:{pio_env}", "build_flags")
|
||||||
env_platform = None
|
env_platform = None
|
||||||
for flag in env_build_flags:
|
for flag in env_build_flags:
|
||||||
# Extract the platform from the build flags
|
# Extract the platform from the build flags
|
||||||
@@ -37,36 +41,35 @@ for pio_env in pio_envs:
|
|||||||
exit(1)
|
exit(1)
|
||||||
# Store env details as a dictionary, and add to 'all_envs' list
|
# Store env details as a dictionary, and add to 'all_envs' list
|
||||||
env = {
|
env = {
|
||||||
'name': pio_env,
|
"ci": {"board": pio_env, "platform": env_platform},
|
||||||
'platform': env_platform,
|
"board_level": cfg.get(f"env:{pio_env}", "board_level", default=None),
|
||||||
'board_level': cfg.get(f"env:{pio_env}", 'board_level', default=None),
|
"board_check": bool(cfg.get(f"env:{pio_env}", "board_check", default=False)),
|
||||||
'board_check': bool(cfg.get(f"env:{pio_env}", 'board_check', default=False))
|
|
||||||
}
|
}
|
||||||
all_envs.append(env)
|
all_envs.append(env)
|
||||||
|
|
||||||
# Filter outputs based on options
|
# Filter outputs based on options
|
||||||
# Check is mutually exclusive with other options (except 'pr')
|
# Check is mutually exclusive with other options (except 'pr')
|
||||||
if "check" in options:
|
if "check" in args.platform:
|
||||||
for env in all_envs:
|
for env in all_envs:
|
||||||
if env['board_check']:
|
if env["board_check"]:
|
||||||
if "pr" in options:
|
if "pr" in args.level:
|
||||||
if env['board_level'] == 'pr':
|
if env["board_level"] == "pr":
|
||||||
outlist.append(env['name'])
|
outlist.append(env["ci"])
|
||||||
else:
|
else:
|
||||||
outlist.append(env['name'])
|
outlist.append(env["ci"])
|
||||||
# Filter (non-check) builds by platform
|
# Filter (non-check) builds by platform
|
||||||
else:
|
else:
|
||||||
for env in all_envs:
|
for env in all_envs:
|
||||||
if options[0] == env['platform']:
|
if args.platform == env["ci"]["platform"] or args.platform == "all":
|
||||||
# Always include board_level = 'pr'
|
# Always include board_level = 'pr'
|
||||||
if env['board_level'] == 'pr':
|
if env["board_level"] == "pr":
|
||||||
outlist.append(env['name'])
|
outlist.append(env["ci"])
|
||||||
# Include board_level = 'extra' when requested
|
# Include board_level = 'extra' when requested
|
||||||
elif "extra" in options and env['board_level'] == "extra":
|
elif "extra" in args.level and env["board_level"] == "extra":
|
||||||
outlist.append(env['name'])
|
outlist.append(env["ci"])
|
||||||
# If no board level is specified, include in release builds (not PR)
|
# If no board level is specified, include in release builds (not PR)
|
||||||
elif "pr" not in options and not env['board_level']:
|
elif "pr" not in args.level and not env["board_level"]:
|
||||||
outlist.append(env['name'])
|
outlist.append(env["ci"])
|
||||||
|
|
||||||
# Return as a JSON list
|
# Return as a JSON list
|
||||||
print(json.dumps(outlist))
|
print(json.dumps(outlist))
|
||||||
|
|||||||
116
bin/kill-github-actions.sh
Executable file
116
bin/kill-github-actions.sh
Executable file
@@ -0,0 +1,116 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Script to cancel all running GitHub Actions workflows
|
||||||
|
# Requires GitHub CLI (gh) to be installed and authenticated
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Function to print colored output
|
||||||
|
print_status() {
|
||||||
|
echo -e "${GREEN}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_warning() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if gh CLI is installed
|
||||||
|
if ! command -v gh &> /dev/null; then
|
||||||
|
print_error "GitHub CLI (gh) is not installed. Please install it first:"
|
||||||
|
echo " brew install gh"
|
||||||
|
echo " Or visit: https://cli.github.com/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if authenticated
|
||||||
|
if ! gh auth status &> /dev/null; then
|
||||||
|
print_error "GitHub CLI is not authenticated. Please run:"
|
||||||
|
echo " gh auth login"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get repository info
|
||||||
|
REPO=$(gh repo view --json owner,name -q '.owner.login + "/" + .name')
|
||||||
|
if [[ -z "$REPO" ]]; then
|
||||||
|
print_error "Could not determine repository. Make sure you're in a GitHub repository."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_status "Working with repository: $REPO"
|
||||||
|
|
||||||
|
# Get all active workflows (both queued and in-progress)
|
||||||
|
print_status "Fetching active workflows (queued and in-progress)..."
|
||||||
|
QUEUED_WORKFLOWS=$(gh run list --status queued --json databaseId,displayTitle,headBranch,status --limit 100)
|
||||||
|
IN_PROGRESS_WORKFLOWS=$(gh run list --status in_progress --json databaseId,displayTitle,headBranch,status --limit 100)
|
||||||
|
|
||||||
|
# Combine both lists
|
||||||
|
ALL_WORKFLOWS=$(echo "$QUEUED_WORKFLOWS $IN_PROGRESS_WORKFLOWS" | jq -s 'add | unique_by(.databaseId)')
|
||||||
|
|
||||||
|
if [[ "$ALL_WORKFLOWS" == "[]" ]]; then
|
||||||
|
print_status "No active workflows found."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Parse and display active workflows
|
||||||
|
echo
|
||||||
|
print_warning "Found active workflows:"
|
||||||
|
echo "$ALL_WORKFLOWS" | jq -r '.[] | " - \(.displayTitle) (Branch: \(.headBranch), Status: \(.status), ID: \(.databaseId))"'
|
||||||
|
|
||||||
|
echo
|
||||||
|
read -p "Do you want to cancel ALL these workflows? (y/N): " -n 1 -r
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
print_status "Cancelled by user."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cancel each workflow
|
||||||
|
print_status "Cancelling workflows..."
|
||||||
|
CANCELLED_COUNT=0
|
||||||
|
FAILED_COUNT=0
|
||||||
|
|
||||||
|
while IFS= read -r WORKFLOW_ID; do
|
||||||
|
if [[ -n "$WORKFLOW_ID" ]]; then
|
||||||
|
print_status "Cancelling workflow ID: $WORKFLOW_ID"
|
||||||
|
if gh run cancel "$WORKFLOW_ID" 2>/dev/null; then
|
||||||
|
((CANCELLED_COUNT++))
|
||||||
|
else
|
||||||
|
print_error "Failed to cancel workflow ID: $WORKFLOW_ID"
|
||||||
|
((FAILED_COUNT++))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < <(echo "$ALL_WORKFLOWS" | jq -r '.[].databaseId')
|
||||||
|
|
||||||
|
echo
|
||||||
|
print_status "Summary:"
|
||||||
|
echo " - Cancelled: $CANCELLED_COUNT workflows"
|
||||||
|
if [[ $FAILED_COUNT -gt 0 ]]; then
|
||||||
|
echo " - Failed: $FAILED_COUNT workflows"
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_status "Done!"
|
||||||
|
|
||||||
|
# Optional: Show remaining active workflows
|
||||||
|
echo
|
||||||
|
print_status "Checking for any remaining active workflows..."
|
||||||
|
REMAINING_QUEUED=$(gh run list --status queued --json databaseId --limit 10)
|
||||||
|
REMAINING_IN_PROGRESS=$(gh run list --status in_progress --json databaseId --limit 10)
|
||||||
|
REMAINING_ALL=$(echo "$REMAINING_QUEUED $REMAINING_IN_PROGRESS" | jq -s 'add | unique_by(.databaseId)')
|
||||||
|
|
||||||
|
if [[ "$REMAINING_ALL" == "[]" ]]; then
|
||||||
|
print_status "All workflows successfully cancelled."
|
||||||
|
else
|
||||||
|
REMAINING_COUNT=$(echo "$REMAINING_ALL" | jq '. | length')
|
||||||
|
print_warning "Still $REMAINING_COUNT workflows active (may take a moment to update status)"
|
||||||
|
fi
|
||||||
@@ -87,6 +87,30 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="2.7.17" date="2025-11-28">
|
||||||
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.17</url>
|
||||||
|
</release>
|
||||||
|
<release version="2.7.16" date="2025-11-19">
|
||||||
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.16</url>
|
||||||
|
</release>
|
||||||
|
<release version="2.7.15" date="2025-11-13">
|
||||||
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.15</url>
|
||||||
|
</release>
|
||||||
|
<release version="2.7.14" date="2025-11-03">
|
||||||
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.14</url>
|
||||||
|
</release>
|
||||||
|
<release version="2.7.13" date="2025-10-11">
|
||||||
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.13</url>
|
||||||
|
</release>
|
||||||
|
<release version="2.7.12" date="2025-10-01">
|
||||||
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.12</url>
|
||||||
|
</release>
|
||||||
|
<release version="2.7.11" date="2025-09-24">
|
||||||
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.11</url>
|
||||||
|
</release>
|
||||||
|
<release version="2.7.10" date="2025-09-18">
|
||||||
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.10</url>
|
||||||
|
</release>
|
||||||
<release version="2.7.9" date="2025-09-03">
|
<release version="2.7.9" date="2025-09-03">
|
||||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.9</url>
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.9</url>
|
||||||
</release>
|
</release>
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ if platform.name == "espressif32":
|
|||||||
|
|
||||||
if platform.name == "nordicnrf52":
|
if platform.name == "nordicnrf52":
|
||||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex",
|
env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex",
|
||||||
env.VerboseAction(f"\"{sys.executable}\" ./bin/uf2conv.py $BUILD_DIR/firmware.hex -c -f 0xADA52840 -o $BUILD_DIR/firmware.uf2",
|
env.VerboseAction(f"\"{sys.executable}\" ./bin/uf2conv.py \"$BUILD_DIR/firmware.hex\" -c -f 0xADA52840 -o \"$BUILD_DIR/firmware.uf2\"",
|
||||||
"Generating UF2 file"))
|
"Generating UF2 file"))
|
||||||
|
|
||||||
Import("projenv")
|
Import("projenv")
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.6.4
|
2.6.7
|
||||||
53
boards/ThinkNode-M3.json
Normal file
53
boards/ThinkNode-M3.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"arduino": {
|
||||||
|
"ldscript": "nrf52840_s140_v6.ld"
|
||||||
|
},
|
||||||
|
"core": "nRF5",
|
||||||
|
"cpu": "cortex-m4",
|
||||||
|
"extra_flags": "-DNRF52840_XXAA",
|
||||||
|
"f_cpu": "64000000L",
|
||||||
|
"hwids": [
|
||||||
|
["0x239A", "0x4405"],
|
||||||
|
["0x239A", "0x0029"],
|
||||||
|
["0x239A", "0x002A"]
|
||||||
|
],
|
||||||
|
"usb_product": "elecrow_eink",
|
||||||
|
"mcu": "nrf52840",
|
||||||
|
"variant": "ELECROW-ThinkNode-M3",
|
||||||
|
"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",
|
||||||
|
"openocd_target": "nrf52840-mdk-rs"
|
||||||
|
},
|
||||||
|
"frameworks": ["arduino"],
|
||||||
|
"name": "elecrow nrf",
|
||||||
|
"upload": {
|
||||||
|
"maximum_ram_size": 248832,
|
||||||
|
"maximum_size": 815104,
|
||||||
|
"speed": 115200,
|
||||||
|
"protocol": "nrfutil",
|
||||||
|
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
|
||||||
|
"use_1200bps_touch": true,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"wait_for_upload_port": true
|
||||||
|
},
|
||||||
|
"url": "",
|
||||||
|
"vendor": "ELECROW"
|
||||||
|
}
|
||||||
53
boards/ThinkNode-M6.json
Normal file
53
boards/ThinkNode-M6.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"arduino": {
|
||||||
|
"ldscript": "nrf52840_s140_v6.ld"
|
||||||
|
},
|
||||||
|
"core": "nRF5",
|
||||||
|
"cpu": "cortex-m4",
|
||||||
|
"extra_flags": "-DARDUINO_NRF52840_ELECROW_M6 -DNRF52840_XXAA",
|
||||||
|
"f_cpu": "64000000L",
|
||||||
|
"hwids": [
|
||||||
|
["0x239A", "0x4405"],
|
||||||
|
["0x239A", "0x0029"],
|
||||||
|
["0x239A", "0x002A"]
|
||||||
|
],
|
||||||
|
"usb_product": "elecrow_thinknode_m6",
|
||||||
|
"mcu": "nrf52840",
|
||||||
|
"variant": "ELECROW-ThinkNode-M6",
|
||||||
|
"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",
|
||||||
|
"openocd_target": "nrf52840-mdk-rs"
|
||||||
|
},
|
||||||
|
"frameworks": ["arduino"],
|
||||||
|
"name": "ELECROW ThinkNode M6",
|
||||||
|
"upload": {
|
||||||
|
"maximum_ram_size": 248832,
|
||||||
|
"maximum_size": 815104,
|
||||||
|
"speed": 115200,
|
||||||
|
"protocol": "nrfutil",
|
||||||
|
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
|
||||||
|
"use_1200bps_touch": true,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"wait_for_upload_port": true
|
||||||
|
},
|
||||||
|
"url": "https://www.elecrow.com/thinknode-m6-outdoor-solar-power-for-lora-powered-by-nrf52840-supports-gps.html",
|
||||||
|
"vendor": "ELECROW"
|
||||||
|
}
|
||||||
41
boards/hackaday-communicator.json
Normal file
41
boards/hackaday-communicator.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"arduino": {
|
||||||
|
"ldscript": "esp32s3_out.ld",
|
||||||
|
"memory_type": "qio_opi"
|
||||||
|
},
|
||||||
|
"core": "esp32",
|
||||||
|
"extra_flags": [
|
||||||
|
"-DBOARD_HAS_PSRAM",
|
||||||
|
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||||
|
"-DARDUINO_USB_MODE=0",
|
||||||
|
"-DARDUINO_RUNNING_CORE=1",
|
||||||
|
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||||
|
],
|
||||||
|
"f_cpu": "240000000L",
|
||||||
|
"f_flash": "80000000L",
|
||||||
|
"flash_mode": "qio",
|
||||||
|
"hwids": [["0x303A", "0x1001"]],
|
||||||
|
"mcu": "esp32s3",
|
||||||
|
"variant": "hackaday-communicator"
|
||||||
|
},
|
||||||
|
"connectivity": ["wifi", "bluetooth", "lora"],
|
||||||
|
"debug": {
|
||||||
|
"default_tool": "esp-builtin",
|
||||||
|
"onboard_tools": ["esp-builtin"],
|
||||||
|
"openocd_target": "esp32s3.cfg"
|
||||||
|
},
|
||||||
|
"frameworks": ["arduino", "espidf"],
|
||||||
|
"name": "hackaday-communicator (16 MB FLASH, 8 MB PSRAM)",
|
||||||
|
"upload": {
|
||||||
|
"flash_size": "16MB",
|
||||||
|
"maximum_ram_size": 327680,
|
||||||
|
"maximum_size": 16777216,
|
||||||
|
"use_1200bps_touch": true,
|
||||||
|
"wait_for_upload_port": true,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"speed": 1500000
|
||||||
|
},
|
||||||
|
"url": "hackaday.com",
|
||||||
|
"vendor": "hackaday"
|
||||||
|
}
|
||||||
43
boards/heltec_v4.json
Normal file
43
boards/heltec_v4.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"arduino": {
|
||||||
|
"ldscript": "esp32s3_out.ld",
|
||||||
|
"partitions": "default_16MB.csv",
|
||||||
|
"memory_type": "qio_qspi"
|
||||||
|
},
|
||||||
|
"core": "esp32",
|
||||||
|
"extra_flags": [
|
||||||
|
"-DBOARD_HAS_PSRAM",
|
||||||
|
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||||
|
"-DARDUINO_USB_MODE=1",
|
||||||
|
"-DARDUINO_RUNNING_CORE=1",
|
||||||
|
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||||
|
],
|
||||||
|
"f_cpu": "240000000L",
|
||||||
|
"f_flash": "80000000L",
|
||||||
|
"flash_mode": "qio",
|
||||||
|
"psram_type": "qspi",
|
||||||
|
"hwids": [["0x303A", "0x1001"]],
|
||||||
|
"mcu": "esp32s3",
|
||||||
|
"variant": "heltec_v4"
|
||||||
|
},
|
||||||
|
"connectivity": ["wifi", "bluetooth", "lora"],
|
||||||
|
"debug": {
|
||||||
|
"default_tool": "esp-builtin",
|
||||||
|
"onboard_tools": ["esp-builtin"],
|
||||||
|
"openocd_target": "esp32s3.cfg"
|
||||||
|
},
|
||||||
|
"frameworks": ["arduino", "espidf"],
|
||||||
|
"name": "heltec_wifi_lora_32 v4 (16 MB FLASH, 2 MB PSRAM)",
|
||||||
|
"upload": {
|
||||||
|
"flash_size": "16MB",
|
||||||
|
"maximum_ram_size": 2097152,
|
||||||
|
"maximum_size": 16777216,
|
||||||
|
"use_1200bps_touch": true,
|
||||||
|
"wait_for_upload_port": true,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"speed": 921600
|
||||||
|
},
|
||||||
|
"url": "https://heltec.org/",
|
||||||
|
"vendor": "heltec"
|
||||||
|
}
|
||||||
37
boards/heltec_wireless_tracker_v2.json
Normal file
37
boards/heltec_wireless_tracker_v2.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"arduino": {
|
||||||
|
"ldscript": "esp32s3_out.ld",
|
||||||
|
"partitions": "default_8MB.csv"
|
||||||
|
},
|
||||||
|
"core": "esp32",
|
||||||
|
"extra_flags": [
|
||||||
|
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||||
|
"-DARDUINO_USB_MODE=0",
|
||||||
|
"-DARDUINO_RUNNING_CORE=1",
|
||||||
|
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||||
|
],
|
||||||
|
"f_cpu": "240000000L",
|
||||||
|
"f_flash": "80000000L",
|
||||||
|
"flash_mode": "qio",
|
||||||
|
"hwids": [["0x303A", "0x1001"]],
|
||||||
|
"mcu": "esp32s3",
|
||||||
|
"variant": "heltec_wireless_tracker_v2"
|
||||||
|
},
|
||||||
|
"connectivity": ["wifi", "bluetooth", "lora"],
|
||||||
|
"debug": {
|
||||||
|
"openocd_target": "esp32s3.cfg"
|
||||||
|
},
|
||||||
|
"frameworks": ["arduino", "espidf"],
|
||||||
|
"name": "Heltec Wireless Tracker V2",
|
||||||
|
"upload": {
|
||||||
|
"flash_size": "8MB",
|
||||||
|
"maximum_ram_size": 327680,
|
||||||
|
"maximum_size": 8388608,
|
||||||
|
"wait_for_upload_port": true,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"speed": 921600
|
||||||
|
},
|
||||||
|
"url": "https://heltec.org",
|
||||||
|
"vendor": "Heltec"
|
||||||
|
}
|
||||||
56
boards/muzi-base.json
Normal file
56
boards/muzi-base.json
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"arduino": {
|
||||||
|
"ldscript": "nrf52840_s140_v6.ld"
|
||||||
|
},
|
||||||
|
"core": "nRF5",
|
||||||
|
"cpu": "cortex-m4",
|
||||||
|
"extra_flags": "-DARDUINO_NRF52840_MUZI_BASE -DNRF52840_XXAA",
|
||||||
|
"f_cpu": "64000000L",
|
||||||
|
"hwids": [["0x239A", "0xcafe"]],
|
||||||
|
"mcu": "nrf52840",
|
||||||
|
"variant": "muzi-base",
|
||||||
|
"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",
|
||||||
|
"openocd_target": "nrf52840-mdk-rs"
|
||||||
|
},
|
||||||
|
"frameworks": ["arduino"],
|
||||||
|
"name": "Muzi Base",
|
||||||
|
"url": "https://muzi.works/",
|
||||||
|
"vendor": "MuziWorks",
|
||||||
|
"upload": {
|
||||||
|
"maximum_ram_size": 248832,
|
||||||
|
"maximum_size": 815104,
|
||||||
|
"speed": 115200,
|
||||||
|
"protocol": "nrfutil",
|
||||||
|
"protocols": [
|
||||||
|
"jlink",
|
||||||
|
"nrfjprog",
|
||||||
|
"nrfutil",
|
||||||
|
"blackmagic",
|
||||||
|
"cmsis-dap",
|
||||||
|
"mbed",
|
||||||
|
"stlink"
|
||||||
|
],
|
||||||
|
"use_1200bps_touch": true,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"wait_for_upload_port": true
|
||||||
|
}
|
||||||
|
}
|
||||||
52
boards/r1-neo.json
Normal file
52
boards/r1-neo.json
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"arduino": {
|
||||||
|
"ldscript": "nrf52840_s140_v6.ld"
|
||||||
|
},
|
||||||
|
"core": "nRF5",
|
||||||
|
"cpu": "cortex-m4",
|
||||||
|
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
|
||||||
|
"f_cpu": "64000000L",
|
||||||
|
"hwids": [
|
||||||
|
["0x239A", "0x8029"],
|
||||||
|
["0x239A", "0x0029"],
|
||||||
|
["0x239A", "0x002A"],
|
||||||
|
["0x239A", "0x802A"]
|
||||||
|
],
|
||||||
|
"usb_product": "Muzi R1 Neo",
|
||||||
|
"mcu": "nrf52840",
|
||||||
|
"variant": "r1-neo",
|
||||||
|
"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",
|
||||||
|
"svd_path": "nrf52840.svd",
|
||||||
|
"openocd_target": "nrf52840-mdk-rs"
|
||||||
|
},
|
||||||
|
"frameworks": ["arduino", "freertos"],
|
||||||
|
"name": "WisCore RAK4631 Board",
|
||||||
|
"upload": {
|
||||||
|
"maximum_ram_size": 248832,
|
||||||
|
"maximum_size": 815104,
|
||||||
|
"speed": 115200,
|
||||||
|
"protocol": "nrfutil",
|
||||||
|
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
|
||||||
|
"use_1200bps_touch": true,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"wait_for_upload_port": true
|
||||||
|
},
|
||||||
|
"url": "https://muzi.works/",
|
||||||
|
"vendor": "Muzi Works"
|
||||||
|
}
|
||||||
78
debian/changelog
vendored
78
debian/changelog
vendored
@@ -1,50 +1,44 @@
|
|||||||
meshtasticd (2.7.9.0) UNRELEASED; urgency=medium
|
meshtasticd (2.7.17.0) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Version 2.7.17
|
||||||
|
|
||||||
|
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Fri, 28 Nov 2025 15:11:34 +0000
|
||||||
|
|
||||||
|
meshtasticd (2.7.16.0) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Version 2.7.16
|
||||||
|
|
||||||
|
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Wed, 19 Nov 2025 16:12:32 +0000
|
||||||
|
|
||||||
|
|
||||||
|
meshtasticd (2.7.15.0) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Version 2.7.15
|
||||||
|
|
||||||
|
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Thu, 13 Nov 2025 12:31:57 +0000
|
||||||
|
|
||||||
|
meshtasticd (2.7.14.0) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Version 2.7.14
|
||||||
|
|
||||||
|
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Mon, 03 Nov 2025 16:11:31 +0000
|
||||||
|
|
||||||
|
meshtasticd (2.7.13.0) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Version 2.7.13
|
||||||
|
|
||||||
|
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Sat, 11 Oct 2025 15:27:28 +0000
|
||||||
|
|
||||||
|
meshtasticd (2.7.12.0) unstable; urgency=medium
|
||||||
|
|
||||||
[ Austin Lane ]
|
[ Austin Lane ]
|
||||||
* Initial packaging
|
* Initial packaging
|
||||||
* GitHub Actions Automatic version bump
|
* Version 2.5.19
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
[ ]
|
||||||
* GitHub Actions Automatic version bump
|
* GitHub Actions Automatic version bump
|
||||||
|
|
||||||
[ ]
|
[ GitHub Actions ]
|
||||||
* GitHub Actions Automatic version bump
|
* Version 2.7.12
|
||||||
|
|
||||||
[ ]
|
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Wed, 01 Oct 2025 19:51:41 +0000
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ Ubuntu ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
-- <github-actions[bot]@users.noreply.github.com> Wed, 03 Sep 2025 23:39:17 +0000
|
|
||||||
|
|||||||
5
debian/ci_changelog.sh
vendored
5
debian/ci_changelog.sh
vendored
@@ -1,7 +1,8 @@
|
|||||||
#!/usr/bin/bash
|
#!/usr/bin/bash
|
||||||
|
export DEBFULLNAME="GitHub Actions"
|
||||||
export DEBEMAIL="github-actions[bot]@users.noreply.github.com"
|
export DEBEMAIL="github-actions[bot]@users.noreply.github.com"
|
||||||
PKG_VERSION=$(python3 bin/buildinfo.py short)
|
PKG_VERSION=$(python3 bin/buildinfo.py short)
|
||||||
|
|
||||||
dch --newversion "$PKG_VERSION.0" \
|
dch --newversion "$PKG_VERSION.0" \
|
||||||
--distribution UNRELEASED \
|
--distribution unstable \
|
||||||
"GitHub Actions Automatic version bump"
|
"Version $PKG_VERSION"
|
||||||
|
|||||||
1
debian/control
vendored
1
debian/control
vendored
@@ -3,6 +3,7 @@ Section: misc
|
|||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: Austin Lane <vidplace7@gmail.com>
|
Maintainer: Austin Lane <vidplace7@gmail.com>
|
||||||
Build-Depends: debhelper-compat (= 13),
|
Build-Depends: debhelper-compat (= 13),
|
||||||
|
libc6-dev (>= 2.38) | libbsd-dev,
|
||||||
lsb-release,
|
lsb-release,
|
||||||
tar,
|
tar,
|
||||||
gzip,
|
gzip,
|
||||||
|
|||||||
2
debian/meshtasticd.postinst
vendored
2
debian/meshtasticd.postinst
vendored
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/sh
|
||||||
# postinst script for meshtasticd
|
# postinst script for meshtasticd
|
||||||
#
|
#
|
||||||
# see: dh_installdeb(1)
|
# see: dh_installdeb(1)
|
||||||
|
|||||||
2
debian/meshtasticd.postrm
vendored
2
debian/meshtasticd.postrm
vendored
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/sh
|
||||||
# postrm script for meshtasticd
|
# postrm script for meshtasticd
|
||||||
#
|
#
|
||||||
# see: dh_installdeb(1)
|
# see: dh_installdeb(1)
|
||||||
|
|||||||
@@ -49,6 +49,13 @@ BuildRequires: pkgconfig(x11)
|
|||||||
BuildRequires: pkgconfig(libinput)
|
BuildRequires: pkgconfig(libinput)
|
||||||
BuildRequires: pkgconfig(xkbcommon-x11)
|
BuildRequires: pkgconfig(xkbcommon-x11)
|
||||||
|
|
||||||
|
# libbsd is needed on older Fedora/RHEL to provide 'strlcpy'
|
||||||
|
%if 0%{?fedora} >= 39 || 0%{?rhel} >= 10
|
||||||
|
BuildRequires: glibc-devel >= 2.38
|
||||||
|
%else
|
||||||
|
BuildRequires: pkgconfig(libbsd-overlay)
|
||||||
|
%endif
|
||||||
|
|
||||||
Requires: systemd-udev
|
Requires: systemd-udev
|
||||||
|
|
||||||
%description
|
%description
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
default_envs = tbeam
|
default_envs = tbeam
|
||||||
|
|
||||||
extra_configs =
|
extra_configs =
|
||||||
arch/*/*.ini
|
variants/*/*.ini
|
||||||
variants/*/*/platformio.ini
|
variants/*/*/platformio.ini
|
||||||
variants/*/diy/*/platformio.ini
|
variants/*/diy/*/platformio.ini
|
||||||
src/graphics/niche/InkHUD/PlatformioConfig.ini
|
src/graphics/niche/InkHUD/PlatformioConfig.ini
|
||||||
@@ -55,12 +55,14 @@ build_flags = -Wno-missing-field-initializers
|
|||||||
-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
|
-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
|
||||||
#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
|
#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
|
||||||
#-D OLED_PL=1
|
#-D OLED_PL=1
|
||||||
|
#-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs
|
||||||
|
#-D DEBUG_LOOP_TIMING=1 ; uncomment to add main loop timing logs
|
||||||
|
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_filters = direct
|
monitor_filters = direct
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
|
||||||
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/0cbc26b1f8f61957af0475f486b362eafe7cc4e2.zip
|
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/2887bf4a19f64d92c984dcc8fd5ca7429e425e4a.zip
|
||||||
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
|
||||||
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
|
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
|
||||||
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
|
||||||
@@ -68,7 +70,7 @@ lib_deps =
|
|||||||
# renovate: datasource=git-refs depName=meshtastic-TinyGPSPlus packageName=https://github.com/meshtastic/TinyGPSPlus gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic-TinyGPSPlus packageName=https://github.com/meshtastic/TinyGPSPlus gitBranch=master
|
||||||
https://github.com/meshtastic/TinyGPSPlus/archive/71a82db35f3b973440044c476d4bcdc673b104f4.zip
|
https://github.com/meshtastic/TinyGPSPlus/archive/71a82db35f3b973440044c476d4bcdc673b104f4.zip
|
||||||
# renovate: datasource=git-refs depName=meshtastic-ArduinoThread packageName=https://github.com/meshtastic/ArduinoThread gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic-ArduinoThread packageName=https://github.com/meshtastic/ArduinoThread gitBranch=master
|
||||||
https://github.com/meshtastic/ArduinoThread/archive/7c3ee9e1951551b949763b1f5280f8db1fa4068d.zip
|
https://github.com/meshtastic/ArduinoThread/archive/b841b0415721f1341ea41cccfb4adccfaf951567.zip
|
||||||
# renovate: datasource=custom.pio depName=Nanopb packageName=nanopb/library/Nanopb
|
# renovate: datasource=custom.pio depName=Nanopb packageName=nanopb/library/Nanopb
|
||||||
nanopb/Nanopb@0.4.91
|
nanopb/Nanopb@0.4.91
|
||||||
# renovate: datasource=custom.pio depName=ErriezCRC32 packageName=erriez/library/ErriezCRC32
|
# renovate: datasource=custom.pio depName=ErriezCRC32 packageName=erriez/library/ErriezCRC32
|
||||||
@@ -88,7 +90,7 @@ framework = arduino
|
|||||||
lib_deps =
|
lib_deps =
|
||||||
${env.lib_deps}
|
${env.lib_deps}
|
||||||
# renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL
|
# renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL
|
||||||
end2endzone/NonBlockingRTTTL@1.3.0
|
end2endzone/NonBlockingRTTTL@1.4.0
|
||||||
build_flags = ${env.build_flags} -Os
|
build_flags = ${env.build_flags} -Os
|
||||||
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/>
|
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/>
|
||||||
|
|
||||||
@@ -113,18 +115,19 @@ lib_deps =
|
|||||||
[radiolib_base]
|
[radiolib_base]
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
|
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
|
||||||
jgromes/RadioLib@7.2.1
|
# jgromes/RadioLib@7.4.0
|
||||||
|
https://github.com/jgromes/RadioLib/archive/536c7267362e2c1345be7054ba45e503252975ff.zip
|
||||||
|
|
||||||
[device-ui_base]
|
[device-ui_base]
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
||||||
https://github.com/meshtastic/device-ui/archive/9ed5355a24059750e9b2eb5d669574d9ea42a37b.zip
|
https://github.com/meshtastic/device-ui/archive/3bf332240416c5cb8c919fac2a0ec7260eb3be75.zip
|
||||||
|
|
||||||
; Common libs for environmental measurements in telemetry module
|
; Common libs for environmental measurements in telemetry module
|
||||||
[environmental_base]
|
[environmental_base]
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# renovate: datasource=custom.pio depName=Adafruit BusIO packageName=adafruit/library/Adafruit BusIO
|
# renovate: datasource=custom.pio depName=Adafruit BusIO packageName=adafruit/library/Adafruit BusIO
|
||||||
adafruit/Adafruit BusIO@1.17.2
|
adafruit/Adafruit BusIO@1.17.4
|
||||||
# renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor
|
# renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor
|
||||||
adafruit/Adafruit Unified Sensor@1.1.15
|
adafruit/Adafruit Unified Sensor@1.1.15
|
||||||
# renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library
|
# renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library
|
||||||
@@ -162,11 +165,11 @@ lib_deps =
|
|||||||
# renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass
|
# renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass
|
||||||
mprograms/QMC5883LCompass@1.2.3
|
mprograms/QMC5883LCompass@1.2.3
|
||||||
# renovate: datasource=custom.pio depName=DFRobot_RTU packageName=dfrobot/library/DFRobot_RTU
|
# renovate: datasource=custom.pio depName=DFRobot_RTU packageName=dfrobot/library/DFRobot_RTU
|
||||||
dfrobot/DFRobot_RTU@1.0.3
|
dfrobot/DFRobot_RTU@1.0.6
|
||||||
# renovate: datasource=git-refs depName=DFRobot_RainfallSensor packageName=https://github.com/DFRobot/DFRobot_RainfallSensor gitBranch=master
|
# renovate: datasource=git-refs depName=DFRobot_RainfallSensor packageName=https://github.com/DFRobot/DFRobot_RainfallSensor gitBranch=master
|
||||||
https://github.com/DFRobot/DFRobot_RainfallSensor/archive/38fea5e02b40a5430be6dab39a99a6f6347d667e.zip
|
https://github.com/DFRobot/DFRobot_RainfallSensor/archive/38fea5e02b40a5430be6dab39a99a6f6347d667e.zip
|
||||||
# renovate: datasource=custom.pio depName=INA226 packageName=robtillaart/library/INA226
|
# renovate: datasource=custom.pio depName=INA226 packageName=robtillaart/library/INA226
|
||||||
robtillaart/INA226@0.6.4
|
robtillaart/INA226@0.6.5
|
||||||
# renovate: datasource=custom.pio depName=SparkFun MAX3010x packageName=sparkfun/library/SparkFun MAX3010x Pulse and Proximity Sensor Library
|
# renovate: datasource=custom.pio depName=SparkFun MAX3010x packageName=sparkfun/library/SparkFun MAX3010x Pulse and Proximity Sensor Library
|
||||||
sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@1.1.2
|
sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@1.1.2
|
||||||
# renovate: datasource=custom.pio depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/library/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library
|
# renovate: datasource=custom.pio depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/library/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library
|
||||||
@@ -174,11 +177,13 @@ lib_deps =
|
|||||||
# renovate: datasource=custom.pio depName=Adafruit LTR390 Library packageName=adafruit/library/Adafruit LTR390 Library
|
# renovate: datasource=custom.pio depName=Adafruit LTR390 Library packageName=adafruit/library/Adafruit LTR390 Library
|
||||||
adafruit/Adafruit LTR390 Library@1.1.2
|
adafruit/Adafruit LTR390 Library@1.1.2
|
||||||
# renovate: datasource=custom.pio depName=Adafruit PCT2075 packageName=adafruit/library/Adafruit PCT2075
|
# renovate: datasource=custom.pio depName=Adafruit PCT2075 packageName=adafruit/library/Adafruit PCT2075
|
||||||
adafruit/Adafruit PCT2075@1.0.5
|
adafruit/Adafruit PCT2075@1.0.6
|
||||||
# renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150
|
# renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150
|
||||||
dfrobot/DFRobot_BMM150@1.0.0
|
dfrobot/DFRobot_BMM150@1.0.0
|
||||||
# renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561
|
# renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561
|
||||||
adafruit/Adafruit TSL2561@1.1.2
|
adafruit/Adafruit TSL2561@1.1.2
|
||||||
|
# renovate: datasource=custom.pio depName=BH1750_WE packageName=wollewald/BH1750_WE@^1.1.10
|
||||||
|
wollewald/BH1750_WE@^1.1.10
|
||||||
|
|
||||||
; (not included in native / portduino)
|
; (not included in native / portduino)
|
||||||
[environmental_extra]
|
[environmental_extra]
|
||||||
@@ -208,6 +213,6 @@ lib_deps =
|
|||||||
# renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master
|
||||||
https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip
|
https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip
|
||||||
# renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core
|
# renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core
|
||||||
sensirion/Sensirion Core@0.7.1
|
sensirion/Sensirion Core@0.7.2
|
||||||
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
|
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
|
||||||
sensirion/Sensirion I2C SCD4x@1.1.0
|
sensirion/Sensirion I2C SCD4x@1.1.0
|
||||||
|
|||||||
Submodule protobufs updated: 945b796a98...52fa252f1e
@@ -8,6 +8,7 @@
|
|||||||
"replacements:all",
|
"replacements:all",
|
||||||
"workarounds:all"
|
"workarounds:all"
|
||||||
],
|
],
|
||||||
|
"baseBranchPatterns": ["master"],
|
||||||
"forkProcessing": "enabled",
|
"forkProcessing": "enabled",
|
||||||
"ignoreDeps": [
|
"ignoreDeps": [
|
||||||
"protobufs"
|
"protobufs"
|
||||||
|
|||||||
@@ -11,6 +11,11 @@
|
|||||||
#include <AudioOutputI2S.h>
|
#include <AudioOutputI2S.h>
|
||||||
#include <ESP8266SAM.h>
|
#include <ESP8266SAM.h>
|
||||||
|
|
||||||
|
#ifdef USE_XL9555
|
||||||
|
#include "ExtensionIOXL9555.hpp"
|
||||||
|
extern ExtensionIOXL9555 io;
|
||||||
|
#endif
|
||||||
|
|
||||||
#define AUDIO_THREAD_INTERVAL_MS 100
|
#define AUDIO_THREAD_INTERVAL_MS 100
|
||||||
|
|
||||||
class AudioThread : public concurrency::OSThread
|
class AudioThread : public concurrency::OSThread
|
||||||
@@ -20,12 +25,16 @@ class AudioThread : public concurrency::OSThread
|
|||||||
|
|
||||||
void beginRttl(const void *data, uint32_t len)
|
void beginRttl(const void *data, uint32_t len)
|
||||||
{
|
{
|
||||||
|
#ifdef T_LORA_PAGER
|
||||||
|
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
|
||||||
|
#endif
|
||||||
setCPUFast(true);
|
setCPUFast(true);
|
||||||
rtttlFile = new AudioFileSourcePROGMEM(data, len);
|
rtttlFile = new AudioFileSourcePROGMEM(data, len);
|
||||||
i2sRtttl = new AudioGeneratorRTTTL();
|
i2sRtttl = new AudioGeneratorRTTTL();
|
||||||
i2sRtttl->begin(rtttlFile, audioOut);
|
i2sRtttl->begin(rtttlFile, audioOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also handles actually playing the RTTTL, needs to be called in loop
|
||||||
bool isPlaying()
|
bool isPlaying()
|
||||||
{
|
{
|
||||||
if (i2sRtttl != nullptr) {
|
if (i2sRtttl != nullptr) {
|
||||||
@@ -41,10 +50,16 @@ class AudioThread : public concurrency::OSThread
|
|||||||
delete i2sRtttl;
|
delete i2sRtttl;
|
||||||
i2sRtttl = nullptr;
|
i2sRtttl = nullptr;
|
||||||
}
|
}
|
||||||
delete rtttlFile;
|
|
||||||
rtttlFile = nullptr;
|
if (rtttlFile != nullptr) {
|
||||||
|
delete rtttlFile;
|
||||||
|
rtttlFile = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
setCPUFast(false);
|
setCPUFast(false);
|
||||||
|
#ifdef T_LORA_PAGER
|
||||||
|
io.digitalWrite(EXPANDS_AMP_EN, LOW);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void readAloud(const char *text)
|
void readAloud(const char *text)
|
||||||
@@ -55,10 +70,16 @@ class AudioThread : public concurrency::OSThread
|
|||||||
i2sRtttl = nullptr;
|
i2sRtttl = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef T_LORA_PAGER
|
||||||
|
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
|
||||||
|
#endif
|
||||||
ESP8266SAM *sam = new ESP8266SAM;
|
ESP8266SAM *sam = new ESP8266SAM;
|
||||||
sam->Say(audioOut, text);
|
sam->Say(audioOut, text);
|
||||||
delete sam;
|
delete sam;
|
||||||
setCPUFast(false);
|
setCPUFast(false);
|
||||||
|
#ifdef T_LORA_PAGER
|
||||||
|
io.digitalWrite(EXPANDS_AMP_EN, LOW);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -81,9 +102,9 @@ class AudioThread : public concurrency::OSThread
|
|||||||
};
|
};
|
||||||
|
|
||||||
AudioGeneratorRTTTL *i2sRtttl = nullptr;
|
AudioGeneratorRTTTL *i2sRtttl = nullptr;
|
||||||
AudioOutputI2S *audioOut;
|
AudioOutputI2S *audioOut = nullptr;
|
||||||
|
|
||||||
AudioFileSourcePROGMEM *rtttlFile;
|
AudioFileSourcePROGMEM *rtttlFile = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess
|
|||||||
{
|
{
|
||||||
int result;
|
int result;
|
||||||
#ifdef ARCH_PORTDUINO
|
#ifdef ARCH_PORTDUINO
|
||||||
bool utf = !settingsMap[ascii_logs];
|
bool utf = !portduino_config.ascii_logs;
|
||||||
#else
|
#else
|
||||||
bool utf = true;
|
bool utf = true;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -38,4 +38,46 @@ const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaC
|
|||||||
return useShortName ? "Custom" : "Invalid";
|
return useShortName ? "Custom" : "Invalid";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *DisplayFormatters::getDeviceRole(meshtastic_Config_DeviceConfig_Role role)
|
||||||
|
{
|
||||||
|
switch (role) {
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_CLIENT:
|
||||||
|
return "Client";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE:
|
||||||
|
return "Client Mute";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN:
|
||||||
|
return "Client Hidden";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_CLIENT_BASE:
|
||||||
|
return "Client Base";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND:
|
||||||
|
return "Lost and Found";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_TRACKER:
|
||||||
|
return "Tracker";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_SENSOR:
|
||||||
|
return "Sensor";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_TAK:
|
||||||
|
return "TAK";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_TAK_TRACKER:
|
||||||
|
return "TAK Tracker";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_ROUTER:
|
||||||
|
return "Router";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_ROUTER_LATE:
|
||||||
|
return "Router Late";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,4 +6,5 @@ class DisplayFormatters
|
|||||||
public:
|
public:
|
||||||
static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
|
static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
|
||||||
bool usePreset);
|
bool usePreset);
|
||||||
|
static const char *getDeviceRole(meshtastic_Config_DeviceConfig_Role role);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ class GPSStatus : public Status
|
|||||||
|
|
||||||
meshtastic_Position p = meshtastic_Position_init_default;
|
meshtastic_Position p = meshtastic_Position_init_default;
|
||||||
|
|
||||||
|
/// Time of last valid GPS fix (millis since boot)
|
||||||
|
uint32_t lastFixMillis = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GPSStatus() { statusType = STATUS_TYPE_GPS; }
|
GPSStatus() { statusType = STATUS_TYPE_GPS; }
|
||||||
|
|
||||||
@@ -83,6 +86,9 @@ class GPSStatus : public Status
|
|||||||
|
|
||||||
uint32_t getNumSatellites() const { return p.sats_in_view; }
|
uint32_t getNumSatellites() const { return p.sats_in_view; }
|
||||||
|
|
||||||
|
/// Return millis() when the last GPS fix occurred (0 = never)
|
||||||
|
uint32_t getLastFixMillis() const { return lastFixMillis; }
|
||||||
|
|
||||||
bool matches(const GPSStatus *newStatus) const
|
bool matches(const GPSStatus *newStatus) const
|
||||||
{
|
{
|
||||||
#ifdef GPS_DEBUG
|
#ifdef GPS_DEBUG
|
||||||
@@ -114,6 +120,9 @@ class GPSStatus : public Status
|
|||||||
|
|
||||||
if (isDirty) {
|
if (isDirty) {
|
||||||
if (hasLock) {
|
if (hasLock) {
|
||||||
|
// Record time of last valid GPS fix
|
||||||
|
lastFixMillis = millis();
|
||||||
|
|
||||||
// In debug logs, identify position by @timestamp:stage (stage 3 = notify)
|
// In debug logs, identify position by @timestamp:stage (stage 3 = notify)
|
||||||
LOG_DEBUG("New GPS pos@%x:3 lat=%f lon=%f alt=%d pdop=%.2f track=%.2f speed=%.2f sats=%d", p.timestamp,
|
LOG_DEBUG("New GPS pos@%x:3 lat=%f lon=%f alt=%d pdop=%.2f track=%.2f speed=%.2f sats=%d", p.timestamp,
|
||||||
p.latitude_i * 1e-7, p.longitude_i * 1e-7, p.altitude, p.PDOP * 1e-2, p.ground_track * 1e-5,
|
p.latitude_i * 1e-7, p.longitude_i * 1e-7, p.altitude, p.PDOP * 1e-2, p.ground_track * 1e-5,
|
||||||
|
|||||||
@@ -14,16 +14,16 @@ class NodeStatus : public Status
|
|||||||
CallbackObserver<NodeStatus, const NodeStatus *> statusObserver =
|
CallbackObserver<NodeStatus, const NodeStatus *> statusObserver =
|
||||||
CallbackObserver<NodeStatus, const NodeStatus *>(this, &NodeStatus::updateStatus);
|
CallbackObserver<NodeStatus, const NodeStatus *>(this, &NodeStatus::updateStatus);
|
||||||
|
|
||||||
uint8_t numOnline = 0;
|
uint16_t numOnline = 0;
|
||||||
uint8_t numTotal = 0;
|
uint16_t numTotal = 0;
|
||||||
|
|
||||||
uint8_t lastNumTotal = 0;
|
uint16_t lastNumTotal = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool forceUpdate = false;
|
bool forceUpdate = false;
|
||||||
|
|
||||||
NodeStatus() { statusType = STATUS_TYPE_NODE; }
|
NodeStatus() { statusType = STATUS_TYPE_NODE; }
|
||||||
NodeStatus(uint8_t numOnline, uint8_t numTotal, bool forceUpdate = false) : Status()
|
NodeStatus(uint16_t numOnline, uint16_t numTotal, bool forceUpdate = false) : Status()
|
||||||
{
|
{
|
||||||
this->forceUpdate = forceUpdate;
|
this->forceUpdate = forceUpdate;
|
||||||
this->numOnline = numOnline;
|
this->numOnline = numOnline;
|
||||||
@@ -34,11 +34,11 @@ class NodeStatus : public Status
|
|||||||
|
|
||||||
void observe(Observable<const NodeStatus *> *source) { statusObserver.observe(source); }
|
void observe(Observable<const NodeStatus *> *source) { statusObserver.observe(source); }
|
||||||
|
|
||||||
uint8_t getNumOnline() const { return numOnline; }
|
uint16_t getNumOnline() const { return numOnline; }
|
||||||
|
|
||||||
uint8_t getNumTotal() const { return numTotal; }
|
uint16_t getNumTotal() const { return numTotal; }
|
||||||
|
|
||||||
uint8_t getLastNumTotal() const { return lastNumTotal; }
|
uint16_t getLastNumTotal() const { return lastNumTotal; }
|
||||||
|
|
||||||
bool matches(const NodeStatus *newStatus) const
|
bool matches(const NodeStatus *newStatus) const
|
||||||
{
|
{
|
||||||
@@ -56,7 +56,7 @@ class NodeStatus : public Status
|
|||||||
numTotal = newStatus->getNumTotal();
|
numTotal = newStatus->getNumTotal();
|
||||||
}
|
}
|
||||||
if (isDirty || newStatus->forceUpdate) {
|
if (isDirty || newStatus->forceUpdate) {
|
||||||
LOG_DEBUG("Node status update: %d online, %d total", numOnline, numTotal);
|
LOG_DEBUG("Node status update: %u online, %u total", numOnline, numTotal);
|
||||||
onNewStatus.notifyObservers(this);
|
onNewStatus.notifyObservers(this);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ static HasBatteryLevel *batteryLevel; // Default to NULL for no battery level se
|
|||||||
|
|
||||||
#ifdef BATTERY_PIN
|
#ifdef BATTERY_PIN
|
||||||
|
|
||||||
static void adcEnable()
|
void battery_adcEnable()
|
||||||
{
|
{
|
||||||
#ifdef ADC_CTRL // enable adc voltage divider when we need to read
|
#ifdef ADC_CTRL // enable adc voltage divider when we need to read
|
||||||
#ifdef ADC_USE_PULLUP
|
#ifdef ADC_USE_PULLUP
|
||||||
@@ -214,7 +214,7 @@ static void adcEnable()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void adcDisable()
|
static void battery_adcDisable()
|
||||||
{
|
{
|
||||||
#ifdef ADC_CTRL // disable adc voltage divider when we need to read
|
#ifdef ADC_CTRL // disable adc voltage divider when we need to read
|
||||||
#ifdef ADC_USE_PULLUP
|
#ifdef ADC_USE_PULLUP
|
||||||
@@ -278,6 +278,11 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if defined(BATTERY_CHARGING_INV)
|
||||||
|
// bit of trickery to show 99% up until the charge finishes
|
||||||
|
if (!digitalRead(BATTERY_CHARGING_INV) && battery_SOC > 99)
|
||||||
|
battery_SOC = 99;
|
||||||
|
#endif
|
||||||
return clamp((int)(battery_SOC), 0, 100);
|
return clamp((int)(battery_SOC), 0, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,7 +325,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
uint32_t raw = 0;
|
uint32_t raw = 0;
|
||||||
float scaled = 0;
|
float scaled = 0;
|
||||||
|
|
||||||
adcEnable();
|
battery_adcEnable();
|
||||||
#ifdef ARCH_ESP32 // ADC block for espressif platforms
|
#ifdef ARCH_ESP32 // ADC block for espressif platforms
|
||||||
raw = espAdcRead();
|
raw = espAdcRead();
|
||||||
scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
|
scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
|
||||||
@@ -332,7 +337,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
raw = raw / BATTERY_SENSE_SAMPLES;
|
raw = raw / BATTERY_SENSE_SAMPLES;
|
||||||
scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw;
|
scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw;
|
||||||
#endif
|
#endif
|
||||||
adcDisable();
|
battery_adcDisable();
|
||||||
|
|
||||||
if (!initial_read_done) {
|
if (!initial_read_done) {
|
||||||
// Flush the smoothing filter with an ADC reading, if the reading is plausibly correct
|
// Flush the smoothing filter with an ADC reading, if the reading is plausibly correct
|
||||||
@@ -455,6 +460,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
}
|
}
|
||||||
// if it's not HIGH - check the battery
|
// if it's not HIGH - check the battery
|
||||||
#endif
|
#endif
|
||||||
|
#elif defined(MUZI_BASE)
|
||||||
|
return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk;
|
||||||
#endif
|
#endif
|
||||||
return getBattVoltage() > chargingVolt;
|
return getBattVoltage() > chargingVolt;
|
||||||
}
|
}
|
||||||
@@ -470,6 +477,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
#endif
|
#endif
|
||||||
#ifdef EXT_CHRG_DETECT
|
#ifdef EXT_CHRG_DETECT
|
||||||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
|
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
|
||||||
|
#elif defined(BATTERY_CHARGING_INV)
|
||||||
|
return !digitalRead(BATTERY_CHARGING_INV);
|
||||||
#else
|
#else
|
||||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION)
|
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION)
|
||||||
if (hasINA()) {
|
if (hasINA()) {
|
||||||
@@ -562,6 +571,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
config.power.device_battery_ina_address) {
|
config.power.device_battery_ina_address) {
|
||||||
if (!ina226Sensor.isInitialized())
|
if (!ina226Sensor.isInitialized())
|
||||||
return ina226Sensor.runOnce() > 0;
|
return ina226Sensor.runOnce() > 0;
|
||||||
|
return ina226Sensor.isRunning();
|
||||||
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA260].first ==
|
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA260].first ==
|
||||||
config.power.device_battery_ina_address) {
|
config.power.device_battery_ina_address) {
|
||||||
if (!ina260Sensor.isInitialized())
|
if (!ina260Sensor.isInitialized())
|
||||||
@@ -691,7 +701,24 @@ bool Power::setup()
|
|||||||
#ifdef NRF_APM
|
#ifdef NRF_APM
|
||||||
found = true;
|
found = true;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef EXT_PWR_DETECT
|
||||||
|
attachInterrupt(
|
||||||
|
EXT_PWR_DETECT,
|
||||||
|
[]() {
|
||||||
|
power->setIntervalFromNow(0);
|
||||||
|
runASAP = true;
|
||||||
|
},
|
||||||
|
CHANGE);
|
||||||
|
#endif
|
||||||
|
#ifdef BATTERY_CHARGING_INV
|
||||||
|
attachInterrupt(
|
||||||
|
BATTERY_CHARGING_INV,
|
||||||
|
[]() {
|
||||||
|
power->setIntervalFromNow(0);
|
||||||
|
runASAP = true;
|
||||||
|
},
|
||||||
|
CHANGE);
|
||||||
|
#endif
|
||||||
enabled = found;
|
enabled = found;
|
||||||
low_voltage_counter = 0;
|
low_voltage_counter = 0;
|
||||||
|
|
||||||
@@ -748,6 +775,8 @@ void Power::shutdown()
|
|||||||
if (screen) {
|
if (screen) {
|
||||||
#ifdef T_DECK_PRO
|
#ifdef T_DECK_PRO
|
||||||
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button
|
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button
|
||||||
|
#elif defined(USE_EINK)
|
||||||
|
screen->showSimpleBanner("Shutting Down...", 2250); // dismiss after 3 seconds to avoid the banner on the sleep screen
|
||||||
#else
|
#else
|
||||||
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
|
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
|
||||||
#endif
|
#endif
|
||||||
@@ -828,8 +857,11 @@ void Power::readPowerStatus()
|
|||||||
|
|
||||||
// Notify any status instances that are observing us
|
// Notify any status instances that are observing us
|
||||||
const PowerStatus powerStatus2 = PowerStatus(hasBattery, usbPowered, isChargingNow, batteryVoltageMv, batteryChargePercent);
|
const PowerStatus powerStatus2 = PowerStatus(hasBattery, usbPowered, isChargingNow, batteryVoltageMv, batteryChargePercent);
|
||||||
LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(), powerStatus2.getIsCharging(),
|
if (millis() > lastLogTime + 50 * 1000) {
|
||||||
powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
|
LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(),
|
||||||
|
powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
|
||||||
|
lastLogTime = millis();
|
||||||
|
}
|
||||||
newStatus.notifyObservers(&powerStatus2);
|
newStatus.notifyObservers(&powerStatus2);
|
||||||
#ifdef DEBUG_HEAP
|
#ifdef DEBUG_HEAP
|
||||||
if (lastheap != memGet.getFreeHeap()) {
|
if (lastheap != memGet.getFreeHeap()) {
|
||||||
@@ -892,13 +924,8 @@ void Power::readPowerStatus()
|
|||||||
low_voltage_counter++;
|
low_voltage_counter++;
|
||||||
LOG_DEBUG("Low voltage counter: %d/10", low_voltage_counter);
|
LOG_DEBUG("Low voltage counter: %d/10", low_voltage_counter);
|
||||||
if (low_voltage_counter > 10) {
|
if (low_voltage_counter > 10) {
|
||||||
#ifdef ARCH_NRF52
|
|
||||||
// We can't trigger deep sleep on NRF52, it's freezing the board
|
|
||||||
LOG_DEBUG("Low voltage detected, but not trigger deep sleep");
|
|
||||||
#else
|
|
||||||
LOG_INFO("Low voltage detected, trigger deep sleep");
|
LOG_INFO("Low voltage detected, trigger deep sleep");
|
||||||
powerFSM.trigger(EVENT_LOW_BATTERY);
|
powerFSM.trigger(EVENT_LOW_BATTERY);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
low_voltage_counter = 0;
|
low_voltage_counter = 0;
|
||||||
@@ -1426,7 +1453,7 @@ class LipoCharger : public HasBatteryLevel
|
|||||||
/**
|
/**
|
||||||
* return true if there is an external power source detected
|
* return true if there is an external power source detected
|
||||||
*/
|
*/
|
||||||
virtual bool isVbusIn() override { return PPM->getVbusVoltage() > 0; }
|
virtual bool isVbusIn() override { return PPM->isVbusIn(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return true if the battery is currently charging
|
* return true if the battery is currently charging
|
||||||
@@ -1538,4 +1565,4 @@ bool Power::meshSolarInit()
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -57,21 +57,21 @@ static bool isPowered()
|
|||||||
|
|
||||||
static void sdsEnter()
|
static void sdsEnter()
|
||||||
{
|
{
|
||||||
LOG_DEBUG("State: SDS");
|
LOG_POWERFSM("State: SDS");
|
||||||
// FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw
|
// FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw
|
||||||
doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false, false);
|
doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lowBattSDSEnter()
|
static void lowBattSDSEnter()
|
||||||
{
|
{
|
||||||
LOG_DEBUG("State: Lower batt SDS");
|
LOG_POWERFSM("State: Lower batt SDS");
|
||||||
doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false, true);
|
doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false, true);
|
||||||
}
|
}
|
||||||
extern Power *power;
|
extern Power *power;
|
||||||
|
|
||||||
static void shutdownEnter()
|
static void shutdownEnter()
|
||||||
{
|
{
|
||||||
LOG_DEBUG("State: SHUTDOWN");
|
LOG_POWERFSM("State: SHUTDOWN");
|
||||||
shutdownAtMsec = millis();
|
shutdownAtMsec = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ static uint32_t secsSlept;
|
|||||||
|
|
||||||
static void lsEnter()
|
static void lsEnter()
|
||||||
{
|
{
|
||||||
LOG_INFO("lsEnter begin, ls_secs=%u", config.power.ls_secs);
|
LOG_POWERFSM("lsEnter begin, ls_secs=%u", config.power.ls_secs);
|
||||||
if (screen)
|
if (screen)
|
||||||
screen->setOn(false);
|
screen->setOn(false);
|
||||||
secsSlept = 0; // How long have we been sleeping this time
|
secsSlept = 0; // How long have we been sleeping this time
|
||||||
@@ -155,12 +155,12 @@ static void lsIdle()
|
|||||||
|
|
||||||
static void lsExit()
|
static void lsExit()
|
||||||
{
|
{
|
||||||
LOG_INFO("Exit state: LS");
|
LOG_POWERFSM("State: lsExit");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nbEnter()
|
static void nbEnter()
|
||||||
{
|
{
|
||||||
LOG_DEBUG("State: NB");
|
LOG_POWERFSM("State: nbEnter");
|
||||||
if (screen)
|
if (screen)
|
||||||
screen->setOn(false);
|
screen->setOn(false);
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
@@ -173,6 +173,7 @@ static void nbEnter()
|
|||||||
|
|
||||||
static void darkEnter()
|
static void darkEnter()
|
||||||
{
|
{
|
||||||
|
LOG_POWERFSM("State: darkEnter");
|
||||||
setBluetoothEnable(true);
|
setBluetoothEnable(true);
|
||||||
if (screen)
|
if (screen)
|
||||||
screen->setOn(false);
|
screen->setOn(false);
|
||||||
@@ -180,7 +181,7 @@ static void darkEnter()
|
|||||||
|
|
||||||
static void serialEnter()
|
static void serialEnter()
|
||||||
{
|
{
|
||||||
LOG_DEBUG("State: SERIAL");
|
LOG_POWERFSM("State: serialEnter");
|
||||||
setBluetoothEnable(false);
|
setBluetoothEnable(false);
|
||||||
if (screen) {
|
if (screen) {
|
||||||
screen->setOn(true);
|
screen->setOn(true);
|
||||||
@@ -189,13 +190,14 @@ static void serialEnter()
|
|||||||
|
|
||||||
static void serialExit()
|
static void serialExit()
|
||||||
{
|
{
|
||||||
|
LOG_POWERFSM("State: serialExit");
|
||||||
// Turn bluetooth back on when we leave serial stream API
|
// Turn bluetooth back on when we leave serial stream API
|
||||||
setBluetoothEnable(true);
|
setBluetoothEnable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void powerEnter()
|
static void powerEnter()
|
||||||
{
|
{
|
||||||
// LOG_DEBUG("State: POWER");
|
LOG_POWERFSM("State: powerEnter");
|
||||||
if (!isPowered()) {
|
if (!isPowered()) {
|
||||||
// If we got here, we are in the wrong state - we should be in powered, let that state handle things
|
// If we got here, we are in the wrong state - we should be in powered, let that state handle things
|
||||||
LOG_INFO("Loss of power in Powered");
|
LOG_INFO("Loss of power in Powered");
|
||||||
@@ -210,6 +212,7 @@ static void powerEnter()
|
|||||||
|
|
||||||
static void powerIdle()
|
static void powerIdle()
|
||||||
{
|
{
|
||||||
|
// LOG_POWERFSM("State: powerIdle"); // very chatty
|
||||||
if (!isPowered()) {
|
if (!isPowered()) {
|
||||||
// If we got here, we are in the wrong state
|
// If we got here, we are in the wrong state
|
||||||
LOG_INFO("Loss of power in Powered");
|
LOG_INFO("Loss of power in Powered");
|
||||||
@@ -219,14 +222,13 @@ static void powerIdle()
|
|||||||
|
|
||||||
static void powerExit()
|
static void powerExit()
|
||||||
{
|
{
|
||||||
if (screen)
|
LOG_POWERFSM("State: powerExit");
|
||||||
screen->setOn(true);
|
|
||||||
setBluetoothEnable(true);
|
setBluetoothEnable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void onEnter()
|
static void onEnter()
|
||||||
{
|
{
|
||||||
LOG_DEBUG("State: ON");
|
LOG_POWERFSM("State: onEnter");
|
||||||
if (screen)
|
if (screen)
|
||||||
screen->setOn(true);
|
screen->setOn(true);
|
||||||
setBluetoothEnable(true);
|
setBluetoothEnable(true);
|
||||||
@@ -234,6 +236,7 @@ static void onEnter()
|
|||||||
|
|
||||||
static void onIdle()
|
static void onIdle()
|
||||||
{
|
{
|
||||||
|
LOG_POWERFSM("State: onIdle");
|
||||||
if (isPowered()) {
|
if (isPowered()) {
|
||||||
// If we got here, we are in the wrong state - we should be in powered, let that state handle things
|
// If we got here, we are in the wrong state - we should be in powered, let that state handle things
|
||||||
powerFSM.trigger(EVENT_POWER_CONNECTED);
|
powerFSM.trigger(EVENT_POWER_CONNECTED);
|
||||||
@@ -242,7 +245,7 @@ static void onIdle()
|
|||||||
|
|
||||||
static void bootEnter()
|
static void bootEnter()
|
||||||
{
|
{
|
||||||
LOG_DEBUG("State: BOOT");
|
LOG_POWERFSM("State: bootEnter");
|
||||||
}
|
}
|
||||||
|
|
||||||
State stateSHUTDOWN(shutdownEnter, NULL, NULL, "SHUTDOWN");
|
State stateSHUTDOWN(shutdownEnter, NULL, NULL, "SHUTDOWN");
|
||||||
@@ -319,11 +322,6 @@ void PowerFSM_setup()
|
|||||||
// if any packet destined for phone arrives, turn on bluetooth at least
|
// if any packet destined for phone arrives, turn on bluetooth at least
|
||||||
powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone");
|
powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone");
|
||||||
|
|
||||||
// Removed 2.7: we don't show the nodes individually for every node on the screen anymore
|
|
||||||
// powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
|
||||||
// powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
|
||||||
// powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
|
||||||
|
|
||||||
// Show the received text message
|
// Show the received text message
|
||||||
powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text");
|
powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text");
|
||||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text");
|
powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text");
|
||||||
@@ -372,7 +370,7 @@ void PowerFSM_setup()
|
|||||||
// Don't add power saving transitions if we are a power saving tracker or sensor or have Wifi enabled. Sleep will be initiated
|
// Don't add power saving transitions if we are a power saving tracker or sensor or have Wifi enabled. Sleep will be initiated
|
||||||
// through the modules
|
// through the modules
|
||||||
|
|
||||||
#if HAS_WIFI || !defined(MESHTASTIC_EXCLUDE_WIFI)
|
#if HAS_WIFI && !defined(MESHTASTIC_EXCLUDE_WIFI)
|
||||||
bool isTrackerOrSensor = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER ||
|
bool isTrackerOrSensor = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER ||
|
||||||
config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER ||
|
config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER ||
|
||||||
config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR;
|
config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR;
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#ifdef PowerFSMDebug
|
||||||
|
#define LOG_POWERFSM(...) LOG_DEBUG(__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define LOG_POWERFSM(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
// See sw-design.md for documentation
|
// See sw-design.md for documentation
|
||||||
|
|
||||||
#define EVENT_PRESS 1
|
#define EVENT_PRESS 1
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "concurrency/OSThread.h"
|
#include "concurrency/OSThread.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
#include "memGet.h"
|
||||||
#include "mesh/generated/meshtastic/mesh.pb.h"
|
#include "mesh/generated/meshtastic/mesh.pb.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -57,7 +58,7 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ARCH_PORTDUINO
|
#ifdef ARCH_PORTDUINO
|
||||||
bool color = !settingsMap[ascii_logs];
|
bool color = !portduino_config.ascii_logs;
|
||||||
#else
|
#else
|
||||||
bool color = true;
|
bool color = true;
|
||||||
#endif
|
#endif
|
||||||
@@ -99,7 +100,7 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
|
|||||||
size_t r = 0;
|
size_t r = 0;
|
||||||
|
|
||||||
#ifdef ARCH_PORTDUINO
|
#ifdef ARCH_PORTDUINO
|
||||||
bool color = !settingsMap[ascii_logs];
|
bool color = !portduino_config.ascii_logs;
|
||||||
#else
|
#else
|
||||||
bool color = true;
|
bool color = true;
|
||||||
#endif
|
#endif
|
||||||
@@ -166,6 +167,16 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
|
|||||||
print(thread->ThreadName);
|
print(thread->ThreadName);
|
||||||
print("] ");
|
print("] ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_HEAP
|
||||||
|
// Add heap free space bytes prefix before every log message
|
||||||
|
#ifdef ARCH_PORTDUINO
|
||||||
|
::printf("[heap %u] ", memGet.getFreeHeap());
|
||||||
|
#else
|
||||||
|
printf("[heap %u] ", memGet.getFreeHeap());
|
||||||
|
#endif
|
||||||
|
#endif // DEBUG_HEAP
|
||||||
|
|
||||||
r += vprintf(logLevel, format, arg);
|
r += vprintf(logLevel, format, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,7 +299,7 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...)
|
|||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
// level trace is special, two possible ways to handle it.
|
// level trace is special, two possible ways to handle it.
|
||||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) {
|
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) {
|
||||||
if (settingsStrings[traceFilename] != "") {
|
if (portduino_config.traceFilename != "") {
|
||||||
va_list arg;
|
va_list arg;
|
||||||
va_start(arg, format);
|
va_start(arg, format);
|
||||||
try {
|
try {
|
||||||
@@ -297,18 +308,18 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...)
|
|||||||
}
|
}
|
||||||
va_end(arg);
|
va_end(arg);
|
||||||
}
|
}
|
||||||
if (settingsMap[logoutputlevel] < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) {
|
if (portduino_config.logoutputlevel < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) {
|
||||||
delete[] newFormat;
|
delete[] newFormat;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (settingsMap[logoutputlevel] < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) {
|
if (portduino_config.logoutputlevel < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) {
|
||||||
delete[] newFormat;
|
delete[] newFormat;
|
||||||
return;
|
return;
|
||||||
} else if (settingsMap[logoutputlevel] < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) {
|
} else if (portduino_config.logoutputlevel < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) {
|
||||||
delete[] newFormat;
|
delete[] newFormat;
|
||||||
return;
|
return;
|
||||||
} else if (settingsMap[logoutputlevel] < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) {
|
} else if (portduino_config.logoutputlevel < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) {
|
||||||
delete[] newFormat;
|
delete[] newFormat;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,14 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "time.h"
|
#include "time.h"
|
||||||
|
|
||||||
|
#if defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT
|
||||||
|
#define IS_USB_SERIAL
|
||||||
|
#ifdef SERIAL_HAS_ON_RECEIVE
|
||||||
|
#undef SERIAL_HAS_ON_RECEIVE
|
||||||
|
#endif
|
||||||
|
#include "HWCDC.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef RP2040_SLOW_CLOCK
|
#ifdef RP2040_SLOW_CLOCK
|
||||||
#define Port Serial2
|
#define Port Serial2
|
||||||
#else
|
#else
|
||||||
@@ -22,7 +30,12 @@ SerialConsole *console;
|
|||||||
|
|
||||||
void consoleInit()
|
void consoleInit()
|
||||||
{
|
{
|
||||||
new SerialConsole(); // Must be dynamically allocated because we are now inheriting from thread
|
auto sc = new SerialConsole(); // Must be dynamically allocated because we are now inheriting from thread
|
||||||
|
|
||||||
|
#if defined(SERIAL_HAS_ON_RECEIVE)
|
||||||
|
// onReceive does only exist for HardwareSerial not for USB CDC serial
|
||||||
|
Port.onReceive([sc]() { sc->rxInt(); });
|
||||||
|
#endif
|
||||||
DEBUG_PORT.rpInit(); // Simply sets up semaphore
|
DEBUG_PORT.rpInit(); // Simply sets up semaphore
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +50,7 @@ void consolePrintf(const char *format, ...)
|
|||||||
|
|
||||||
SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), concurrency::OSThread("SerialConsole")
|
SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), concurrency::OSThread("SerialConsole")
|
||||||
{
|
{
|
||||||
|
api_type = TYPE_SERIAL;
|
||||||
assert(!console);
|
assert(!console);
|
||||||
console = this;
|
console = this;
|
||||||
canWrite = false; // We don't send packets to our port until it has talked to us first
|
canWrite = false; // We don't send packets to our port until it has talked to us first
|
||||||
@@ -65,14 +79,21 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
|
|||||||
int32_t SerialConsole::runOnce()
|
int32_t SerialConsole::runOnce()
|
||||||
{
|
{
|
||||||
#ifdef HELTEC_MESH_SOLAR
|
#ifdef HELTEC_MESH_SOLAR
|
||||||
//After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module.
|
// After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module.
|
||||||
if(moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port
|
if (moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port &&
|
||||||
&& moduleConfig.serial.mode==meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)
|
moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG) {
|
||||||
{
|
|
||||||
return 250;
|
return 250;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return runOncePart();
|
|
||||||
|
int32_t delay = runOncePart();
|
||||||
|
#if defined(SERIAL_HAS_ON_RECEIVE) || defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||||
|
return Port.available() ? delay : INT32_MAX;
|
||||||
|
#elif defined(IS_USB_SERIAL)
|
||||||
|
return HWCDC::isPlugged() ? delay : (1000 * 20);
|
||||||
|
#else
|
||||||
|
return delay;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void SerialConsole::flush()
|
void SerialConsole::flush()
|
||||||
@@ -80,6 +101,18 @@ void SerialConsole::flush()
|
|||||||
Port.flush();
|
Port.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// trigger tx of serial data
|
||||||
|
void SerialConsole::onNowHasData(uint32_t fromRadioNum)
|
||||||
|
{
|
||||||
|
setIntervalFromNow(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// trigger rx of serial data
|
||||||
|
void SerialConsole::rxInt()
|
||||||
|
{
|
||||||
|
setIntervalFromNow(0);
|
||||||
|
}
|
||||||
|
|
||||||
// For the serial port we can't really detect if any client is on the other side, so instead just look for recent messages
|
// For the serial port we can't really detect if any client is on the other side, so instead just look for recent messages
|
||||||
bool SerialConsole::checkIsConnected()
|
bool SerialConsole::checkIsConnected()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,11 +32,14 @@ class SerialConsole : public StreamAPI, public RedirectablePrint, private concur
|
|||||||
virtual int32_t runOnce() override;
|
virtual int32_t runOnce() override;
|
||||||
|
|
||||||
void flush();
|
void flush();
|
||||||
|
void rxInt();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Check the current underlying physical link to see if the client is currently connected
|
/// Check the current underlying physical link to see if the client is currently connected
|
||||||
virtual bool checkIsConnected() override;
|
virtual bool checkIsConnected() override;
|
||||||
|
|
||||||
|
virtual void onNowHasData(uint32_t fromRadioNum) override;
|
||||||
|
|
||||||
/// Possibly switch to protobufs if we see a valid protobuf message
|
/// Possibly switch to protobufs if we see a valid protobuf message
|
||||||
virtual void log_to_serial(const char *logLevel, const char *format, va_list arg);
|
virtual void log_to_serial(const char *logLevel, const char *format, va_list arg);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
BuzzerFeedbackThread *buzzerFeedbackThread;
|
BuzzerFeedbackThread *buzzerFeedbackThread;
|
||||||
|
|
||||||
BuzzerFeedbackThread::BuzzerFeedbackThread() : OSThread("BuzzerFeedback")
|
BuzzerFeedbackThread::BuzzerFeedbackThread()
|
||||||
{
|
{
|
||||||
if (inputBroker)
|
if (inputBroker)
|
||||||
inputObserver.observe(inputBroker);
|
inputObserver.observe(inputBroker);
|
||||||
@@ -15,24 +15,24 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
|
|||||||
{
|
{
|
||||||
// Only provide feedback if buzzer is enabled for notifications
|
// Only provide feedback if buzzer is enabled for notifications
|
||||||
if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DISABLED ||
|
if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DISABLED ||
|
||||||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_NOTIFICATIONS_ONLY) {
|
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_NOTIFICATIONS_ONLY ||
|
||||||
|
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY) {
|
||||||
return 0; // Let other handlers process the event
|
return 0; // Let other handlers process the event
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track last event time for potential future use
|
|
||||||
lastEventTime = millis();
|
|
||||||
needsUpdate = true;
|
|
||||||
|
|
||||||
// Handle different input events with appropriate buzzer feedback
|
// Handle different input events with appropriate buzzer feedback
|
||||||
switch (event->inputEvent) {
|
switch (event->inputEvent) {
|
||||||
case INPUT_BROKER_USER_PRESS:
|
case INPUT_BROKER_USER_PRESS:
|
||||||
case INPUT_BROKER_ALT_PRESS:
|
case INPUT_BROKER_ALT_PRESS:
|
||||||
case INPUT_BROKER_SELECT:
|
case INPUT_BROKER_SELECT:
|
||||||
|
case INPUT_BROKER_SELECT_LONG:
|
||||||
playBeep(); // Confirmation feedback
|
playBeep(); // Confirmation feedback
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case INPUT_BROKER_UP:
|
case INPUT_BROKER_UP:
|
||||||
|
case INPUT_BROKER_UP_LONG:
|
||||||
case INPUT_BROKER_DOWN:
|
case INPUT_BROKER_DOWN:
|
||||||
|
case INPUT_BROKER_DOWN_LONG:
|
||||||
case INPUT_BROKER_LEFT:
|
case INPUT_BROKER_LEFT:
|
||||||
case INPUT_BROKER_RIGHT:
|
case INPUT_BROKER_RIGHT:
|
||||||
playChirp(); // Navigation feedback
|
playChirp(); // Navigation feedback
|
||||||
@@ -58,15 +58,4 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return 0; // Allow other handlers to process the event
|
return 0; // Allow other handlers to process the event
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t BuzzerFeedbackThread::runOnce()
|
|
||||||
{
|
|
||||||
// This thread is primarily event-driven, but we can use runOnce
|
|
||||||
// for any periodic tasks if needed in the future
|
|
||||||
|
|
||||||
needsUpdate = false;
|
|
||||||
|
|
||||||
// Run every 100ms when active, less frequently when idle
|
|
||||||
return needsUpdate ? 100 : 1000;
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
#include "concurrency/OSThread.h"
|
#include "concurrency/OSThread.h"
|
||||||
#include "input/InputBroker.h"
|
#include "input/InputBroker.h"
|
||||||
|
|
||||||
class BuzzerFeedbackThread : public concurrency::OSThread
|
class BuzzerFeedbackThread
|
||||||
{
|
{
|
||||||
CallbackObserver<BuzzerFeedbackThread, const InputEvent *> inputObserver =
|
CallbackObserver<BuzzerFeedbackThread, const InputEvent *> inputObserver =
|
||||||
CallbackObserver<BuzzerFeedbackThread, const InputEvent *>(this, &BuzzerFeedbackThread::handleInputEvent);
|
CallbackObserver<BuzzerFeedbackThread, const InputEvent *>(this, &BuzzerFeedbackThread::handleInputEvent);
|
||||||
@@ -12,13 +12,6 @@ class BuzzerFeedbackThread : public concurrency::OSThread
|
|||||||
public:
|
public:
|
||||||
BuzzerFeedbackThread();
|
BuzzerFeedbackThread();
|
||||||
int handleInputEvent(const InputEvent *event);
|
int handleInputEvent(const InputEvent *event);
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual int32_t runOnce() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint32_t lastEventTime = 0;
|
|
||||||
bool needsUpdate = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern BuzzerFeedbackThread *buzzerFeedbackThread;
|
extern BuzzerFeedbackThread *buzzerFeedbackThread;
|
||||||
|
|||||||
@@ -90,7 +90,9 @@ void OSThread::run()
|
|||||||
if (heap < newHeap)
|
if (heap < newHeap)
|
||||||
LOG_HEAP("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap);
|
LOG_HEAP("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef DEBUG_LOOP_TIMING
|
||||||
|
LOG_DEBUG("====== Thread next run in: %d", newDelay);
|
||||||
|
#endif
|
||||||
runned();
|
runned();
|
||||||
|
|
||||||
if (newDelay >= 0)
|
if (newDelay >= 0)
|
||||||
|
|||||||
@@ -33,6 +33,32 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#include "pcf8563.h"
|
#include "pcf8563.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Offer chance for variant-specific defines */
|
||||||
|
#include "variant.h"
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Display feature overrides
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Allow build environments to opt-in explicitly to the E-Ink UI stack while
|
||||||
|
// keeping headless targets slim by default. Existing variants that already
|
||||||
|
// define USE_EINK continue to work without additional flags.
|
||||||
|
#ifndef MESHTASTIC_USE_EINK_UI
|
||||||
|
#ifdef USE_EINK
|
||||||
|
#define MESHTASTIC_USE_EINK_UI 1
|
||||||
|
#else
|
||||||
|
#define MESHTASTIC_USE_EINK_UI 0
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if MESHTASTIC_USE_EINK_UI
|
||||||
|
#ifndef USE_EINK
|
||||||
|
#define USE_EINK
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#undef USE_EINK
|
||||||
|
#endif
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Version
|
// Version
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -117,6 +143,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define SX126X_MAX_POWER 22
|
#define SX126X_MAX_POWER 22
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_GC1109_PA
|
||||||
|
// Power Amps are often non-linear, so we can use an array of values for the power curve
|
||||||
|
#define NUM_PA_POINTS 22
|
||||||
|
#define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef RAK13302
|
||||||
|
#define NUM_PA_POINTS 22
|
||||||
|
#define TX_GAIN_LORA 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8
|
||||||
|
#endif
|
||||||
|
|
||||||
// Default system gain to 0 if not defined
|
// Default system gain to 0 if not defined
|
||||||
#ifndef TX_GAIN_LORA
|
#ifndef TX_GAIN_LORA
|
||||||
#define TX_GAIN_LORA 0
|
#define TX_GAIN_LORA 0
|
||||||
@@ -214,6 +251,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define ICM20948_ADDR_ALT 0x68
|
#define ICM20948_ADDR_ALT 0x68
|
||||||
#define BHI260AP_ADDR 0x28
|
#define BHI260AP_ADDR 0x28
|
||||||
#define BMM150_ADDR 0x13
|
#define BMM150_ADDR 0x13
|
||||||
|
#define DA217_ADDR 0x26
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// LED
|
// LED
|
||||||
@@ -235,7 +273,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
// Touchscreen
|
// Touchscreen
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
#define FT6336U_ADDR 0x48
|
#define FT6336U_ADDR 0x48
|
||||||
#define CST328_ADDR 0x1A
|
#define CST328_ADDR 0x1A // same address as CST226SE
|
||||||
|
#define CHSC6X_ADDR 0x2E
|
||||||
|
#define CST226SE_ADDR_ALT 0x5A
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected)
|
// RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected)
|
||||||
@@ -254,14 +294,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
// convert 24-bit color to 16-bit (56K)
|
// convert 24-bit color to 16-bit (56K)
|
||||||
#define COLOR565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3))
|
#define COLOR565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3))
|
||||||
|
|
||||||
/* Step #1: offer chance for variant-specific defines */
|
|
||||||
#include "variant.h"
|
|
||||||
|
|
||||||
#if defined(VEXT_ENABLE) && !defined(VEXT_ON_VALUE)
|
#if defined(VEXT_ENABLE) && !defined(VEXT_ON_VALUE)
|
||||||
// Older variant.h files might not be defining this value, so stay with the old default
|
// Older variant.h files might not be defining this value, so stay with the old default
|
||||||
#define VEXT_ON_VALUE LOW
|
#define VEXT_ON_VALUE LOW
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Rotary encoder
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
#ifndef ROTARY_DELAY
|
||||||
|
#define ROTARY_DELAY 5
|
||||||
|
#endif
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// GPS
|
// GPS
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -350,6 +394,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#ifndef HAS_BLUETOOTH
|
#ifndef HAS_BLUETOOTH
|
||||||
#define HAS_BLUETOOTH 0
|
#define HAS_BLUETOOTH 0
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef USE_TFTDISPLAY
|
||||||
|
#define USE_TFTDISPLAY 0
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef HW_VENDOR
|
#ifndef HW_VENDOR
|
||||||
#error HW_VENDOR must be defined
|
#error HW_VENDOR must be defined
|
||||||
@@ -376,6 +423,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define HAS_RGB_LED
|
#define HAS_RGB_LED
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef LED_STATE_OFF
|
||||||
|
#define LED_STATE_OFF 0
|
||||||
|
#endif
|
||||||
|
#ifndef LED_STATE_ON
|
||||||
|
#define LED_STATE_ON 1
|
||||||
|
#endif
|
||||||
|
|
||||||
// default mapping of pins
|
// default mapping of pins
|
||||||
#if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN)
|
#if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN)
|
||||||
#define ALT_BUTTON_PIN PIN_BUTTON2
|
#define ALT_BUTTON_PIN PIN_BUTTON2
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ ScanI2C::FoundDevice ScanI2C::firstScreen() const
|
|||||||
|
|
||||||
ScanI2C::FoundDevice ScanI2C::firstRTC() const
|
ScanI2C::FoundDevice ScanI2C::firstRTC() const
|
||||||
{
|
{
|
||||||
ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563};
|
ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563, RTC_RX8130CE};
|
||||||
return firstOfOrNONE(2, types);
|
return firstOfOrNONE(3, types);
|
||||||
}
|
}
|
||||||
|
|
||||||
ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
|
ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class ScanI2C
|
|||||||
SCREEN_ST7567,
|
SCREEN_ST7567,
|
||||||
RTC_RV3028,
|
RTC_RV3028,
|
||||||
RTC_PCF8563,
|
RTC_PCF8563,
|
||||||
|
RTC_RX8130CE,
|
||||||
CARDKB,
|
CARDKB,
|
||||||
TDECKKB,
|
TDECKKB,
|
||||||
BBQ10KB,
|
BBQ10KB,
|
||||||
@@ -81,7 +82,11 @@ class ScanI2C
|
|||||||
BHI260AP,
|
BHI260AP,
|
||||||
BMM150,
|
BMM150,
|
||||||
TSL2561,
|
TSL2561,
|
||||||
DRV2605
|
DRV2605,
|
||||||
|
BH1750,
|
||||||
|
DA217,
|
||||||
|
CHSC6X,
|
||||||
|
CST226SE
|
||||||
} DeviceType;
|
} DeviceType;
|
||||||
|
|
||||||
// typedef uint8_t DeviceAddress;
|
// typedef uint8_t DeviceAddress;
|
||||||
|
|||||||
16
src/detect/ScanI2CConsumer.cpp
Normal file
16
src/detect/ScanI2CConsumer.cpp
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#include "ScanI2CConsumer.h"
|
||||||
|
#include <forward_list>
|
||||||
|
|
||||||
|
static std::forward_list<ScanI2CConsumer *> ScanI2CConsumers;
|
||||||
|
|
||||||
|
ScanI2CConsumer::ScanI2CConsumer()
|
||||||
|
{
|
||||||
|
ScanI2CConsumers.push_front(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScanI2CCompleted(ScanI2C *i2cScanner)
|
||||||
|
{
|
||||||
|
for (ScanI2CConsumer *consumer : ScanI2CConsumers) {
|
||||||
|
consumer->i2cScanFinished(i2cScanner);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/detect/ScanI2CConsumer.h
Normal file
13
src/detect/ScanI2CConsumer.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ScanI2C.h"
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
class ScanI2CConsumer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ScanI2CConsumer();
|
||||||
|
virtual void i2cScanFinished(ScanI2C *i2cScanner) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void ScanI2CCompleted(ScanI2C *i2cScanner);
|
||||||
@@ -106,6 +106,7 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation
|
|||||||
if (i2cBus->available())
|
if (i2cBus->available())
|
||||||
i2cBus->read();
|
i2cBus->read();
|
||||||
}
|
}
|
||||||
|
LOG_DEBUG("Register value: 0x%x", value);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,6 +198,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||||||
#ifdef PCF8563_RTC
|
#ifdef PCF8563_RTC
|
||||||
SCAN_SIMPLE_CASE(PCF8563_RTC, RTC_PCF8563, "PCF8563", (uint8_t)addr.address)
|
SCAN_SIMPLE_CASE(PCF8563_RTC, RTC_PCF8563, "PCF8563", (uint8_t)addr.address)
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef RX8130CE_RTC
|
||||||
|
SCAN_SIMPLE_CASE(RX8130CE_RTC, RTC_RX8130CE, "RX8130CE", (uint8_t)addr.address)
|
||||||
|
#endif
|
||||||
|
|
||||||
case CARDKB_ADDR:
|
case CARDKB_ADDR:
|
||||||
// Do we have the RAK14006 instead?
|
// Do we have the RAK14006 instead?
|
||||||
@@ -374,13 +378,13 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||||||
}
|
}
|
||||||
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT
|
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT
|
||||||
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
|
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
|
||||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2);
|
||||||
if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c || registerValue == 0xc8d) {
|
if (registerValue == 0x5449) {
|
||||||
type = SHT4X;
|
|
||||||
logFoundDevice("SHT4X", (uint8_t)addr.address);
|
|
||||||
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
|
|
||||||
type = OPT3001;
|
type = OPT3001;
|
||||||
logFoundDevice("OPT3001", (uint8_t)addr.address);
|
logFoundDevice("OPT3001", (uint8_t)addr.address);
|
||||||
|
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2) != 0) { // unique SHT4x serial number
|
||||||
|
type = SHT4X;
|
||||||
|
logFoundDevice("SHT4X", (uint8_t)addr.address);
|
||||||
} else {
|
} else {
|
||||||
type = SHT31;
|
type = SHT31;
|
||||||
logFoundDevice("SHT31", (uint8_t)addr.address);
|
logFoundDevice("SHT31", (uint8_t)addr.address);
|
||||||
@@ -461,8 +465,23 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555", (uint8_t)addr.address);
|
|
||||||
SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700", (uint8_t)addr.address);
|
||||||
|
case TCA9555_ADDR:
|
||||||
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 1);
|
||||||
|
if (registerValue == 0x13) {
|
||||||
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1);
|
||||||
|
if (registerValue == 0x81) {
|
||||||
|
type = DA217;
|
||||||
|
logFoundDevice("DA217", (uint8_t)addr.address);
|
||||||
|
} else {
|
||||||
|
type = TCA9555;
|
||||||
|
logFoundDevice("TCA9555", (uint8_t)addr.address);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
type = TCA9555;
|
||||||
|
logFoundDevice("TCA9555", (uint8_t)addr.address);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case TSL25911_ADDR:
|
case TSL25911_ADDR:
|
||||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x12), 1);
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x12), 1);
|
||||||
if (registerValue == 0x50) {
|
if (registerValue == 0x50) {
|
||||||
@@ -480,8 +499,38 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||||||
SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(CST328_ADDR, CST328, "CST328", (uint8_t)addr.address);
|
case CST328_ADDR:
|
||||||
SCAN_SIMPLE_CASE(LTR553ALS_ADDR, LTR553ALS, "LTR553ALS", (uint8_t)addr.address);
|
// Do we have the CST328 or the CST226SE
|
||||||
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xAB), 1);
|
||||||
|
if (registerValue == 0xA9) {
|
||||||
|
type = CST226SE;
|
||||||
|
logFoundDevice("CST226SE", (uint8_t)addr.address);
|
||||||
|
} else {
|
||||||
|
type = CST328;
|
||||||
|
logFoundDevice("CST328", (uint8_t)addr.address);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
SCAN_SIMPLE_CASE(CHSC6X_ADDR, CHSC6X, "CHSC6X", (uint8_t)addr.address);
|
||||||
|
case LTR553ALS_ADDR:
|
||||||
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x86), 1); // Part ID register
|
||||||
|
if (registerValue == 0x92) { // LTR553ALS Part ID
|
||||||
|
type = LTR553ALS;
|
||||||
|
logFoundDevice("LTR553ALS", (uint8_t)addr.address);
|
||||||
|
} else {
|
||||||
|
// Test BH1750 - send power on command
|
||||||
|
i2cBus->beginTransmission(addr.address);
|
||||||
|
i2cBus->write(0x01); // Power On command
|
||||||
|
uint8_t bh1750_error = i2cBus->endTransmission();
|
||||||
|
if (bh1750_error == 0) {
|
||||||
|
type = BH1750;
|
||||||
|
logFoundDevice("BH1750", (uint8_t)addr.address);
|
||||||
|
} else {
|
||||||
|
LOG_INFO("Device found at address 0x%x was not able to be enumerated", (uint8_t)addr.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
SCAN_SIMPLE_CASE(BHI260AP_ADDR, BHI260AP, "BHI260AP", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(BHI260AP_ADDR, BHI260AP, "BHI260AP", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(BMM150_ADDR, BMM150, "BMM150", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(BMM150_ADDR, BMM150, "BMM150", (uint8_t)addr.address);
|
||||||
@@ -490,8 +539,12 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
case MLX90614_ADDR_DEF:
|
case MLX90614_ADDR_DEF:
|
||||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0e), 1);
|
// Do we have the MLX90614 or the MPR121KB or the CST226SE
|
||||||
if (registerValue == 0x5a) {
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x06), 1);
|
||||||
|
if (registerValue == 0xAB) {
|
||||||
|
type = CST226SE;
|
||||||
|
logFoundDevice("CST226SE", (uint8_t)addr.address);
|
||||||
|
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0e), 1) == 0x5a) {
|
||||||
type = MLX90614;
|
type = MLX90614;
|
||||||
logFoundDevice("MLX90614", (uint8_t)addr.address);
|
logFoundDevice("MLX90614", (uint8_t)addr.address);
|
||||||
} else {
|
} else {
|
||||||
@@ -509,6 +562,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||||||
case ICM20948_ADDR: // same as BMX160_ADDR
|
case ICM20948_ADDR: // same as BMX160_ADDR
|
||||||
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR
|
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR
|
||||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1);
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1);
|
||||||
|
#ifdef HAS_ICM20948
|
||||||
|
type = ICM20948;
|
||||||
|
logFoundDevice("ICM20948", (uint8_t)addr.address);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
if (registerValue == 0xEA) {
|
if (registerValue == 0xEA) {
|
||||||
type = ICM20948;
|
type = ICM20948;
|
||||||
logFoundDevice("ICM20948", (uint8_t)addr.address);
|
logFoundDevice("ICM20948", (uint8_t)addr.address);
|
||||||
@@ -577,7 +635,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
|
|||||||
scanPort(port, nullptr, 0);
|
scanPort(port, nullptr, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const
|
TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address)
|
||||||
{
|
{
|
||||||
if (address.port == ScanI2C::I2CPort::WIRE) {
|
if (address.port == ScanI2C::I2CPort::WIRE) {
|
||||||
return &Wire;
|
return &Wire;
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ class ScanI2CTwoWire : public ScanI2C
|
|||||||
|
|
||||||
ScanI2C::FoundDevice find(ScanI2C::DeviceType) const override;
|
ScanI2C::FoundDevice find(ScanI2C::DeviceType) const override;
|
||||||
|
|
||||||
TwoWire *fetchI2CBus(ScanI2C::DeviceAddress) const;
|
|
||||||
|
|
||||||
bool exists(ScanI2C::DeviceType) const override;
|
bool exists(ScanI2C::DeviceType) const override;
|
||||||
|
|
||||||
size_t countDevices() const override;
|
size_t countDevices() const override;
|
||||||
|
|
||||||
|
static TwoWire *fetchI2CBus(ScanI2C::DeviceAddress);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
FoundDevice firstOfOrNONE(size_t, DeviceType[]) const override;
|
FoundDevice firstOfOrNONE(size_t, DeviceType[]) const override;
|
||||||
|
|
||||||
|
|||||||
541
src/gps/GPS.cpp
541
src/gps/GPS.cpp
@@ -38,14 +38,16 @@ template <typename T, std::size_t N> std::size_t array_count(const T (&)[N])
|
|||||||
return N;
|
return N;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
|
#ifndef GPS_SERIAL_PORT
|
||||||
#if defined(GPS_SERIAL_PORT)
|
#define GPS_SERIAL_PORT Serial1
|
||||||
HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT;
|
|
||||||
#else
|
|
||||||
HardwareSerial *GPS::_serial_gps = &Serial1;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(ARCH_NRF52)
|
||||||
|
Uart *GPS::_serial_gps = &GPS_SERIAL_PORT;
|
||||||
|
#elif defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
|
||||||
|
HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT;
|
||||||
#elif defined(ARCH_RP2040)
|
#elif defined(ARCH_RP2040)
|
||||||
SerialUART *GPS::_serial_gps = &Serial1;
|
SerialUART *GPS::_serial_gps = &GPS_SERIAL_PORT;
|
||||||
#else
|
#else
|
||||||
HardwareSerial *GPS::_serial_gps = nullptr;
|
HardwareSerial *GPS::_serial_gps = nullptr;
|
||||||
#endif
|
#endif
|
||||||
@@ -240,6 +242,9 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
|
|||||||
buffer[bytesRead] = b;
|
buffer[bytesRead] = b;
|
||||||
bytesRead++;
|
bytesRead++;
|
||||||
if ((bytesRead == 767) || (b == '\r')) {
|
if ((bytesRead == 767) || (b == '\r')) {
|
||||||
|
#ifdef GPS_DEBUG
|
||||||
|
LOG_DEBUG(debugmsg.c_str());
|
||||||
|
#endif
|
||||||
if (strnstr((char *)buffer, message, bytesRead) != nullptr) {
|
if (strnstr((char *)buffer, message, bytesRead) != nullptr) {
|
||||||
#ifdef GPS_DEBUG
|
#ifdef GPS_DEBUG
|
||||||
LOG_DEBUG("Found: %s", message); // Log the found message
|
LOG_DEBUG("Found: %s", message); // Log the found message
|
||||||
@@ -247,9 +252,6 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
|
|||||||
return GNSS_RESPONSE_OK;
|
return GNSS_RESPONSE_OK;
|
||||||
} else {
|
} else {
|
||||||
bytesRead = 0;
|
bytesRead = 0;
|
||||||
#ifdef GPS_DEBUG
|
|
||||||
LOG_DEBUG(debugmsg.c_str());
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -494,38 +496,27 @@ bool GPS::setup()
|
|||||||
if (!didSerialInit) {
|
if (!didSerialInit) {
|
||||||
int msglen = 0;
|
int msglen = 0;
|
||||||
if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) {
|
if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) {
|
||||||
#ifdef TRACKER_T1000_E
|
|
||||||
// add power up/down strategy, improve ag3335 detection success
|
|
||||||
digitalWrite(PIN_GPS_EN, LOW);
|
|
||||||
delay(500);
|
|
||||||
digitalWrite(GPS_VRTC_EN, LOW);
|
|
||||||
delay(1000);
|
|
||||||
digitalWrite(GPS_VRTC_EN, HIGH);
|
|
||||||
delay(500);
|
|
||||||
digitalWrite(PIN_GPS_EN, HIGH);
|
|
||||||
delay(1000);
|
|
||||||
#endif
|
|
||||||
if (probeTries < GPS_PROBETRIES) {
|
if (probeTries < GPS_PROBETRIES) {
|
||||||
LOG_DEBUG("Probe for GPS at %d", serialSpeeds[speedSelect]);
|
|
||||||
gnssModel = probe(serialSpeeds[speedSelect]);
|
gnssModel = probe(serialSpeeds[speedSelect]);
|
||||||
if (gnssModel == GNSS_MODEL_UNKNOWN) {
|
if (gnssModel == GNSS_MODEL_UNKNOWN) {
|
||||||
if (++speedSelect == array_count(serialSpeeds)) {
|
if (currentStep == 0 && ++speedSelect == array_count(serialSpeeds)) {
|
||||||
speedSelect = 0;
|
speedSelect = 0;
|
||||||
++probeTries;
|
++probeTries;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Rare Serial Speeds
|
// Rare Serial Speeds
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||||
if (probeTries == GPS_PROBETRIES) {
|
if (probeTries == GPS_PROBETRIES) {
|
||||||
LOG_DEBUG("Probe for GPS at %d", rareSerialSpeeds[speedSelect]);
|
|
||||||
gnssModel = probe(rareSerialSpeeds[speedSelect]);
|
gnssModel = probe(rareSerialSpeeds[speedSelect]);
|
||||||
if (gnssModel == GNSS_MODEL_UNKNOWN) {
|
if (gnssModel == GNSS_MODEL_UNKNOWN) {
|
||||||
if (++speedSelect == array_count(rareSerialSpeeds)) {
|
if (currentStep == 0 && ++speedSelect == array_count(rareSerialSpeeds)) {
|
||||||
LOG_WARN("Give up on GPS probe and set to %d", GPS_BAUDRATE);
|
LOG_WARN("Give up on GPS probe and set to %d", GPS_BAUDRATE);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gnssModel != GNSS_MODEL_UNKNOWN) {
|
if (gnssModel != GNSS_MODEL_UNKNOWN) {
|
||||||
@@ -807,6 +798,14 @@ bool GPS::setup()
|
|||||||
} else {
|
} else {
|
||||||
LOG_INFO("GNSS module configuration saved!");
|
LOG_INFO("GNSS module configuration saved!");
|
||||||
}
|
}
|
||||||
|
} else if (gnssModel == GNSS_MODEL_CM121) {
|
||||||
|
// only ask for RMC and GGA
|
||||||
|
// enable GGA
|
||||||
|
_serial_gps->write("$CFGMSG,0,0,1,1*1B\r\n");
|
||||||
|
delay(250);
|
||||||
|
// enable RMC
|
||||||
|
_serial_gps->write("$CFGMSG,0,4,1,1*1F\r\n");
|
||||||
|
delay(250);
|
||||||
}
|
}
|
||||||
didSerialInit = true;
|
didSerialInit = true;
|
||||||
}
|
}
|
||||||
@@ -1023,7 +1022,7 @@ void GPS::down()
|
|||||||
LOG_DEBUG("%us until next search", sleepTime / 1000);
|
LOG_DEBUG("%us until next search", sleepTime / 1000);
|
||||||
|
|
||||||
// If update interval less than 10 seconds, no attempt to sleep
|
// If update interval less than 10 seconds, no attempt to sleep
|
||||||
if (updateInterval <= 10 * 1000UL || sleepTime == 0)
|
if (updateInterval <= GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS || sleepTime == 0)
|
||||||
setPowerState(GPS_IDLE);
|
setPowerState(GPS_IDLE);
|
||||||
|
|
||||||
else {
|
else {
|
||||||
@@ -1084,7 +1083,7 @@ int32_t GPS::runOnce()
|
|||||||
return disable();
|
return disable();
|
||||||
}
|
}
|
||||||
if (!setup())
|
if (!setup())
|
||||||
return 2000; // Setup failed, re-run in two seconds
|
return currentDelay; // Setup failed, re-run in two seconds
|
||||||
|
|
||||||
// We have now loaded our saved preferences from flash
|
// We have now loaded our saved preferences from flash
|
||||||
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
||||||
@@ -1094,11 +1093,29 @@ int32_t GPS::runOnce()
|
|||||||
publishUpdate();
|
publishUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repeaters have no need for GPS
|
// ======================== GPS_ACTIVE state ========================
|
||||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
|
// In GPS_ACTIVE state, GPS is powered on and we're receiving NMEA messages.
|
||||||
return disable();
|
// We use the following logic to determine when to update the local position
|
||||||
}
|
// or time by running GPS::publishUpdate.
|
||||||
|
// Note: Local position update is asynchronous to position broadcast. We
|
||||||
|
// generally run this state every gps_update_interval seconds, and in most cases
|
||||||
|
// gps_update_interval is faster than the position broadcast interval so there's a
|
||||||
|
// fresh position ready when the device wants to broadcast one on the mesh.
|
||||||
|
//
|
||||||
|
// 1. Got a time for the first time --> set the time, don't publish.
|
||||||
|
// 2. Got a lock for the first time
|
||||||
|
// --> If gps_update_interval is <= 10s --> publishUpdate
|
||||||
|
// --> Otherwise, hold for MIN(gps_update_interval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS, 20s)
|
||||||
|
// 3. Got a lock after turning back on
|
||||||
|
// --> If gps_update_interval is <= 10s --> publishUpdate
|
||||||
|
// --> Otherwise, hold for MIN(gps_update_interval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS, 20s)
|
||||||
|
// 4. Hold has expired
|
||||||
|
// --> If we have a time and a location --> publishUpdate
|
||||||
|
// --> down()
|
||||||
|
// 5. Search time has expired
|
||||||
|
// --> If we have a time and a location --> publishUpdate
|
||||||
|
// --> If we had a location before but don't now --> publishUpdate
|
||||||
|
// --> down()
|
||||||
if (whileActive()) {
|
if (whileActive()) {
|
||||||
// if we have received valid NMEA claim we are connected
|
// if we have received valid NMEA claim we are connected
|
||||||
setConnected();
|
setConnected();
|
||||||
@@ -1108,55 +1125,81 @@ int32_t GPS::runOnce()
|
|||||||
if (!config.position.fixed_position && powerState != GPS_ACTIVE && scheduling.isUpdateDue())
|
if (!config.position.fixed_position && powerState != GPS_ACTIVE && scheduling.isUpdateDue())
|
||||||
up();
|
up();
|
||||||
|
|
||||||
// If we've already set time from the GPS, no need to ask the GPS
|
// quality of the previous fix. We set it to 0 when we go down, so it's a way
|
||||||
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
|
// to check if we're getting a lock after being GPS_OFF.
|
||||||
if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
|
|
||||||
gotTime = true;
|
|
||||||
shouldPublish = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t prev_fixQual = fixQual;
|
uint8_t prev_fixQual = fixQual;
|
||||||
bool gotLoc = lookForLocation();
|
|
||||||
if (gotLoc && !hasValidLocation) { // declare that we have location ASAP
|
|
||||||
LOG_DEBUG("hasValidLocation RISING EDGE");
|
|
||||||
hasValidLocation = true;
|
|
||||||
shouldPublish = true;
|
|
||||||
// Hold for 20secs after getting a lock to download ephemeris etc
|
|
||||||
fixHoldEnds = millis() + 20000;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gotLoc && prev_fixQual == 0) { // just got a lock after turning back on.
|
if (powerState == GPS_ACTIVE) {
|
||||||
fixHoldEnds = millis() + 20000;
|
// if gps_update_interval is <=10s, GPS never goes off, so we treat that differently
|
||||||
shouldPublish = true; // Publish immediately, since next publish is at end of hold
|
uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval);
|
||||||
}
|
|
||||||
|
|
||||||
bool tooLong = scheduling.searchedTooLong();
|
// 1. Got a time for the first time
|
||||||
if (tooLong)
|
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
|
||||||
LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time");
|
if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
|
||||||
|
gotTime = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Once we get a location we no longer desperately want an update
|
// 2. Got a lock for the first time, or 3. Got a lock after turning back on
|
||||||
if ((gotLoc && gotTime) || tooLong) {
|
bool gotLoc = lookForLocation();
|
||||||
|
if (gotLoc) {
|
||||||
|
#ifdef GPS_DEBUG
|
||||||
|
if (!hasValidLocation) { // declare that we have location ASAP
|
||||||
|
LOG_DEBUG("hasValidLocation RISING EDGE");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (updateInterval <= GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS) {
|
||||||
|
hasValidLocation = true;
|
||||||
|
shouldPublish = true;
|
||||||
|
} else if (!hasValidLocation || prev_fixQual == 0 || (fixHoldEnds + GPS_THREAD_INTERVAL) < millis()) {
|
||||||
|
hasValidLocation = true;
|
||||||
|
// Hold for up to 20secs after getting a lock to download ephemeris etc
|
||||||
|
uint32_t holdTime = updateInterval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS;
|
||||||
|
if (holdTime > GPS_FIX_HOLD_MAX_MS)
|
||||||
|
holdTime = GPS_FIX_HOLD_MAX_MS;
|
||||||
|
fixHoldEnds = millis() + holdTime;
|
||||||
|
#ifdef GPS_DEBUG
|
||||||
|
LOG_DEBUG("Holding for %ums after lock", holdTime);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tooLong = scheduling.searchedTooLong();
|
||||||
if (tooLong && !gotLoc) {
|
if (tooLong && !gotLoc) {
|
||||||
|
LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time");
|
||||||
// we didn't get a location during this ack window, therefore declare loss of lock
|
// we didn't get a location during this ack window, therefore declare loss of lock
|
||||||
if (hasValidLocation) {
|
if (hasValidLocation) {
|
||||||
LOG_DEBUG("hasValidLocation FALLING EDGE");
|
p = meshtastic_Position_init_default;
|
||||||
}
|
hasValidLocation = false;
|
||||||
p = meshtastic_Position_init_default;
|
shouldPublish = true;
|
||||||
hasValidLocation = false;
|
|
||||||
}
|
|
||||||
if (millis() > fixHoldEnds) {
|
|
||||||
shouldPublish = true; // publish our update at the end of the lock hold
|
|
||||||
publishUpdate();
|
|
||||||
down();
|
|
||||||
#ifdef GPS_DEBUG
|
#ifdef GPS_DEBUG
|
||||||
} else {
|
LOG_DEBUG("hasValidLocation FALLING EDGE");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hold has expired , Search time has expired, we got a time only, or we never needed to hold.
|
||||||
|
bool holdExpired = (fixHoldEnds != 0 && millis() > fixHoldEnds);
|
||||||
|
if (shouldPublish || tooLong || holdExpired) {
|
||||||
|
if (gotTime && hasValidLocation) {
|
||||||
|
shouldPublish = true;
|
||||||
|
}
|
||||||
|
if (shouldPublish) {
|
||||||
|
fixHoldEnds = 0;
|
||||||
|
publishUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// There's a chance we just got a time, so keep going to see if we can get a location too
|
||||||
|
if (tooLong || holdExpired) {
|
||||||
|
down();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef GPS_DEBUG
|
||||||
|
} else if (fixHoldEnds != 0) {
|
||||||
LOG_DEBUG("Holding for GPS data download: %d ms (numSats=%d)", fixHoldEnds - millis(), p.sats_in_view);
|
LOG_DEBUG("Holding for GPS data download: %d ms (numSats=%d)", fixHoldEnds - millis(), p.sats_in_view);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ===================== end GPS_ACTIVE state ========================
|
||||||
// If state has changed do a publish
|
|
||||||
publishUpdate();
|
|
||||||
|
|
||||||
if (config.position.fixed_position == true && hasValidLocation)
|
if (config.position.fixed_position == true && hasValidLocation)
|
||||||
return disable(); // This should trigger when we have a fixed position, and get that first position
|
return disable(); // This should trigger when we have a fixed position, and get that first position
|
||||||
@@ -1213,157 +1256,215 @@ static const char *DETECTED_MESSAGE = "%s detected";
|
|||||||
|
|
||||||
GnssModel_t GPS::probe(int serialSpeed)
|
GnssModel_t GPS::probe(int serialSpeed)
|
||||||
{
|
{
|
||||||
|
uint8_t buffer[768] = {0};
|
||||||
|
|
||||||
|
switch (currentStep) {
|
||||||
|
case 0: {
|
||||||
#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
|
#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
|
||||||
_serial_gps->end();
|
_serial_gps->end();
|
||||||
_serial_gps->begin(serialSpeed);
|
_serial_gps->begin(serialSpeed);
|
||||||
#elif defined(ARCH_RP2040)
|
#elif defined(ARCH_RP2040)
|
||||||
_serial_gps->end();
|
_serial_gps->end();
|
||||||
_serial_gps->setFIFOSize(256);
|
_serial_gps->setFIFOSize(256);
|
||||||
_serial_gps->begin(serialSpeed);
|
_serial_gps->begin(serialSpeed);
|
||||||
#else
|
#else
|
||||||
if (_serial_gps->baudRate() != serialSpeed) {
|
if (_serial_gps->baudRate() != serialSpeed) {
|
||||||
LOG_DEBUG("Set Baud to %i", serialSpeed);
|
LOG_DEBUG("Set GPS Baud to %i", serialSpeed);
|
||||||
_serial_gps->updateBaudRate(serialSpeed);
|
_serial_gps->updateBaudRate(serialSpeed);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
memset(&ublox_info, 0, sizeof(ublox_info));
|
memset(&ublox_info, 0, sizeof(ublox_info));
|
||||||
uint8_t buffer[768] = {0};
|
delay(100);
|
||||||
delay(100);
|
|
||||||
|
|
||||||
// Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices)
|
#if defined(PIN_GPS_RESET) && PIN_GPS_RESET != -1
|
||||||
_serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n");
|
digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); // assert for 10ms
|
||||||
delay(20);
|
delay(10);
|
||||||
// Close NMEA sequences on Ublox
|
digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE);
|
||||||
_serial_gps->write("$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n");
|
|
||||||
_serial_gps->write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n");
|
|
||||||
_serial_gps->write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n");
|
|
||||||
delay(20);
|
|
||||||
|
|
||||||
// Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A
|
// attempt to detect the chip based on boot messages
|
||||||
std::vector<ChipInfo> unicore = {{"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}};
|
std::vector<ChipInfo> passive_detect = {
|
||||||
PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500);
|
{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
|
||||||
|
{"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
|
||||||
std::vector<ChipInfo> atgm = {
|
{"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352},
|
||||||
{"ATGM336H", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H},
|
{"UC6580", "UC6580", GNSS_MODEL_UC6580},
|
||||||
/* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) based on AT6558 */
|
// as L76K is sort of a last ditch effort, we won't attempt to detect it by startup messages for now.
|
||||||
{"ATGM332D", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H}};
|
/*{"L76K", "SW=URANUS", GNSS_MODEL_MTK}*/};
|
||||||
PROBE_FAMILY("ATGM33xx Family", "$PCAS06,1*1A", atgm, 500);
|
GnssModel_t detectedDriver = getProbeResponse(500, passive_detect, serialSpeed);
|
||||||
|
if (detectedDriver != GNSS_MODEL_UNKNOWN) {
|
||||||
/* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */
|
return detectedDriver;
|
||||||
_serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume
|
}
|
||||||
_serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume
|
#endif
|
||||||
_serial_gps->write("$PAIR513*3D\r\n"); // save configuration
|
// Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices)
|
||||||
std::vector<ChipInfo> airoha = {{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
|
_serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n");
|
||||||
{"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
|
delay(20);
|
||||||
{"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352}};
|
// Close NMEA sequences on Ublox
|
||||||
PROBE_FAMILY("Airoha Family", "$PAIR021*39", airoha, 1000);
|
_serial_gps->write("$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n");
|
||||||
|
_serial_gps->write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n");
|
||||||
PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500);
|
_serial_gps->write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n");
|
||||||
PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500);
|
delay(20);
|
||||||
|
// Close NMEA sequences on CM121
|
||||||
// Close all NMEA sentences, valid for MTK3333 and MTK3339 platforms
|
_serial_gps->write("$CFGMSG,0,1,0,1*1B\r\n");
|
||||||
_serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n");
|
_serial_gps->write("$CFGMSG,0,2,0,1*18\r\n");
|
||||||
delay(20);
|
_serial_gps->write("$CFGMSG,0,3,0,1*19\r\n");
|
||||||
std::vector<ChipInfo> mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B}, {"PA1010D", "1010D", GNSS_MODEL_MTK_PA1010D},
|
currentDelay = 20;
|
||||||
{"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S}, {"LS20031", "MC-1513", GNSS_MODEL_MTK_L76B},
|
currentStep = 1;
|
||||||
{"L96", "Quectel-L96", GNSS_MODEL_MTK_L76B}, {"L80-R", "_3337_", GNSS_MODEL_MTK_L76B},
|
|
||||||
{"L80", "_3339_", GNSS_MODEL_MTK_L76B}};
|
|
||||||
|
|
||||||
PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500);
|
|
||||||
|
|
||||||
uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
|
|
||||||
UBXChecksum(cfg_rate, sizeof(cfg_rate));
|
|
||||||
clearBuffer();
|
|
||||||
_serial_gps->write(cfg_rate, sizeof(cfg_rate));
|
|
||||||
// Check that the returned response class and message ID are correct
|
|
||||||
GPS_RESPONSE response = getACK(0x06, 0x08, 750);
|
|
||||||
if (response == GNSS_RESPONSE_NONE) {
|
|
||||||
LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed);
|
|
||||||
return GNSS_MODEL_UNKNOWN;
|
return GNSS_MODEL_UNKNOWN;
|
||||||
} else if (response == GNSS_RESPONSE_FRAME_ERRORS) {
|
|
||||||
LOG_INFO("UBlox Frame Errors (baudrate %d)", serialSpeed);
|
|
||||||
}
|
}
|
||||||
|
case 1: {
|
||||||
|
|
||||||
memset(buffer, 0, sizeof(buffer));
|
// Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A,or CM121
|
||||||
uint8_t _message_MONVER[8] = {
|
std::vector<ChipInfo> unicore = {
|
||||||
0xB5, 0x62, // Sync message for UBX protocol
|
{"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}, {"CM121", "CM121", GNSS_MODEL_CM121}};
|
||||||
0x0A, 0x04, // Message class and ID (UBX-MON-VER)
|
PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500);
|
||||||
0x00, 0x00, // Length of payload (we're asking for an answer, so no payload)
|
currentDelay = 20;
|
||||||
0x00, 0x00 // Checksum
|
currentStep = 2;
|
||||||
};
|
return GNSS_MODEL_UNKNOWN;
|
||||||
// Get Ublox gnss module hardware and software info
|
}
|
||||||
UBXChecksum(_message_MONVER, sizeof(_message_MONVER));
|
case 2: {
|
||||||
clearBuffer();
|
std::vector<ChipInfo> atgm = {
|
||||||
_serial_gps->write(_message_MONVER, sizeof(_message_MONVER));
|
{"ATGM336H", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H},
|
||||||
|
/* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) based on AT6558 */
|
||||||
|
{"ATGM332D", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H}};
|
||||||
|
PROBE_FAMILY("ATGM33xx Family", "$PCAS06,1*1A", atgm, 500);
|
||||||
|
currentDelay = 20;
|
||||||
|
currentStep = 3;
|
||||||
|
return GNSS_MODEL_UNKNOWN;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
/* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */
|
||||||
|
_serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume
|
||||||
|
_serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume
|
||||||
|
_serial_gps->write("$PAIR513*3D\r\n"); // save configuration
|
||||||
|
std::vector<ChipInfo> airoha = {{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
|
||||||
|
{"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
|
||||||
|
{"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352}};
|
||||||
|
PROBE_FAMILY("Airoha Family", "$PAIR021*39", airoha, 1000);
|
||||||
|
currentDelay = 20;
|
||||||
|
currentStep = 4;
|
||||||
|
return GNSS_MODEL_UNKNOWN;
|
||||||
|
}
|
||||||
|
case 4: {
|
||||||
|
PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500);
|
||||||
|
PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500);
|
||||||
|
currentDelay = 20;
|
||||||
|
currentStep = 5;
|
||||||
|
return GNSS_MODEL_UNKNOWN;
|
||||||
|
}
|
||||||
|
case 5: {
|
||||||
|
|
||||||
uint16_t len = getACK(buffer, sizeof(buffer), 0x0A, 0x04, 1200);
|
// Close all NMEA sentences, valid for MTK3333 and MTK3339 platforms
|
||||||
if (len) {
|
_serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n");
|
||||||
uint16_t position = 0;
|
delay(20);
|
||||||
for (int i = 0; i < 30; i++) {
|
std::vector<ChipInfo> mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B}, {"PA1010D", "1010D", GNSS_MODEL_MTK_PA1010D},
|
||||||
ublox_info.swVersion[i] = buffer[position];
|
{"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S}, {"LS20031", "MC-1513", GNSS_MODEL_MTK_L76B},
|
||||||
position++;
|
{"L96", "Quectel-L96", GNSS_MODEL_MTK_L76B}, {"L80-R", "_3337_", GNSS_MODEL_MTK_L76B},
|
||||||
}
|
{"L80", "_3339_", GNSS_MODEL_MTK_L76B}};
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
ublox_info.hwVersion[i] = buffer[position];
|
|
||||||
position++;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (len >= position + 30) {
|
PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500);
|
||||||
for (int i = 0; i < 30; i++) {
|
currentDelay = 20;
|
||||||
ublox_info.extension[ublox_info.extensionNo][i] = buffer[position];
|
currentStep = 6;
|
||||||
position++;
|
return GNSS_MODEL_UNKNOWN;
|
||||||
}
|
}
|
||||||
ublox_info.extensionNo++;
|
case 6: {
|
||||||
if (ublox_info.extensionNo > 9)
|
uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
|
||||||
break;
|
UBXChecksum(cfg_rate, sizeof(cfg_rate));
|
||||||
}
|
clearBuffer();
|
||||||
|
_serial_gps->write(cfg_rate, sizeof(cfg_rate));
|
||||||
LOG_DEBUG("Module Info : ");
|
// Check that the returned response class and message ID are correct
|
||||||
LOG_DEBUG("Soft version: %s", ublox_info.swVersion);
|
GPS_RESPONSE response = getACK(0x06, 0x08, 750);
|
||||||
LOG_DEBUG("Hard version: %s", ublox_info.hwVersion);
|
if (response == GNSS_RESPONSE_NONE) {
|
||||||
LOG_DEBUG("Extensions:%d", ublox_info.extensionNo);
|
LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed);
|
||||||
for (int i = 0; i < ublox_info.extensionNo; i++) {
|
currentDelay = 2000;
|
||||||
LOG_DEBUG(" %s", ublox_info.extension[i]);
|
currentStep = 0;
|
||||||
|
return GNSS_MODEL_UNKNOWN;
|
||||||
|
} else if (response == GNSS_RESPONSE_FRAME_ERRORS) {
|
||||||
|
LOG_INFO("UBlox Frame Errors (baudrate %d)", serialSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(buffer, 0, sizeof(buffer));
|
memset(buffer, 0, sizeof(buffer));
|
||||||
|
uint8_t _message_MONVER[8] = {
|
||||||
|
0xB5, 0x62, // Sync message for UBX protocol
|
||||||
|
0x0A, 0x04, // Message class and ID (UBX-MON-VER)
|
||||||
|
0x00, 0x00, // Length of payload (we're asking for an answer, so no payload)
|
||||||
|
0x00, 0x00 // Checksum
|
||||||
|
};
|
||||||
|
// Get Ublox gnss module hardware and software info
|
||||||
|
UBXChecksum(_message_MONVER, sizeof(_message_MONVER));
|
||||||
|
clearBuffer();
|
||||||
|
_serial_gps->write(_message_MONVER, sizeof(_message_MONVER));
|
||||||
|
|
||||||
// tips: extensionNo field is 0 on some 6M GNSS modules
|
uint16_t len = getACK(buffer, sizeof(buffer), 0x0A, 0x04, 1200);
|
||||||
for (int i = 0; i < ublox_info.extensionNo; ++i) {
|
if (len) {
|
||||||
if (!strncmp(ublox_info.extension[i], "MOD=", 4)) {
|
uint16_t position = 0;
|
||||||
strncpy((char *)buffer, &(ublox_info.extension[i][4]), sizeof(buffer));
|
for (int i = 0; i < 30; i++) {
|
||||||
} else if (!strncmp(ublox_info.extension[i], "PROTVER", 7)) {
|
ublox_info.swVersion[i] = buffer[position];
|
||||||
char *ptr = nullptr;
|
position++;
|
||||||
memset(buffer, 0, sizeof(buffer));
|
}
|
||||||
strncpy((char *)buffer, &(ublox_info.extension[i][8]), sizeof(buffer));
|
for (int i = 0; i < 10; i++) {
|
||||||
LOG_DEBUG("Protocol Version:%s", (char *)buffer);
|
ublox_info.hwVersion[i] = buffer[position];
|
||||||
if (strlen((char *)buffer)) {
|
position++;
|
||||||
ublox_info.protocol_version = strtoul((char *)buffer, &ptr, 10);
|
}
|
||||||
LOG_DEBUG("ProtVer=%d", ublox_info.protocol_version);
|
|
||||||
} else {
|
while (len >= position + 30) {
|
||||||
ublox_info.protocol_version = 0;
|
for (int i = 0; i < 30; i++) {
|
||||||
|
ublox_info.extension[ublox_info.extensionNo][i] = buffer[position];
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
ublox_info.extensionNo++;
|
||||||
|
if (ublox_info.extensionNo > 9)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG("Module Info : ");
|
||||||
|
LOG_DEBUG("Soft version: %s", ublox_info.swVersion);
|
||||||
|
LOG_DEBUG("Hard version: %s", ublox_info.hwVersion);
|
||||||
|
LOG_DEBUG("Extensions:%d", ublox_info.extensionNo);
|
||||||
|
for (int i = 0; i < ublox_info.extensionNo; i++) {
|
||||||
|
LOG_DEBUG(" %s", ublox_info.extension[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(buffer, 0, sizeof(buffer));
|
||||||
|
|
||||||
|
// tips: extensionNo field is 0 on some 6M GNSS modules
|
||||||
|
for (int i = 0; i < ublox_info.extensionNo; ++i) {
|
||||||
|
if (!strncmp(ublox_info.extension[i], "MOD=", 4)) {
|
||||||
|
strncpy((char *)buffer, &(ublox_info.extension[i][4]), sizeof(buffer));
|
||||||
|
} else if (!strncmp(ublox_info.extension[i], "PROTVER", 7)) {
|
||||||
|
char *ptr = nullptr;
|
||||||
|
memset(buffer, 0, sizeof(buffer));
|
||||||
|
strncpy((char *)buffer, &(ublox_info.extension[i][8]), sizeof(buffer));
|
||||||
|
LOG_DEBUG("Protocol Version:%s", (char *)buffer);
|
||||||
|
if (strlen((char *)buffer)) {
|
||||||
|
ublox_info.protocol_version = strtoul((char *)buffer, &ptr, 10);
|
||||||
|
LOG_DEBUG("ProtVer=%d", ublox_info.protocol_version);
|
||||||
|
} else {
|
||||||
|
ublox_info.protocol_version = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (strncmp(ublox_info.hwVersion, "00040007", 8) == 0) {
|
||||||
if (strncmp(ublox_info.hwVersion, "00040007", 8) == 0) {
|
LOG_INFO(DETECTED_MESSAGE, "U-blox 6", "6");
|
||||||
LOG_INFO(DETECTED_MESSAGE, "U-blox 6", "6");
|
return GNSS_MODEL_UBLOX6;
|
||||||
return GNSS_MODEL_UBLOX6;
|
} else if (strncmp(ublox_info.hwVersion, "00070000", 8) == 0) {
|
||||||
} else if (strncmp(ublox_info.hwVersion, "00070000", 8) == 0) {
|
LOG_INFO(DETECTED_MESSAGE, "U-blox 7", "7");
|
||||||
LOG_INFO(DETECTED_MESSAGE, "U-blox 7", "7");
|
return GNSS_MODEL_UBLOX7;
|
||||||
return GNSS_MODEL_UBLOX7;
|
} else if (strncmp(ublox_info.hwVersion, "00080000", 8) == 0) {
|
||||||
} else if (strncmp(ublox_info.hwVersion, "00080000", 8) == 0) {
|
LOG_INFO(DETECTED_MESSAGE, "U-blox 8", "8");
|
||||||
LOG_INFO(DETECTED_MESSAGE, "U-blox 8", "8");
|
return GNSS_MODEL_UBLOX8;
|
||||||
return GNSS_MODEL_UBLOX8;
|
} else if (strncmp(ublox_info.hwVersion, "00190000", 8) == 0) {
|
||||||
} else if (strncmp(ublox_info.hwVersion, "00190000", 8) == 0) {
|
LOG_INFO(DETECTED_MESSAGE, "U-blox 9", "9");
|
||||||
LOG_INFO(DETECTED_MESSAGE, "U-blox 9", "9");
|
return GNSS_MODEL_UBLOX9;
|
||||||
return GNSS_MODEL_UBLOX9;
|
} else if (strncmp(ublox_info.hwVersion, "000A0000", 8) == 0) {
|
||||||
} else if (strncmp(ublox_info.hwVersion, "000A0000", 8) == 0) {
|
LOG_INFO(DETECTED_MESSAGE, "U-blox 10", "10");
|
||||||
LOG_INFO(DETECTED_MESSAGE, "U-blox 10", "10");
|
return GNSS_MODEL_UBLOX10;
|
||||||
return GNSS_MODEL_UBLOX10;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed);
|
LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed);
|
||||||
|
currentDelay = 2000;
|
||||||
|
currentStep = 0;
|
||||||
return GNSS_MODEL_UNKNOWN;
|
return GNSS_MODEL_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1392,12 +1493,12 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipI
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (c == ',' || (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n')) {
|
if (c == ',' || (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n')) {
|
||||||
#ifdef GPS_DEBUG
|
|
||||||
LOG_DEBUG(response);
|
|
||||||
#endif
|
|
||||||
// check if we can see our chips
|
// check if we can see our chips
|
||||||
for (const auto &chipInfo : responseMap) {
|
for (const auto &chipInfo : responseMap) {
|
||||||
if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) {
|
if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) {
|
||||||
|
#ifdef GPS_DEBUG
|
||||||
|
LOG_DEBUG(response);
|
||||||
|
#endif
|
||||||
LOG_INFO("%s detected", chipInfo.chipName.c_str());
|
LOG_INFO("%s detected", chipInfo.chipName.c_str());
|
||||||
delete[] response; // Cleanup before return
|
delete[] response; // Cleanup before return
|
||||||
return chipInfo.driver;
|
return chipInfo.driver;
|
||||||
@@ -1405,6 +1506,9 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') {
|
if (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') {
|
||||||
|
#ifdef GPS_DEBUG
|
||||||
|
LOG_DEBUG(response);
|
||||||
|
#endif
|
||||||
// Reset the response buffer for the next potential message
|
// Reset the response buffer for the next potential message
|
||||||
responseLen = 0;
|
responseLen = 0;
|
||||||
response[0] = '\0';
|
response[0] = '\0';
|
||||||
@@ -1423,10 +1527,7 @@ GPS *GPS::createGps()
|
|||||||
int8_t _rx_gpio = config.position.rx_gpio;
|
int8_t _rx_gpio = config.position.rx_gpio;
|
||||||
int8_t _tx_gpio = config.position.tx_gpio;
|
int8_t _tx_gpio = config.position.tx_gpio;
|
||||||
int8_t _en_gpio = config.position.gps_en_gpio;
|
int8_t _en_gpio = config.position.gps_en_gpio;
|
||||||
#if HAS_GPS && !defined(ARCH_ESP32)
|
|
||||||
_rx_gpio = 1; // We only specify GPS serial ports on ESP32. Otherwise, these are just flags.
|
|
||||||
_tx_gpio = 1;
|
|
||||||
#endif
|
|
||||||
#if defined(GPS_RX_PIN)
|
#if defined(GPS_RX_PIN)
|
||||||
if (!_rx_gpio)
|
if (!_rx_gpio)
|
||||||
_rx_gpio = GPS_RX_PIN;
|
_rx_gpio = GPS_RX_PIN;
|
||||||
@@ -1440,7 +1541,7 @@ GPS *GPS::createGps()
|
|||||||
_en_gpio = PIN_GPS_EN;
|
_en_gpio = PIN_GPS_EN;
|
||||||
#endif
|
#endif
|
||||||
#ifdef ARCH_PORTDUINO
|
#ifdef ARCH_PORTDUINO
|
||||||
if (!settingsMap[has_gps])
|
if (!portduino_config.has_gps)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
#endif
|
#endif
|
||||||
if (!_rx_gpio || !_serial_gps) // Configured to have no GPS at all
|
if (!_rx_gpio || !_serial_gps) // Configured to have no GPS at all
|
||||||
@@ -1491,8 +1592,6 @@ GPS *GPS::createGps()
|
|||||||
|
|
||||||
#ifdef PIN_GPS_RESET
|
#ifdef PIN_GPS_RESET
|
||||||
pinMode(PIN_GPS_RESET, OUTPUT);
|
pinMode(PIN_GPS_RESET, OUTPUT);
|
||||||
digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); // assert for 10ms
|
|
||||||
delay(10);
|
|
||||||
digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE);
|
digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1502,16 +1601,28 @@ GPS *GPS::createGps()
|
|||||||
_serial_gps->setRxBufferSize(SERIAL_BUFFER_SIZE); // the default is 256
|
_serial_gps->setRxBufferSize(SERIAL_BUFFER_SIZE); // the default is 256
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// ESP32 has a special set of parameters vs other arduino ports
|
|
||||||
#if defined(ARCH_ESP32)
|
|
||||||
LOG_DEBUG("Use GPIO%d for GPS RX", new_gps->rx_gpio);
|
LOG_DEBUG("Use GPIO%d for GPS RX", new_gps->rx_gpio);
|
||||||
LOG_DEBUG("Use GPIO%d for GPS TX", new_gps->tx_gpio);
|
LOG_DEBUG("Use GPIO%d for GPS TX", new_gps->tx_gpio);
|
||||||
|
|
||||||
|
// ESP32 has a special set of parameters vs other arduino ports
|
||||||
|
#if defined(ARCH_ESP32)
|
||||||
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, new_gps->rx_gpio, new_gps->tx_gpio);
|
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, new_gps->rx_gpio, new_gps->tx_gpio);
|
||||||
#elif defined(ARCH_RP2040)
|
#elif defined(ARCH_RP2040)
|
||||||
|
_serial_gps->setPinout(new_gps->tx_gpio, new_gps->rx_gpio);
|
||||||
_serial_gps->setFIFOSize(256);
|
_serial_gps->setFIFOSize(256);
|
||||||
_serial_gps->begin(GPS_BAUDRATE);
|
_serial_gps->begin(GPS_BAUDRATE);
|
||||||
#else
|
#elif defined(ARCH_NRF52)
|
||||||
|
_serial_gps->setPins(new_gps->rx_gpio, new_gps->tx_gpio);
|
||||||
_serial_gps->begin(GPS_BAUDRATE);
|
_serial_gps->begin(GPS_BAUDRATE);
|
||||||
|
#elif defined(ARCH_STM32WL)
|
||||||
|
_serial_gps->setTx(new_gps->tx_gpio);
|
||||||
|
_serial_gps->setRx(new_gps->rx_gpio);
|
||||||
|
_serial_gps->begin(GPS_BAUDRATE);
|
||||||
|
#elif defined(ARCH_PORTDUINO)
|
||||||
|
// Portduino can't set the GPS pins directly.
|
||||||
|
_serial_gps->begin(GPS_BAUDRATE);
|
||||||
|
#else
|
||||||
|
#error Unsupported architecture!
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
return new_gps;
|
return new_gps;
|
||||||
@@ -1578,8 +1689,12 @@ bool GPS::lookForLocation()
|
|||||||
|
|
||||||
#ifndef TINYGPS_OPTION_NO_STATISTICS
|
#ifndef TINYGPS_OPTION_NO_STATISTICS
|
||||||
if (reader.failedChecksum() > lastChecksumFailCount) {
|
if (reader.failedChecksum() > lastChecksumFailCount) {
|
||||||
LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount,
|
// In a GPS_DEBUG build we want to log all of these. In production, we only care if there are many of them.
|
||||||
reader.failedChecksum());
|
#ifndef GPS_DEBUG
|
||||||
|
if (reader.failedChecksum() > 4)
|
||||||
|
#endif
|
||||||
|
LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount,
|
||||||
|
reader.failedChecksum());
|
||||||
lastChecksumFailCount = reader.failedChecksum();
|
lastChecksumFailCount = reader.failedChecksum();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
#define GPS_EN_ACTIVE 1
|
#define GPS_EN_ACTIVE 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static constexpr uint32_t GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS = 10 * 1000UL;
|
||||||
|
static constexpr uint32_t GPS_FIX_HOLD_MAX_MS = 20000;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
GNSS_MODEL_ATGM336H,
|
GNSS_MODEL_ATGM336H,
|
||||||
GNSS_MODEL_MTK,
|
GNSS_MODEL_MTK,
|
||||||
@@ -31,7 +34,8 @@ typedef enum {
|
|||||||
GNSS_MODEL_MTK_PA1616S,
|
GNSS_MODEL_MTK_PA1616S,
|
||||||
GNSS_MODEL_AG3335,
|
GNSS_MODEL_AG3335,
|
||||||
GNSS_MODEL_AG3352,
|
GNSS_MODEL_AG3352,
|
||||||
GNSS_MODEL_LS20031
|
GNSS_MODEL_LS20031,
|
||||||
|
GNSS_MODEL_CM121
|
||||||
} GnssModel_t;
|
} GnssModel_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@@ -150,6 +154,8 @@ class GPS : private concurrency::OSThread
|
|||||||
TinyGPSPlus reader;
|
TinyGPSPlus reader;
|
||||||
uint8_t fixQual = 0; // fix quality from GPGGA
|
uint8_t fixQual = 0; // fix quality from GPGGA
|
||||||
uint32_t lastChecksumFailCount = 0;
|
uint32_t lastChecksumFailCount = 0;
|
||||||
|
uint8_t currentStep = 0;
|
||||||
|
int32_t currentDelay = 2000;
|
||||||
|
|
||||||
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
|
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
|
||||||
// (20210908) TinyGps++ can only read the GPGSA "FIX TYPE" field
|
// (20210908) TinyGps++ can only read the GPGSA "FIX TYPE" field
|
||||||
@@ -172,8 +178,6 @@ class GPS : private concurrency::OSThread
|
|||||||
*/
|
*/
|
||||||
bool hasValidLocation = false; // default to false, until we complete our first read
|
bool hasValidLocation = false; // default to false, until we complete our first read
|
||||||
|
|
||||||
bool isInPowersave = false;
|
|
||||||
|
|
||||||
bool shouldPublish = false; // If we've changed GPS state, this will force a publish the next loop()
|
bool shouldPublish = false; // If we've changed GPS state, this will force a publish the next loop()
|
||||||
|
|
||||||
bool hasGPS = false; // Do we have a GPS we are talking to
|
bool hasGPS = false; // Do we have a GPS we are talking to
|
||||||
@@ -190,6 +194,8 @@ class GPS : private concurrency::OSThread
|
|||||||
/** If !NULL we will use this serial port to construct our GPS */
|
/** If !NULL we will use this serial port to construct our GPS */
|
||||||
#if defined(ARCH_RP2040)
|
#if defined(ARCH_RP2040)
|
||||||
static SerialUART *_serial_gps;
|
static SerialUART *_serial_gps;
|
||||||
|
#elif defined(ARCH_NRF52)
|
||||||
|
static Uart *_serial_gps;
|
||||||
#else
|
#else
|
||||||
static HardwareSerial *_serial_gps;
|
static HardwareSerial *_serial_gps;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -109,6 +109,39 @@ RTCSetResult readFromRTC()
|
|||||||
}
|
}
|
||||||
return RTCSetResultSuccess;
|
return RTCSetResultSuccess;
|
||||||
}
|
}
|
||||||
|
#elif defined(RX8130CE_RTC)
|
||||||
|
if (rtc_found.address == RX8130CE_RTC) {
|
||||||
|
uint32_t now = millis();
|
||||||
|
#ifdef MUZI_BASE
|
||||||
|
ArtronShop_RX8130CE rtc(&Wire1);
|
||||||
|
#else
|
||||||
|
ArtronShop_RX8130CE rtc(&Wire);
|
||||||
|
#endif
|
||||||
|
tm t;
|
||||||
|
if (rtc.getTime(&t)) {
|
||||||
|
tv.tv_sec = gm_mktime(&t);
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
|
||||||
|
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
||||||
|
LOG_DEBUG("Read RTC time from RX8130CE getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900,
|
||||||
|
t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
|
||||||
|
#ifdef BUILD_EPOCH
|
||||||
|
if (tv.tv_sec < BUILD_EPOCH) {
|
||||||
|
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
|
||||||
|
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
|
||||||
|
lastTimeValidationWarning = millis();
|
||||||
|
}
|
||||||
|
return RTCSetResultInvalidTime;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (currentQuality == RTCQualityNone) {
|
||||||
|
timeStartMsec = now;
|
||||||
|
zeroOffsetSecs = tv.tv_sec;
|
||||||
|
currentQuality = RTCQualityDevice;
|
||||||
|
}
|
||||||
|
return RTCSetResultSuccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
if (!gettimeofday(&tv, NULL)) {
|
if (!gettimeofday(&tv, NULL)) {
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
@@ -214,6 +247,21 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
|
|||||||
LOG_DEBUG("PCF8563_RTC setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
|
LOG_DEBUG("PCF8563_RTC setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
|
||||||
t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
|
t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
|
||||||
}
|
}
|
||||||
|
#elif defined(RX8130CE_RTC)
|
||||||
|
if (rtc_found.address == RX8130CE_RTC) {
|
||||||
|
#ifdef MUZI_BASE
|
||||||
|
ArtronShop_RX8130CE rtc(&Wire1);
|
||||||
|
#else
|
||||||
|
ArtronShop_RX8130CE rtc(&Wire);
|
||||||
|
#endif
|
||||||
|
tm *t = gmtime(&tv->tv_sec);
|
||||||
|
if (rtc.setTime(*t)) {
|
||||||
|
LOG_DEBUG("RX8130CE setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1,
|
||||||
|
t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
|
||||||
|
} else {
|
||||||
|
LOG_WARN("Failed to set time for RX8130CE");
|
||||||
|
}
|
||||||
|
}
|
||||||
#elif defined(ARCH_ESP32)
|
#elif defined(ARCH_ESP32)
|
||||||
settimeofday(tv, NULL);
|
settimeofday(tv, NULL);
|
||||||
#endif
|
#endif
|
||||||
@@ -270,7 +318,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
|
|||||||
#ifdef BUILD_EPOCH
|
#ifdef BUILD_EPOCH
|
||||||
if (tv.tv_sec < BUILD_EPOCH) {
|
if (tv.tv_sec < BUILD_EPOCH) {
|
||||||
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
|
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
|
||||||
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
|
LOG_WARN("Ignore time (%lu) before build epoch (%lu)!", printableEpoch, BUILD_EPOCH);
|
||||||
lastTimeValidationWarning = millis();
|
lastTimeValidationWarning = millis();
|
||||||
}
|
}
|
||||||
return RTCSetResultInvalidTime;
|
return RTCSetResultInvalidTime;
|
||||||
@@ -279,7 +327,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
|
|||||||
// Calculate max allowed time safely to avoid overflow in logging
|
// Calculate max allowed time safely to avoid overflow in logging
|
||||||
uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
|
uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
|
||||||
uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime;
|
uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime;
|
||||||
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch,
|
LOG_WARN("Ignore time (%lu) too far in the future (build epoch: %lu, max allowed: %lu)!", printableEpoch,
|
||||||
(uint32_t)BUILD_EPOCH, maxAllowedPrintable);
|
(uint32_t)BUILD_EPOCH, maxAllowedPrintable);
|
||||||
lastTimeValidationWarning = millis();
|
lastTimeValidationWarning = millis();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
#include "sys/time.h"
|
#include "sys/time.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#ifdef RX8130CE_RTC
|
||||||
|
#include <ArtronShop_RX8130CE.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
enum RTCQuality {
|
enum RTCQuality {
|
||||||
|
|
||||||
/// We haven't had our RTC set yet
|
/// We haven't had our RTC set yet
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ bool EInkDisplay::connect()
|
|||||||
adafruitDisplay->setRotation(1);
|
adafruitDisplay->setRotation(1);
|
||||||
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
||||||
}
|
}
|
||||||
#elif defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK)
|
#elif defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK)
|
||||||
{
|
{
|
||||||
spi1 = &SPI1;
|
spi1 = &SPI1;
|
||||||
spi1->begin();
|
spi1->begin();
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class EInkDisplay : public OLEDDisplay
|
|||||||
SPIClass *hspi = NULL;
|
SPIClass *hspi = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK)
|
#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK)
|
||||||
SPIClass *spi1 = NULL;
|
SPIClass *spi1 = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
687
src/graphics/Panel_sdl.cpp
Normal file
687
src/graphics/Panel_sdl.cpp
Normal file
@@ -0,0 +1,687 @@
|
|||||||
|
/*----------------------------------------------------------------------------/
|
||||||
|
Lovyan GFX - Graphics library for embedded devices.
|
||||||
|
|
||||||
|
Original Source:
|
||||||
|
https://github.com/lovyan03/LovyanGFX/
|
||||||
|
|
||||||
|
Licence:
|
||||||
|
[FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
|
||||||
|
|
||||||
|
Author:
|
||||||
|
[lovyan03](https://twitter.com/lovyan03)
|
||||||
|
|
||||||
|
Contributors:
|
||||||
|
[ciniml](https://github.com/ciniml)
|
||||||
|
[mongonta0716](https://github.com/mongonta0716)
|
||||||
|
[tobozo](https://github.com/tobozo)
|
||||||
|
|
||||||
|
Porting for SDL:
|
||||||
|
[imliubo](https://github.com/imliubo)
|
||||||
|
/----------------------------------------------------------------------------*/
|
||||||
|
#include "Panel_sdl.hpp"
|
||||||
|
|
||||||
|
#if defined(SDL_h_)
|
||||||
|
|
||||||
|
// #include "../common.hpp"
|
||||||
|
// #include "../../misc/common_function.hpp"
|
||||||
|
// #include "../../Bus.hpp"
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
#include <math.h>
|
||||||
|
#include <vector>
|
||||||
|
#ifndef M_PI
|
||||||
|
#define M_PI 3.14159265358979323846
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace lgfx
|
||||||
|
{
|
||||||
|
inline namespace v1
|
||||||
|
{
|
||||||
|
SDL_Keymod Panel_sdl::_keymod = KMOD_NONE;
|
||||||
|
static SDL_semaphore *_update_in_semaphore = nullptr;
|
||||||
|
static SDL_semaphore *_update_out_semaphore = nullptr;
|
||||||
|
volatile static uint32_t _in_step_exec = 0;
|
||||||
|
volatile static uint32_t _msec_step_exec = 512;
|
||||||
|
static bool _inited = false;
|
||||||
|
static bool _all_close = false;
|
||||||
|
|
||||||
|
volatile uint8_t Panel_sdl::_gpio_dummy_values[EMULATED_GPIO_MAX];
|
||||||
|
|
||||||
|
static inline void *heap_alloc_dma(size_t length)
|
||||||
|
{
|
||||||
|
return malloc(length);
|
||||||
|
} // aligned_alloc(16, length);
|
||||||
|
static inline void heap_free(void *buf)
|
||||||
|
{
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::list<monitor_t *> _list_monitor;
|
||||||
|
|
||||||
|
static monitor_t *const getMonitorByWindowID(uint32_t windowID)
|
||||||
|
{
|
||||||
|
for (auto &m : _list_monitor) {
|
||||||
|
if (SDL_GetWindowID(m->window) == windowID) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static std::vector<Panel_sdl::KeyCodeMapping_t> _key_code_map;
|
||||||
|
|
||||||
|
void Panel_sdl::addKeyCodeMapping(SDL_KeyCode keyCode, uint8_t gpio)
|
||||||
|
{
|
||||||
|
if (gpio > EMULATED_GPIO_MAX)
|
||||||
|
return;
|
||||||
|
KeyCodeMapping_t map;
|
||||||
|
map.keycode = keyCode;
|
||||||
|
map.gpio = gpio;
|
||||||
|
_key_code_map.push_back(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Panel_sdl::getKeyCodeMapping(SDL_KeyCode keyCode)
|
||||||
|
{
|
||||||
|
for (const auto &i : _key_code_map) {
|
||||||
|
if (i.keycode == keyCode)
|
||||||
|
return i.gpio;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel_sdl::_event_proc(void)
|
||||||
|
{
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
if ((event.type == SDL_KEYDOWN) || (event.type == SDL_KEYUP)) {
|
||||||
|
auto mon = getMonitorByWindowID(event.button.windowID);
|
||||||
|
int gpio = -1;
|
||||||
|
|
||||||
|
/// Check key mapping
|
||||||
|
gpio = getKeyCodeMapping((SDL_KeyCode)event.key.keysym.sym);
|
||||||
|
if (gpio < 0) {
|
||||||
|
switch (event.key.keysym.sym) { /// M5StackのBtnA~BtnCのエミュレート;
|
||||||
|
// case SDLK_LEFT: gpio = 39; break;
|
||||||
|
// case SDLK_DOWN: gpio = 38; break;
|
||||||
|
// case SDLK_RIGHT: gpio = 37; break;
|
||||||
|
// case SDLK_UP: gpio = 36; break;
|
||||||
|
|
||||||
|
/// L/Rキーで画面回転
|
||||||
|
case SDLK_r:
|
||||||
|
case SDLK_l:
|
||||||
|
if (event.type == SDL_KEYDOWN && event.key.keysym.mod == _keymod) {
|
||||||
|
if (mon != nullptr) {
|
||||||
|
mon->frame_rotation = (mon->frame_rotation += event.key.keysym.sym == SDLK_r ? 1 : -1);
|
||||||
|
int x, y, w, h;
|
||||||
|
SDL_GetWindowSize(mon->window, &w, &h);
|
||||||
|
SDL_GetWindowPosition(mon->window, &x, &y);
|
||||||
|
SDL_SetWindowSize(mon->window, h, w);
|
||||||
|
SDL_SetWindowPosition(mon->window, x + (w - h) / 2, y + (h - w) / 2);
|
||||||
|
mon->panel->sdl_invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
/// 1~6キーで画面拡大率変更
|
||||||
|
case SDLK_1:
|
||||||
|
case SDLK_2:
|
||||||
|
case SDLK_3:
|
||||||
|
case SDLK_4:
|
||||||
|
case SDLK_5:
|
||||||
|
case SDLK_6:
|
||||||
|
if (event.type == SDL_KEYDOWN && event.key.keysym.mod == _keymod) {
|
||||||
|
if (mon != nullptr) {
|
||||||
|
int size = 1 + (event.key.keysym.sym - SDLK_1);
|
||||||
|
_update_scaling(mon, size, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type == SDL_KEYDOWN) {
|
||||||
|
Panel_sdl::gpio_lo(gpio);
|
||||||
|
} else {
|
||||||
|
Panel_sdl::gpio_hi(gpio);
|
||||||
|
}
|
||||||
|
} else if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEMOTION) {
|
||||||
|
auto mon = getMonitorByWindowID(event.button.windowID);
|
||||||
|
if (mon != nullptr) {
|
||||||
|
{
|
||||||
|
int x, y, w, h;
|
||||||
|
SDL_GetWindowSize(mon->window, &w, &h);
|
||||||
|
SDL_GetMouseState(&x, &y);
|
||||||
|
float sf = sinf(mon->frame_angle * M_PI / 180);
|
||||||
|
float cf = cosf(mon->frame_angle * M_PI / 180);
|
||||||
|
x -= w / 2.0f;
|
||||||
|
y -= h / 2.0f;
|
||||||
|
float nx = y * sf + x * cf;
|
||||||
|
float ny = y * cf - x * sf;
|
||||||
|
if (mon->frame_rotation & 1) {
|
||||||
|
std::swap(w, h);
|
||||||
|
}
|
||||||
|
x = (nx * mon->frame_width / w) + (mon->frame_width >> 1);
|
||||||
|
y = (ny * mon->frame_height / h) + (mon->frame_height >> 1);
|
||||||
|
mon->touch_x = x - mon->frame_inner_x;
|
||||||
|
mon->touch_y = y - mon->frame_inner_y;
|
||||||
|
}
|
||||||
|
if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) {
|
||||||
|
mon->touched = true;
|
||||||
|
}
|
||||||
|
if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT) {
|
||||||
|
mon->touched = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (event.type == SDL_WINDOWEVENT) {
|
||||||
|
auto monitor = getMonitorByWindowID(event.window.windowID);
|
||||||
|
if (monitor) {
|
||||||
|
if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||||
|
int mw, mh;
|
||||||
|
SDL_GetRendererOutputSize(monitor->renderer, &mw, &mh);
|
||||||
|
if (monitor->frame_rotation & 1) {
|
||||||
|
std::swap(mw, mh);
|
||||||
|
}
|
||||||
|
monitor->scaling_x = (mw * 2 / monitor->frame_width) / 2.0f;
|
||||||
|
monitor->scaling_y = (mh * 2 / monitor->frame_height) / 2.0f;
|
||||||
|
monitor->panel->sdl_invalidate();
|
||||||
|
} else if (event.window.event == SDL_WINDOWEVENT_CLOSE) {
|
||||||
|
monitor->closing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (event.type == SDL_QUIT) {
|
||||||
|
for (auto &m : _list_monitor) {
|
||||||
|
m->closing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// デバッガでステップ実行されていることを検出するスレッド用関数。
|
||||||
|
static int detectDebugger(bool *running)
|
||||||
|
{
|
||||||
|
uint32_t prev_ms = SDL_GetTicks();
|
||||||
|
do {
|
||||||
|
SDL_Delay(1);
|
||||||
|
uint32_t ms = SDL_GetTicks();
|
||||||
|
/// 時間間隔が広すぎる場合はステップ実行中 (ブレークポイントで止まった)と判断する。
|
||||||
|
/// また、解除されたと判断した後も1023msecほど状態を維持する。
|
||||||
|
if (ms - prev_ms > 64) {
|
||||||
|
_in_step_exec = _msec_step_exec;
|
||||||
|
} else if (_in_step_exec) {
|
||||||
|
--_in_step_exec;
|
||||||
|
}
|
||||||
|
prev_ms = ms;
|
||||||
|
} while (*running);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel_sdl::_update_proc(void)
|
||||||
|
{
|
||||||
|
for (auto it = _list_monitor.begin(); it != _list_monitor.end();) {
|
||||||
|
if ((*it)->closing) {
|
||||||
|
if ((*it)->texture_frameimage) {
|
||||||
|
SDL_DestroyTexture((*it)->texture_frameimage);
|
||||||
|
}
|
||||||
|
SDL_DestroyTexture((*it)->texture);
|
||||||
|
SDL_DestroyRenderer((*it)->renderer);
|
||||||
|
SDL_DestroyWindow((*it)->window);
|
||||||
|
_list_monitor.erase(it++);
|
||||||
|
if (_list_monitor.empty()) {
|
||||||
|
_all_close = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
(*it)->panel->sdl_update();
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Panel_sdl::setup(void)
|
||||||
|
{
|
||||||
|
if (_inited)
|
||||||
|
return 1;
|
||||||
|
_inited = true;
|
||||||
|
|
||||||
|
/// Add default keycode mapping
|
||||||
|
/// M5StackのBtnA~BtnCのエミュレート;
|
||||||
|
addKeyCodeMapping(SDLK_LEFT, 39);
|
||||||
|
addKeyCodeMapping(SDLK_DOWN, 38);
|
||||||
|
addKeyCodeMapping(SDLK_RIGHT, 37);
|
||||||
|
addKeyCodeMapping(SDLK_UP, 36);
|
||||||
|
|
||||||
|
SDL_CreateThread((SDL_ThreadFunction)detectDebugger, "dbg", &_inited);
|
||||||
|
|
||||||
|
_update_in_semaphore = SDL_CreateSemaphore(0);
|
||||||
|
_update_out_semaphore = SDL_CreateSemaphore(0);
|
||||||
|
for (size_t pin = 0; pin < EMULATED_GPIO_MAX; ++pin) {
|
||||||
|
gpio_hi(pin);
|
||||||
|
}
|
||||||
|
/*Initialize the SDL*/
|
||||||
|
SDL_Init(SDL_INIT_VIDEO);
|
||||||
|
SDL_StartTextInput();
|
||||||
|
|
||||||
|
// SDL_SetThreadPriority(SDL_ThreadPriority::SDL_THREAD_PRIORITY_HIGH);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Panel_sdl::loop(void)
|
||||||
|
{
|
||||||
|
if (!_inited)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
_event_proc();
|
||||||
|
SDL_SemWaitTimeout(_update_in_semaphore, 1);
|
||||||
|
_update_proc();
|
||||||
|
_event_proc();
|
||||||
|
if (SDL_SemValue(_update_out_semaphore) == 0) {
|
||||||
|
SDL_SemPost(_update_out_semaphore);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _all_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Panel_sdl::close(void)
|
||||||
|
{
|
||||||
|
if (!_inited)
|
||||||
|
return 1;
|
||||||
|
_inited = false;
|
||||||
|
|
||||||
|
SDL_StopTextInput();
|
||||||
|
SDL_DestroySemaphore(_update_in_semaphore);
|
||||||
|
SDL_DestroySemaphore(_update_out_semaphore);
|
||||||
|
SDL_Quit();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Panel_sdl::main(int (*fn)(bool *), uint32_t msec_step_exec)
|
||||||
|
{
|
||||||
|
_msec_step_exec = msec_step_exec;
|
||||||
|
|
||||||
|
/// SDLの準備
|
||||||
|
if (0 != Panel_sdl::setup()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ユーザコード関数の動作・停止フラグ
|
||||||
|
bool running = true;
|
||||||
|
|
||||||
|
/// ユーザコード関数を起動する
|
||||||
|
auto thread = SDL_CreateThread((SDL_ThreadFunction)fn, "fn", &running);
|
||||||
|
|
||||||
|
/// 全部のウィンドウが閉じられるまでSDLのイベント・描画処理を継続
|
||||||
|
while (0 == Panel_sdl::loop()) {
|
||||||
|
};
|
||||||
|
|
||||||
|
/// ユーザコード関数を終了する
|
||||||
|
running = false;
|
||||||
|
SDL_WaitThread(thread, nullptr);
|
||||||
|
|
||||||
|
/// SDLを終了する
|
||||||
|
return Panel_sdl::close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel_sdl::setScaling(uint_fast8_t scaling_x, uint_fast8_t scaling_y)
|
||||||
|
{
|
||||||
|
monitor.scaling_x = scaling_x;
|
||||||
|
monitor.scaling_y = scaling_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel_sdl::setFrameImage(const void *frame_image, int frame_width, int frame_height, int inner_x, int inner_y)
|
||||||
|
{
|
||||||
|
monitor.frame_image = frame_image;
|
||||||
|
monitor.frame_width = frame_width;
|
||||||
|
monitor.frame_height = frame_height;
|
||||||
|
monitor.frame_inner_x = inner_x;
|
||||||
|
monitor.frame_inner_y = inner_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel_sdl::setFrameRotation(uint_fast16_t frame_rotation)
|
||||||
|
{
|
||||||
|
monitor.frame_rotation = frame_rotation;
|
||||||
|
monitor.frame_angle = (monitor.frame_rotation) * 90;
|
||||||
|
}
|
||||||
|
|
||||||
|
Panel_sdl::~Panel_sdl(void)
|
||||||
|
{
|
||||||
|
_list_monitor.remove(&monitor);
|
||||||
|
SDL_DestroyMutex(_sdl_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Panel_sdl::Panel_sdl(void) : Panel_FrameBufferBase()
|
||||||
|
{
|
||||||
|
_sdl_mutex = SDL_CreateMutex();
|
||||||
|
_auto_display = true;
|
||||||
|
monitor.panel = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Panel_sdl::init(bool use_reset)
|
||||||
|
{
|
||||||
|
initFrameBuffer(_cfg.panel_width * 4, _cfg.panel_height);
|
||||||
|
bool res = Panel_FrameBufferBase::init(use_reset);
|
||||||
|
|
||||||
|
_list_monitor.push_back(&monitor);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
color_depth_t Panel_sdl::setColorDepth(color_depth_t depth)
|
||||||
|
{
|
||||||
|
auto bits = depth & color_depth_t::bit_mask;
|
||||||
|
if (bits >= 16) {
|
||||||
|
depth = (bits > 16) ? rgb888_3Byte : rgb565_2Byte;
|
||||||
|
} else {
|
||||||
|
depth = (depth == color_depth_t::grayscale_8bit) ? grayscale_8bit : rgb332_1Byte;
|
||||||
|
}
|
||||||
|
_write_depth = depth;
|
||||||
|
_read_depth = depth;
|
||||||
|
|
||||||
|
return depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
Panel_sdl::lock_t::lock_t(Panel_sdl *parent) : _parent{parent}
|
||||||
|
{
|
||||||
|
SDL_LockMutex(parent->_sdl_mutex);
|
||||||
|
};
|
||||||
|
|
||||||
|
Panel_sdl::lock_t::~lock_t(void)
|
||||||
|
{
|
||||||
|
++_parent->_modified_counter;
|
||||||
|
SDL_UnlockMutex(_parent->_sdl_mutex);
|
||||||
|
if (SDL_SemValue(_update_in_semaphore) < 2) {
|
||||||
|
SDL_SemPost(_update_in_semaphore);
|
||||||
|
if (!_in_step_exec) {
|
||||||
|
SDL_SemWaitTimeout(_update_out_semaphore, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void Panel_sdl::drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor)
|
||||||
|
{
|
||||||
|
lock_t lock(this);
|
||||||
|
Panel_FrameBufferBase::drawPixelPreclipped(x, y, rawcolor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel_sdl::writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor)
|
||||||
|
{
|
||||||
|
lock_t lock(this);
|
||||||
|
Panel_FrameBufferBase::writeFillRectPreclipped(x, y, w, h, rawcolor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel_sdl::writeBlock(uint32_t rawcolor, uint32_t length)
|
||||||
|
{
|
||||||
|
// lock_t lock(this);
|
||||||
|
Panel_FrameBufferBase::writeBlock(rawcolor, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel_sdl::writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param, bool use_dma)
|
||||||
|
{
|
||||||
|
lock_t lock(this);
|
||||||
|
Panel_FrameBufferBase::writeImage(x, y, w, h, param, use_dma);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel_sdl::writeImageARGB(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param)
|
||||||
|
{
|
||||||
|
lock_t lock(this);
|
||||||
|
Panel_FrameBufferBase::writeImageARGB(x, y, w, h, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel_sdl::writePixels(pixelcopy_t *param, uint32_t len, bool use_dma)
|
||||||
|
{
|
||||||
|
lock_t lock(this);
|
||||||
|
Panel_FrameBufferBase::writePixels(param, len, use_dma);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel_sdl::display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h)
|
||||||
|
{
|
||||||
|
(void)x;
|
||||||
|
(void)y;
|
||||||
|
(void)w;
|
||||||
|
(void)h;
|
||||||
|
if (_in_step_exec) {
|
||||||
|
if (_display_counter != _modified_counter) {
|
||||||
|
do {
|
||||||
|
SDL_SemPost(_update_in_semaphore);
|
||||||
|
SDL_SemWaitTimeout(_update_out_semaphore, 1);
|
||||||
|
} while (_display_counter != _modified_counter);
|
||||||
|
SDL_Delay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint_fast8_t Panel_sdl::getTouchRaw(touch_point_t *tp, uint_fast8_t count)
|
||||||
|
{
|
||||||
|
(void)count;
|
||||||
|
tp->x = monitor.touch_x;
|
||||||
|
tp->y = monitor.touch_y;
|
||||||
|
tp->size = monitor.touched ? 1 : 0;
|
||||||
|
tp->id = 0;
|
||||||
|
return monitor.touched;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel_sdl::setWindowTitle(const char *title)
|
||||||
|
{
|
||||||
|
_window_title = title;
|
||||||
|
if (monitor.window) {
|
||||||
|
SDL_SetWindowTitle(monitor.window, _window_title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel_sdl::_update_scaling(monitor_t *mon, float sx, float sy)
|
||||||
|
{
|
||||||
|
mon->scaling_x = sx;
|
||||||
|
mon->scaling_y = sy;
|
||||||
|
int nw = mon->frame_width;
|
||||||
|
int nh = mon->frame_height;
|
||||||
|
if (mon->frame_rotation & 1) {
|
||||||
|
std::swap(nw, nh);
|
||||||
|
}
|
||||||
|
|
||||||
|
int x, y, w, h;
|
||||||
|
int rw, rh;
|
||||||
|
SDL_GetRendererOutputSize(mon->renderer, &rw, &rh);
|
||||||
|
SDL_GetWindowSize(mon->window, &w, &h);
|
||||||
|
nw = nw * sx * w / rw;
|
||||||
|
nh = nh * sy * h / rh;
|
||||||
|
SDL_GetWindowPosition(mon->window, &x, &y);
|
||||||
|
SDL_SetWindowSize(mon->window, nw, nh);
|
||||||
|
SDL_SetWindowPosition(mon->window, x + (w - nw) / 2, y + (h - nh) / 2);
|
||||||
|
mon->panel->sdl_invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel_sdl::sdl_create(monitor_t *m)
|
||||||
|
{
|
||||||
|
int flag = SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
|
||||||
|
#if SDL_FULLSCREEN
|
||||||
|
flag |= SDL_WINDOW_FULLSCREEN;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (m->frame_width < _cfg.panel_width) {
|
||||||
|
m->frame_width = _cfg.panel_width;
|
||||||
|
}
|
||||||
|
if (m->frame_height < _cfg.panel_height) {
|
||||||
|
m->frame_height = _cfg.panel_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
int window_width = m->frame_width * m->scaling_x;
|
||||||
|
int window_height = m->frame_height * m->scaling_y;
|
||||||
|
int scaling_x = m->scaling_x;
|
||||||
|
int scaling_y = m->scaling_y;
|
||||||
|
if (m->frame_rotation & 1) {
|
||||||
|
std::swap(window_width, window_height);
|
||||||
|
std::swap(scaling_x, scaling_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
m->window = SDL_CreateWindow(_window_title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, window_width, window_height,
|
||||||
|
flag); /*last param. SDL_WINDOW_BORDERLESS to hide borders*/
|
||||||
|
}
|
||||||
|
m->renderer = SDL_CreateRenderer(m->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
||||||
|
m->texture =
|
||||||
|
SDL_CreateTexture(m->renderer, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING, _cfg.panel_width, _cfg.panel_height);
|
||||||
|
SDL_SetTextureBlendMode(m->texture, SDL_BLENDMODE_NONE);
|
||||||
|
|
||||||
|
if (m->frame_image) {
|
||||||
|
// 枠画像用のサーフェイスを作成
|
||||||
|
auto sf = SDL_CreateRGBSurfaceFrom((void *)m->frame_image, m->frame_width, m->frame_height, 32, m->frame_width * 4,
|
||||||
|
0xFF000000, 0xFF0000, 0xFF00, 0xFF);
|
||||||
|
if (sf != nullptr) {
|
||||||
|
// 枠画像からテクスチャを作成
|
||||||
|
m->texture_frameimage = SDL_CreateTextureFromSurface(m->renderer, sf);
|
||||||
|
SDL_FreeSurface(sf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SDL_SetTextureBlendMode(m->texture_frameimage, SDL_BLENDMODE_BLEND);
|
||||||
|
_update_scaling(m, scaling_x, scaling_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel_sdl::sdl_update(void)
|
||||||
|
{
|
||||||
|
if (monitor.renderer == nullptr) {
|
||||||
|
sdl_create(&monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool step_exec = _in_step_exec;
|
||||||
|
|
||||||
|
if (_texupdate_counter != _modified_counter) {
|
||||||
|
pixelcopy_t pc(nullptr, color_depth_t::rgb888_3Byte, _write_depth, false);
|
||||||
|
if (_write_depth == rgb565_2Byte) {
|
||||||
|
pc.fp_copy = pixelcopy_t::copy_rgb_fast<bgr888_t, swap565_t>;
|
||||||
|
} else if (_write_depth == rgb888_3Byte) {
|
||||||
|
pc.fp_copy = pixelcopy_t::copy_rgb_fast<bgr888_t, bgr888_t>;
|
||||||
|
} else if (_write_depth == rgb332_1Byte) {
|
||||||
|
pc.fp_copy = pixelcopy_t::copy_rgb_fast<bgr888_t, rgb332_t>;
|
||||||
|
} else if (_write_depth == grayscale_8bit) {
|
||||||
|
pc.fp_copy = pixelcopy_t::copy_rgb_fast<bgr888_t, grayscale_t>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 == SDL_LockMutex(_sdl_mutex)) {
|
||||||
|
_texupdate_counter = _modified_counter;
|
||||||
|
for (int y = 0; y < _cfg.panel_height; ++y) {
|
||||||
|
pc.src_x32 = 0;
|
||||||
|
pc.src_data = _lines_buffer[y];
|
||||||
|
pc.fp_copy(&_texturebuf[y * _cfg.panel_width], 0, _cfg.panel_width, &pc);
|
||||||
|
}
|
||||||
|
SDL_UnlockMutex(_sdl_mutex);
|
||||||
|
SDL_UpdateTexture(monitor.texture, nullptr, _texturebuf, _cfg.panel_width * sizeof(rgb888_t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int angle = monitor.frame_angle;
|
||||||
|
int target = (monitor.frame_rotation) * 90;
|
||||||
|
angle = (((target * 4) + (angle * 4) + (angle < target ? 8 : 0)) >> 3);
|
||||||
|
|
||||||
|
if (monitor.frame_angle != angle) { // 表示する向きを変える
|
||||||
|
monitor.frame_angle = angle;
|
||||||
|
sdl_invalidate();
|
||||||
|
} else if (monitor.frame_rotation & ~3u) {
|
||||||
|
monitor.frame_rotation &= 3;
|
||||||
|
monitor.frame_angle = (monitor.frame_rotation) * 90;
|
||||||
|
sdl_invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_invalidated || (_display_counter != _texupdate_counter)) {
|
||||||
|
SDL_RendererInfo info;
|
||||||
|
if (0 == SDL_GetRendererInfo(monitor.renderer, &info)) {
|
||||||
|
// ステップ実行中はVSYNCを待機しない
|
||||||
|
if (((bool)(info.flags & SDL_RENDERER_PRESENTVSYNC)) == step_exec) {
|
||||||
|
SDL_RenderSetVSync(monitor.renderer, !step_exec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
int red = 0;
|
||||||
|
int green = 0;
|
||||||
|
int blue = 0;
|
||||||
|
#if defined(M5GFX_BACK_COLOR)
|
||||||
|
red = ((M5GFX_BACK_COLOR) >> 16) & 0xFF;
|
||||||
|
green = ((M5GFX_BACK_COLOR) >> 8) & 0xFF;
|
||||||
|
blue = ((M5GFX_BACK_COLOR)) & 0xFF;
|
||||||
|
#endif
|
||||||
|
SDL_SetRenderDrawColor(monitor.renderer, red, green, blue, 0xFF);
|
||||||
|
}
|
||||||
|
SDL_RenderClear(monitor.renderer);
|
||||||
|
if (_invalidated) {
|
||||||
|
_invalidated = false;
|
||||||
|
int mw, mh;
|
||||||
|
SDL_GetRendererOutputSize(monitor.renderer, &mw, &mh);
|
||||||
|
}
|
||||||
|
render_texture(monitor.texture, monitor.frame_inner_x, monitor.frame_inner_y, _cfg.panel_width, _cfg.panel_height, angle);
|
||||||
|
render_texture(monitor.texture_frameimage, 0, 0, monitor.frame_width, monitor.frame_height, angle);
|
||||||
|
SDL_RenderPresent(monitor.renderer);
|
||||||
|
_display_counter = _texupdate_counter;
|
||||||
|
if (_invalidated) {
|
||||||
|
_invalidated = false;
|
||||||
|
SDL_SetRenderDrawColor(monitor.renderer, 0, 0, 0, 0xFF);
|
||||||
|
SDL_RenderClear(monitor.renderer);
|
||||||
|
render_texture(monitor.texture, monitor.frame_inner_x, monitor.frame_inner_y, _cfg.panel_width, _cfg.panel_height,
|
||||||
|
angle);
|
||||||
|
render_texture(monitor.texture_frameimage, 0, 0, monitor.frame_width, monitor.frame_height, angle);
|
||||||
|
SDL_RenderPresent(monitor.renderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel_sdl::render_texture(SDL_Texture *texture, int tx, int ty, int tw, int th, float angle)
|
||||||
|
{
|
||||||
|
SDL_Point pivot;
|
||||||
|
pivot.x = (monitor.frame_width / 2.0f - tx) * (float)monitor.scaling_x;
|
||||||
|
pivot.y = (monitor.frame_height / 2.0f - ty) * (float)monitor.scaling_y;
|
||||||
|
SDL_Rect dstrect;
|
||||||
|
dstrect.w = tw * monitor.scaling_x;
|
||||||
|
dstrect.h = th * monitor.scaling_y;
|
||||||
|
int mw, mh;
|
||||||
|
SDL_GetRendererOutputSize(monitor.renderer, &mw, &mh);
|
||||||
|
dstrect.x = mw / 2.0f - pivot.x;
|
||||||
|
dstrect.y = mh / 2.0f - pivot.y;
|
||||||
|
SDL_RenderCopyEx(monitor.renderer, texture, nullptr, &dstrect, angle, &pivot, SDL_RendererFlip::SDL_FLIP_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Panel_sdl::initFrameBuffer(size_t width, size_t height)
|
||||||
|
{
|
||||||
|
uint8_t **lineArray = (uint8_t **)heap_alloc_dma(height * sizeof(uint8_t *));
|
||||||
|
if (nullptr == lineArray) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_texturebuf = (rgb888_t *)heap_alloc_dma(width * height * sizeof(rgb888_t));
|
||||||
|
|
||||||
|
/// 8byte alignment;
|
||||||
|
width = (width + 7) & ~7u;
|
||||||
|
|
||||||
|
_lines_buffer = lineArray;
|
||||||
|
memset(lineArray, 0, height * sizeof(uint8_t *));
|
||||||
|
|
||||||
|
uint8_t *framebuffer = (uint8_t *)heap_alloc_dma(width * height + 16);
|
||||||
|
|
||||||
|
auto fb = framebuffer;
|
||||||
|
{
|
||||||
|
for (size_t y = 0; y < height; ++y) {
|
||||||
|
lineArray[y] = fb;
|
||||||
|
fb += width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel_sdl::deinitFrameBuffer(void)
|
||||||
|
{
|
||||||
|
auto lines = _lines_buffer;
|
||||||
|
_lines_buffer = nullptr;
|
||||||
|
if (lines != nullptr) {
|
||||||
|
heap_free(lines[0]);
|
||||||
|
heap_free(lines);
|
||||||
|
}
|
||||||
|
if (_texturebuf) {
|
||||||
|
heap_free(_texturebuf);
|
||||||
|
_texturebuf = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
} // namespace v1
|
||||||
|
} // namespace lgfx
|
||||||
|
|
||||||
|
#endif
|
||||||
166
src/graphics/Panel_sdl.hpp
Normal file
166
src/graphics/Panel_sdl.hpp
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
/*----------------------------------------------------------------------------/
|
||||||
|
Lovyan GFX - Graphics library for embedded devices.
|
||||||
|
|
||||||
|
Original Source:
|
||||||
|
https://github.com/lovyan03/LovyanGFX/
|
||||||
|
|
||||||
|
Licence:
|
||||||
|
[FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
|
||||||
|
|
||||||
|
Author:
|
||||||
|
[lovyan03](https://twitter.com/lovyan03)
|
||||||
|
|
||||||
|
Contributors:
|
||||||
|
[ciniml](https://github.com/ciniml)
|
||||||
|
[mongonta0716](https://github.com/mongonta0716)
|
||||||
|
[tobozo](https://github.com/tobozo)
|
||||||
|
|
||||||
|
Porting for SDL:
|
||||||
|
[imliubo](https://github.com/imliubo)
|
||||||
|
/----------------------------------------------------------------------------*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define SDL_MAIN_HANDLED
|
||||||
|
// cppcheck-suppress preprocessorErrorDirective
|
||||||
|
#if __has_include(<SDL2/SDL.h>)
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#include <SDL2/SDL_main.h>
|
||||||
|
#elif __has_include(<SDL.h>)
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <SDL_main.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(SDL_h_)
|
||||||
|
#include "lgfx/v1/Touch.hpp"
|
||||||
|
#include "lgfx/v1/misc/range.hpp"
|
||||||
|
#include "lgfx/v1/panel/Panel_FrameBufferBase.hpp"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace lgfx
|
||||||
|
{
|
||||||
|
inline namespace v1
|
||||||
|
{
|
||||||
|
|
||||||
|
struct Panel_sdl;
|
||||||
|
struct monitor_t {
|
||||||
|
SDL_Window *window = nullptr;
|
||||||
|
SDL_Renderer *renderer = nullptr;
|
||||||
|
SDL_Texture *texture = nullptr;
|
||||||
|
SDL_Texture *texture_frameimage = nullptr;
|
||||||
|
Panel_sdl *panel = nullptr;
|
||||||
|
|
||||||
|
// 外枠
|
||||||
|
const void *frame_image = 0;
|
||||||
|
uint_fast16_t frame_width = 0;
|
||||||
|
uint_fast16_t frame_height = 0;
|
||||||
|
uint_fast16_t frame_inner_x = 0;
|
||||||
|
uint_fast16_t frame_inner_y = 0;
|
||||||
|
int_fast16_t frame_rotation = 0;
|
||||||
|
int_fast16_t frame_angle = 0;
|
||||||
|
|
||||||
|
float scaling_x = 1;
|
||||||
|
float scaling_y = 1;
|
||||||
|
int_fast16_t touch_x, touch_y;
|
||||||
|
bool touched = false;
|
||||||
|
bool closing = false;
|
||||||
|
};
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
struct Touch_sdl : public ITouch {
|
||||||
|
bool init(void) override { return true; }
|
||||||
|
void wakeup(void) override {}
|
||||||
|
void sleep(void) override {}
|
||||||
|
bool isEnable(void) override { return true; };
|
||||||
|
uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override { return 0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
struct Panel_sdl : public Panel_FrameBufferBase {
|
||||||
|
static constexpr size_t EMULATED_GPIO_MAX = 128;
|
||||||
|
static volatile uint8_t _gpio_dummy_values[EMULATED_GPIO_MAX];
|
||||||
|
|
||||||
|
public:
|
||||||
|
Panel_sdl(void);
|
||||||
|
virtual ~Panel_sdl(void);
|
||||||
|
|
||||||
|
bool init(bool use_reset) override;
|
||||||
|
|
||||||
|
color_depth_t setColorDepth(color_depth_t depth) override;
|
||||||
|
|
||||||
|
void display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) override;
|
||||||
|
|
||||||
|
// void setInvert(bool invert) override {}
|
||||||
|
void drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor) override;
|
||||||
|
void writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor) override;
|
||||||
|
void writeBlock(uint32_t rawcolor, uint32_t length) override;
|
||||||
|
void writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param,
|
||||||
|
bool use_dma) override;
|
||||||
|
void writeImageARGB(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param) override;
|
||||||
|
void writePixels(pixelcopy_t *param, uint32_t len, bool use_dma) override;
|
||||||
|
|
||||||
|
uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override;
|
||||||
|
|
||||||
|
void setWindowTitle(const char *title);
|
||||||
|
void setScaling(uint_fast8_t scaling_x, uint_fast8_t scaling_y);
|
||||||
|
void setFrameImage(const void *frame_image, int frame_width, int frame_height, int inner_x, int inner_y);
|
||||||
|
void setFrameRotation(uint_fast16_t frame_rotaion);
|
||||||
|
void setBrightness(uint8_t brightness) override{};
|
||||||
|
|
||||||
|
static volatile void gpio_hi(uint32_t pin) { _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)] = 1; }
|
||||||
|
static volatile void gpio_lo(uint32_t pin) { _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)] = 0; }
|
||||||
|
static volatile bool gpio_in(uint32_t pin) { return _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)]; }
|
||||||
|
|
||||||
|
static int setup(void);
|
||||||
|
static int loop(void);
|
||||||
|
static int close(void);
|
||||||
|
|
||||||
|
static int main(int (*fn)(bool *), uint32_t msec_step_exec = 512);
|
||||||
|
|
||||||
|
static void setShortcutKeymod(SDL_Keymod keymod) { _keymod = keymod; }
|
||||||
|
|
||||||
|
struct KeyCodeMapping_t {
|
||||||
|
SDL_KeyCode keycode = SDLK_UNKNOWN;
|
||||||
|
uint8_t gpio = 0;
|
||||||
|
};
|
||||||
|
static void addKeyCodeMapping(SDL_KeyCode keyCode, uint8_t gpio);
|
||||||
|
static int getKeyCodeMapping(SDL_KeyCode keyCode);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const char *_window_title = "LGFX Simulator";
|
||||||
|
SDL_mutex *_sdl_mutex = nullptr;
|
||||||
|
|
||||||
|
void sdl_create(monitor_t *m);
|
||||||
|
void sdl_update(void);
|
||||||
|
|
||||||
|
touch_point_t _touch_point;
|
||||||
|
monitor_t monitor;
|
||||||
|
|
||||||
|
rgb888_t *_texturebuf = nullptr;
|
||||||
|
uint_fast16_t _modified_counter;
|
||||||
|
uint_fast16_t _texupdate_counter;
|
||||||
|
uint_fast16_t _display_counter;
|
||||||
|
bool _invalidated;
|
||||||
|
|
||||||
|
static void _event_proc(void);
|
||||||
|
static void _update_proc(void);
|
||||||
|
static void _update_scaling(monitor_t *m, float sx, float sy);
|
||||||
|
void sdl_invalidate(void) { _invalidated = true; }
|
||||||
|
void render_texture(SDL_Texture *texture, int tx, int ty, int tw, int th, float angle);
|
||||||
|
bool initFrameBuffer(size_t width, size_t height);
|
||||||
|
void deinitFrameBuffer(void);
|
||||||
|
|
||||||
|
static SDL_Keymod _keymod;
|
||||||
|
|
||||||
|
struct lock_t {
|
||||||
|
lock_t(Panel_sdl *parent);
|
||||||
|
~lock_t();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Panel_sdl *_parent;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
} // namespace v1
|
||||||
|
} // namespace lgfx
|
||||||
|
#endif
|
||||||
@@ -25,6 +25,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#include "PowerMon.h"
|
#include "PowerMon.h"
|
||||||
#include "Throttle.h"
|
#include "Throttle.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
#include "meshUtils.h"
|
||||||
#if HAS_SCREEN
|
#if HAS_SCREEN
|
||||||
#include <OLEDDisplay.h>
|
#include <OLEDDisplay.h>
|
||||||
|
|
||||||
@@ -58,7 +59,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
#include "mesh/Channels.h"
|
#include "mesh/Channels.h"
|
||||||
#include "mesh/generated/meshtastic/deviceonly.pb.h"
|
#include "mesh/generated/meshtastic/deviceonly.pb.h"
|
||||||
#include "meshUtils.h"
|
|
||||||
#include "modules/ExternalNotificationModule.h"
|
#include "modules/ExternalNotificationModule.h"
|
||||||
#include "modules/TextMessageModule.h"
|
#include "modules/TextMessageModule.h"
|
||||||
#include "modules/WaypointModule.h"
|
#include "modules/WaypointModule.h"
|
||||||
@@ -69,7 +69,11 @@ using graphics::Emote;
|
|||||||
using graphics::emotes;
|
using graphics::emotes;
|
||||||
using graphics::numEmotes;
|
using graphics::numEmotes;
|
||||||
|
|
||||||
|
#if USE_TFTDISPLAY
|
||||||
extern uint16_t TFT_MESH;
|
extern uint16_t TFT_MESH;
|
||||||
|
#else
|
||||||
|
uint16_t TFT_MESH = COLOR565(0x67, 0xEA, 0x94);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
|
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
|
||||||
#include "mesh/wifi/WiFiAPClient.h"
|
#include "mesh/wifi/WiFiAPClient.h"
|
||||||
@@ -83,6 +87,11 @@ extern uint16_t TFT_MESH;
|
|||||||
#include "platform/portduino/PortduinoGlue.h"
|
#include "platform/portduino/PortduinoGlue.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(T_LORA_PAGER)
|
||||||
|
// KB backlight control
|
||||||
|
#include "input/cardKbI2cImpl.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace meshtastic; /** @todo remove */
|
using namespace meshtastic; /** @todo remove */
|
||||||
|
|
||||||
namespace graphics
|
namespace graphics
|
||||||
@@ -95,7 +104,7 @@ namespace graphics
|
|||||||
#define NUM_EXTRA_FRAMES 3 // text message and debug frame
|
#define NUM_EXTRA_FRAMES 3 // text message and debug frame
|
||||||
// if defined a pixel will blink to show redraws
|
// if defined a pixel will blink to show redraws
|
||||||
// #define SHOW_REDRAWS
|
// #define SHOW_REDRAWS
|
||||||
|
#define ASCII_BELL '\x07'
|
||||||
// A text message frame + debug frame + all the node infos
|
// A text message frame + debug frame + all the node infos
|
||||||
FrameCallback *normalFrames;
|
FrameCallback *normalFrames;
|
||||||
static uint32_t targetFramerate = IDLE_FRAMERATE;
|
static uint32_t targetFramerate = IDLE_FRAMERATE;
|
||||||
@@ -216,6 +225,44 @@ void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t
|
|||||||
ui->update();
|
ui->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Screen::showTextInput(const char *header, const char *initialText, uint32_t durationMs,
|
||||||
|
std::function<void(const std::string &)> textCallback)
|
||||||
|
{
|
||||||
|
LOG_INFO("showTextInput called with header='%s', durationMs=%d", header ? header : "NULL", durationMs);
|
||||||
|
|
||||||
|
if (NotificationRenderer::virtualKeyboard) {
|
||||||
|
delete NotificationRenderer::virtualKeyboard;
|
||||||
|
NotificationRenderer::virtualKeyboard = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationRenderer::textInputCallback = nullptr;
|
||||||
|
|
||||||
|
NotificationRenderer::virtualKeyboard = new VirtualKeyboard();
|
||||||
|
if (header) {
|
||||||
|
NotificationRenderer::virtualKeyboard->setHeader(header);
|
||||||
|
}
|
||||||
|
if (initialText) {
|
||||||
|
NotificationRenderer::virtualKeyboard->setInputText(initialText);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up callback with safer cleanup mechanism
|
||||||
|
NotificationRenderer::textInputCallback = textCallback;
|
||||||
|
NotificationRenderer::virtualKeyboard->setCallback([textCallback](const std::string &text) { textCallback(text); });
|
||||||
|
|
||||||
|
// Store the message and set the expiration timestamp (use same pattern as other notifications)
|
||||||
|
strncpy(NotificationRenderer::alertBannerMessage, header ? header : "Text Input", 255);
|
||||||
|
NotificationRenderer::alertBannerMessage[255] = '\0';
|
||||||
|
NotificationRenderer::alertBannerUntil = (durationMs == 0) ? 0 : millis() + durationMs;
|
||||||
|
NotificationRenderer::pauseBanner = false;
|
||||||
|
NotificationRenderer::current_notification_type = notificationTypeEnum::text_input;
|
||||||
|
|
||||||
|
// Set the overlay using the same pattern as other notification types
|
||||||
|
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
|
||||||
|
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
|
||||||
|
ui->setTargetFPS(60);
|
||||||
|
ui->update();
|
||||||
|
}
|
||||||
|
|
||||||
static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
uint8_t module_frame;
|
uint8_t module_frame;
|
||||||
@@ -281,7 +328,7 @@ static int8_t prevFrame = -1;
|
|||||||
// Combined dynamic node list frame cycling through LastHeard, HopSignal, and Distance modes
|
// Combined dynamic node list frame cycling through LastHeard, HopSignal, and Distance modes
|
||||||
// Uses a single frame and changes data every few seconds (E-Ink variant is separate)
|
// Uses a single frame and changes data every few seconds (E-Ink variant is separate)
|
||||||
|
|
||||||
#if defined(ESP_PLATFORM) && defined(USE_ST7789)
|
#if defined(ESP_PLATFORM) && (defined(USE_ST7789) || defined(USE_ST7796))
|
||||||
SPIClass SPI1(HSPI);
|
SPIClass SPI1(HSPI);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -313,7 +360,13 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
|
|||||||
#else
|
#else
|
||||||
dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT);
|
dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT);
|
||||||
#endif
|
#endif
|
||||||
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
|
#elif defined(USE_ST7796)
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
dispdev = new ST7796Spi(&SPI1, ST7796_RESET, ST7796_RS, ST7796_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT, ST7796_SDA,
|
||||||
|
ST7796_MISO, ST7796_SCK, TFT_SPI_FREQUENCY);
|
||||||
|
#else
|
||||||
|
dispdev = new ST7796Spi(&SPI1, ST7796_RESET, ST7796_RS, ST7796_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT);
|
||||||
|
#endif
|
||||||
#elif defined(USE_SSD1306)
|
#elif defined(USE_SSD1306)
|
||||||
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
|
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
|
||||||
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
||||||
@@ -326,7 +379,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
|
|||||||
LOG_INFO("SSD1306 init success");
|
LOG_INFO("SSD1306 init success");
|
||||||
}
|
}
|
||||||
#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \
|
#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \
|
||||||
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
|
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(HACKADAY_COMMUNICATOR)
|
||||||
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
|
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
|
||||||
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
||||||
#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
|
#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
|
||||||
@@ -340,7 +393,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
|
|||||||
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
||||||
#elif ARCH_PORTDUINO
|
#elif ARCH_PORTDUINO
|
||||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||||
if (settingsMap[displayPanel] != no_screen) {
|
if (portduino_config.displayPanel != no_screen) {
|
||||||
LOG_DEBUG("Make TFTDisplay!");
|
LOG_DEBUG("Make TFTDisplay!");
|
||||||
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
|
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
|
||||||
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
||||||
@@ -356,6 +409,12 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
|
|||||||
isAUTOOled = true;
|
isAUTOOled = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(USE_ST7789)
|
||||||
|
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
|
||||||
|
#elif defined(USE_ST7796)
|
||||||
|
static_cast<ST7796Spi *>(dispdev)->setRGB(TFT_MESH);
|
||||||
|
#endif
|
||||||
|
|
||||||
ui = new OLEDDisplayUi(dispdev);
|
ui = new OLEDDisplayUi(dispdev);
|
||||||
cmdQueue.setReader(this);
|
cmdQueue.setReader(this);
|
||||||
}
|
}
|
||||||
@@ -392,6 +451,14 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
|||||||
PMU->enablePowerOutput(XPOWERS_ALDO2);
|
PMU->enablePowerOutput(XPOWERS_ALDO2);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(MUZI_BASE)
|
||||||
|
dispdev->init();
|
||||||
|
dispdev->setBrightness(brightness);
|
||||||
|
dispdev->flipScreenVertically();
|
||||||
|
dispdev->resetDisplay();
|
||||||
|
digitalWrite(SCREEN_12V_ENABLE, HIGH);
|
||||||
|
delay(100);
|
||||||
|
#endif
|
||||||
#if !ARCH_PORTDUINO
|
#if !ARCH_PORTDUINO
|
||||||
dispdev->displayOn();
|
dispdev->displayOn();
|
||||||
#endif
|
#endif
|
||||||
@@ -400,7 +467,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
|||||||
if (uiconfig.screen_brightness == 1)
|
if (uiconfig.screen_brightness == 1)
|
||||||
digitalWrite(PIN_EINK_EN, HIGH);
|
digitalWrite(PIN_EINK_EN, HIGH);
|
||||||
#elif defined(PCA_PIN_EINK_EN)
|
#elif defined(PCA_PIN_EINK_EN)
|
||||||
if (uiconfig.screen_brightness == 1)
|
if (uiconfig.screen_brightness > 0)
|
||||||
io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
|
io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -410,7 +477,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
dispdev->displayOn();
|
dispdev->displayOn();
|
||||||
#ifdef HELTEC_TRACKER_V1_X
|
#if defined(HELTEC_TRACKER_V1_X) || defined(HELTEC_WIRELESS_TRACKER_V2)
|
||||||
ui->init();
|
ui->init();
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_ST7789
|
#ifdef USE_ST7789
|
||||||
@@ -423,6 +490,15 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
|||||||
pinMode(VTFT_LEDA, OUTPUT);
|
pinMode(VTFT_LEDA, OUTPUT);
|
||||||
digitalWrite(VTFT_LEDA, TFT_BACKLIGHT_ON);
|
digitalWrite(VTFT_LEDA, TFT_BACKLIGHT_ON);
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ST7796
|
||||||
|
ui->init();
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
analogWrite(VTFT_LEDA, BRIGHTNESS_DEFAULT);
|
||||||
|
#else
|
||||||
|
pinMode(VTFT_LEDA, OUTPUT);
|
||||||
|
digitalWrite(VTFT_LEDA, TFT_BACKLIGHT_ON);
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
enabled = true;
|
enabled = true;
|
||||||
setInterval(0); // Draw ASAP
|
setInterval(0); // Draw ASAP
|
||||||
@@ -441,6 +517,10 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
dispdev->displayOff();
|
dispdev->displayOff();
|
||||||
|
|
||||||
|
#ifdef SCREEN_12V_ENABLE
|
||||||
|
digitalWrite(SCREEN_12V_ENABLE, LOW);
|
||||||
|
#endif
|
||||||
#ifdef USE_ST7789
|
#ifdef USE_ST7789
|
||||||
SPI1.end();
|
SPI1.end();
|
||||||
#if defined(ARCH_ESP32)
|
#if defined(ARCH_ESP32)
|
||||||
@@ -457,6 +537,21 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
|||||||
nrf_gpio_cfg_default(ST7789_NSS);
|
nrf_gpio_cfg_default(ST7789_NSS);
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_ST7796
|
||||||
|
SPI1.end();
|
||||||
|
#if defined(ARCH_ESP32)
|
||||||
|
pinMode(VTFT_LEDA, OUTPUT);
|
||||||
|
digitalWrite(VTFT_LEDA, LOW);
|
||||||
|
pinMode(ST7796_RESET, ANALOG);
|
||||||
|
pinMode(ST7796_RS, ANALOG);
|
||||||
|
pinMode(ST7796_NSS, ANALOG);
|
||||||
|
#else
|
||||||
|
nrf_gpio_cfg_default(VTFT_LEDA);
|
||||||
|
nrf_gpio_cfg_default(ST7796_RESET);
|
||||||
|
nrf_gpio_cfg_default(ST7796_RS);
|
||||||
|
nrf_gpio_cfg_default(ST7796_NSS);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef T_WATCH_S3
|
#ifdef T_WATCH_S3
|
||||||
PMU->disablePowerOutput(XPOWERS_ALDO2);
|
PMU->disablePowerOutput(XPOWERS_ALDO2);
|
||||||
@@ -491,7 +586,7 @@ void Screen::setup()
|
|||||||
static_cast<AutoOLEDWire *>(dispdev)->setDetected(model);
|
static_cast<AutoOLEDWire *>(dispdev)->setDetected(model);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_SH1107_128_64
|
#if defined(USE_SH1107_128_64) || defined(USE_SH1107)
|
||||||
static_cast<SH1106Wire *>(dispdev)->setSubtype(7);
|
static_cast<SH1106Wire *>(dispdev)->setSubtype(7);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -499,6 +594,13 @@ void Screen::setup()
|
|||||||
// Apply custom RGB color (e.g. Heltec T114/T190)
|
// Apply custom RGB color (e.g. Heltec T114/T190)
|
||||||
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
|
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(MUZI_BASE)
|
||||||
|
dispdev->delayPoweron = true;
|
||||||
|
#endif
|
||||||
|
#if defined(USE_ST7796) && defined(TFT_MESH)
|
||||||
|
// Custom text color, if defined in variant.h
|
||||||
|
static_cast<ST7796Spi *>(dispdev)->setRGB(TFT_MESH);
|
||||||
|
#endif
|
||||||
|
|
||||||
// === Initialize display and UI system ===
|
// === Initialize display and UI system ===
|
||||||
ui->init();
|
ui->init();
|
||||||
@@ -558,10 +660,12 @@ void Screen::setup()
|
|||||||
#else
|
#else
|
||||||
if (!config.display.flip_screen) {
|
if (!config.display.flip_screen) {
|
||||||
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
|
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
|
||||||
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
|
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(HACKADAY_COMMUNICATOR)
|
||||||
static_cast<TFTDisplay *>(dispdev)->flipScreenVertically();
|
static_cast<TFTDisplay *>(dispdev)->flipScreenVertically();
|
||||||
#elif defined(USE_ST7789)
|
#elif defined(USE_ST7789)
|
||||||
static_cast<ST7789Spi *>(dispdev)->flipScreenVertically();
|
static_cast<ST7789Spi *>(dispdev)->flipScreenVertically();
|
||||||
|
#elif defined(USE_ST7796)
|
||||||
|
static_cast<ST7796Spi *>(dispdev)->mirrorScreen();
|
||||||
#elif !defined(M5STACK_UNITC6L)
|
#elif !defined(M5STACK_UNITC6L)
|
||||||
dispdev->flipScreenVertically();
|
dispdev->flipScreenVertically();
|
||||||
#endif
|
#endif
|
||||||
@@ -588,13 +692,13 @@ void Screen::setup()
|
|||||||
|
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||||
if (settingsMap[touchscreenModule]) {
|
if (portduino_config.touchscreenModule) {
|
||||||
touchScreenImpl1 =
|
touchScreenImpl1 =
|
||||||
new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast<TFTDisplay *>(dispdev)->getTouch);
|
new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast<TFTDisplay *>(dispdev)->getTouch);
|
||||||
touchScreenImpl1->init();
|
touchScreenImpl1->init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#elif HAS_TOUCHSCREEN && !defined(USE_EINK)
|
#elif HAS_TOUCHSCREEN && !defined(USE_EINK) && !HAS_CST226SE
|
||||||
touchScreenImpl1 =
|
touchScreenImpl1 =
|
||||||
new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast<TFTDisplay *>(dispdev)->getTouch);
|
new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast<TFTDisplay *>(dispdev)->getTouch);
|
||||||
touchScreenImpl1->init();
|
touchScreenImpl1->init();
|
||||||
@@ -617,6 +721,19 @@ void Screen::setup()
|
|||||||
MeshModule::observeUIEvents(&uiFrameEventObserver);
|
MeshModule::observeUIEvents(&uiFrameEventObserver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Screen::setOn(bool on, FrameCallback einkScreensaver)
|
||||||
|
{
|
||||||
|
#if defined(T_LORA_PAGER)
|
||||||
|
if (cardKbI2cImpl)
|
||||||
|
cardKbI2cImpl->toggleBacklight(on);
|
||||||
|
#endif
|
||||||
|
if (!on)
|
||||||
|
// We handle off commands immediately, because they might be called because the CPU is shutting down
|
||||||
|
handleSetOn(false, einkScreensaver);
|
||||||
|
else
|
||||||
|
enqueueCmd(ScreenCmd{.cmd = Cmd::SET_ON});
|
||||||
|
}
|
||||||
|
|
||||||
void Screen::forceDisplay(bool forceUiUpdate)
|
void Screen::forceDisplay(bool forceUiUpdate)
|
||||||
{
|
{
|
||||||
// Nasty hack to force epaper updates for 'key' frames. FIXME, cleanup.
|
// Nasty hack to force epaper updates for 'key' frames. FIXME, cleanup.
|
||||||
@@ -725,13 +842,19 @@ int32_t Screen::runOnce()
|
|||||||
handleSetOn(false);
|
handleSetOn(false);
|
||||||
break;
|
break;
|
||||||
case Cmd::ON_PRESS:
|
case Cmd::ON_PRESS:
|
||||||
handleOnPress();
|
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
||||||
|
handleOnPress();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Cmd::SHOW_PREV_FRAME:
|
case Cmd::SHOW_PREV_FRAME:
|
||||||
handleShowPrevFrame();
|
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
||||||
|
handleShowPrevFrame();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Cmd::SHOW_NEXT_FRAME:
|
case Cmd::SHOW_NEXT_FRAME:
|
||||||
handleShowNextFrame();
|
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
||||||
|
handleShowNextFrame();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Cmd::START_ALERT_FRAME: {
|
case Cmd::START_ALERT_FRAME: {
|
||||||
showingBootScreen = false; // this should avoid the edge case where an alert triggers before the boot screen goes away
|
showingBootScreen = false; // this should avoid the edge case where an alert triggers before the boot screen goes away
|
||||||
@@ -753,7 +876,9 @@ int32_t Screen::runOnce()
|
|||||||
NotificationRenderer::pauseBanner = false;
|
NotificationRenderer::pauseBanner = false;
|
||||||
case Cmd::STOP_BOOT_SCREEN:
|
case Cmd::STOP_BOOT_SCREEN:
|
||||||
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
|
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
|
||||||
setFrames();
|
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
||||||
|
setFrames();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Cmd::NOOP:
|
case Cmd::NOOP:
|
||||||
break;
|
break;
|
||||||
@@ -789,6 +914,7 @@ int32_t Screen::runOnce()
|
|||||||
if (showingNormalScreen) {
|
if (showingNormalScreen) {
|
||||||
// standard screen loop handling here
|
// standard screen loop handling here
|
||||||
if (config.display.auto_screen_carousel_secs > 0 &&
|
if (config.display.auto_screen_carousel_secs > 0 &&
|
||||||
|
NotificationRenderer::current_notification_type != notificationTypeEnum::text_input &&
|
||||||
!Throttle::isWithinTimespanMs(lastScreenTransition, config.display.auto_screen_carousel_secs * 1000)) {
|
!Throttle::isWithinTimespanMs(lastScreenTransition, config.display.auto_screen_carousel_secs * 1000)) {
|
||||||
|
|
||||||
// If an E-Ink display struggles with fast refresh, force carousel to use full refresh instead
|
// If an E-Ink display struggles with fast refresh, force carousel to use full refresh instead
|
||||||
@@ -879,6 +1005,11 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver)
|
|||||||
// Called when a frame should be added / removed, or custom frames should be cleared
|
// Called when a frame should be added / removed, or custom frames should be cleared
|
||||||
void Screen::setFrames(FrameFocus focus)
|
void Screen::setFrames(FrameFocus focus)
|
||||||
{
|
{
|
||||||
|
// Block setFrames calls when virtual keyboard is active to prevent overlay interference
|
||||||
|
if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t originalPosition = ui->getUiState()->currentFrame;
|
uint8_t originalPosition = ui->getUiState()->currentFrame;
|
||||||
uint8_t previousFrameCount = framesetInfo.frameCount;
|
uint8_t previousFrameCount = framesetInfo.frameCount;
|
||||||
FramesetInfo fsi; // Location of specific frames, for applying focus parameter
|
FramesetInfo fsi; // Location of specific frames, for applying focus parameter
|
||||||
@@ -901,75 +1032,95 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if defined(DISPLAY_CLOCK_FRAME)
|
#if defined(DISPLAY_CLOCK_FRAME)
|
||||||
fsi.positions.clock = numframes;
|
if (!hiddenFrames.clock) {
|
||||||
|
fsi.positions.clock = numframes;
|
||||||
#if defined(M5STACK_UNITC6L)
|
#if defined(M5STACK_UNITC6L)
|
||||||
normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame;
|
normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame;
|
||||||
#else
|
#else
|
||||||
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
|
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
|
||||||
: graphics::ClockRenderer::drawDigitalClockFrame;
|
: graphics::ClockRenderer::drawDigitalClockFrame;
|
||||||
#endif
|
#endif
|
||||||
indicatorIcons.push_back(digital_icon_clock);
|
indicatorIcons.push_back(digital_icon_clock);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Declare this early so it’s available in FOCUS_PRESERVE block
|
// Declare this early so it’s available in FOCUS_PRESERVE block
|
||||||
bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message);
|
bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message);
|
||||||
|
|
||||||
fsi.positions.home = numframes;
|
if (!hiddenFrames.home) {
|
||||||
normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused;
|
fsi.positions.home = numframes;
|
||||||
indicatorIcons.push_back(icon_home);
|
normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused;
|
||||||
|
indicatorIcons.push_back(icon_home);
|
||||||
|
}
|
||||||
|
|
||||||
fsi.positions.textMessage = numframes;
|
fsi.positions.textMessage = numframes;
|
||||||
normalFrames[numframes++] = graphics::MessageRenderer::drawTextMessageFrame;
|
normalFrames[numframes++] = graphics::MessageRenderer::drawTextMessageFrame;
|
||||||
indicatorIcons.push_back(icon_mail);
|
indicatorIcons.push_back(icon_mail);
|
||||||
|
|
||||||
#ifndef USE_EINK
|
#ifndef USE_EINK
|
||||||
fsi.positions.nodelist = numframes;
|
if (!hiddenFrames.nodelist) {
|
||||||
normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen;
|
fsi.positions.nodelist = numframes;
|
||||||
indicatorIcons.push_back(icon_nodes);
|
normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen;
|
||||||
|
indicatorIcons.push_back(icon_nodes);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Show detailed node views only on E-Ink builds
|
// Show detailed node views only on E-Ink builds
|
||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
fsi.positions.nodelist_lastheard = numframes;
|
if (!hiddenFrames.nodelist_lastheard) {
|
||||||
normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen;
|
fsi.positions.nodelist_lastheard = numframes;
|
||||||
indicatorIcons.push_back(icon_nodes);
|
normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen;
|
||||||
|
indicatorIcons.push_back(icon_nodes);
|
||||||
fsi.positions.nodelist_hopsignal = numframes;
|
}
|
||||||
normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen;
|
if (!hiddenFrames.nodelist_hopsignal) {
|
||||||
indicatorIcons.push_back(icon_signal);
|
fsi.positions.nodelist_hopsignal = numframes;
|
||||||
|
normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen;
|
||||||
fsi.positions.nodelist_distance = numframes;
|
indicatorIcons.push_back(icon_signal);
|
||||||
normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen;
|
}
|
||||||
indicatorIcons.push_back(icon_distance);
|
if (!hiddenFrames.nodelist_distance) {
|
||||||
|
fsi.positions.nodelist_distance = numframes;
|
||||||
|
normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen;
|
||||||
|
indicatorIcons.push_back(icon_distance);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
#if HAS_GPS
|
#if HAS_GPS
|
||||||
fsi.positions.nodelist_bearings = numframes;
|
if (!hiddenFrames.nodelist_bearings) {
|
||||||
normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses;
|
fsi.positions.nodelist_bearings = numframes;
|
||||||
indicatorIcons.push_back(icon_list);
|
normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses;
|
||||||
|
indicatorIcons.push_back(icon_list);
|
||||||
fsi.positions.gps = numframes;
|
}
|
||||||
normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen;
|
if (!hiddenFrames.gps) {
|
||||||
indicatorIcons.push_back(icon_compass);
|
fsi.positions.gps = numframes;
|
||||||
|
normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen;
|
||||||
|
indicatorIcons.push_back(icon_compass);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
if (RadioLibInterface::instance) {
|
if (RadioLibInterface::instance && !hiddenFrames.lora) {
|
||||||
fsi.positions.lora = numframes;
|
fsi.positions.lora = numframes;
|
||||||
normalFrames[numframes++] = graphics::DebugRenderer::drawLoRaFocused;
|
normalFrames[numframes++] = graphics::DebugRenderer::drawLoRaFocused;
|
||||||
indicatorIcons.push_back(icon_radio);
|
indicatorIcons.push_back(icon_radio);
|
||||||
}
|
}
|
||||||
if (!dismissedFrames.memory) {
|
if (!hiddenFrames.system) {
|
||||||
fsi.positions.memory = numframes;
|
fsi.positions.system = numframes;
|
||||||
normalFrames[numframes++] = graphics::DebugRenderer::drawMemoryUsage;
|
normalFrames[numframes++] = graphics::DebugRenderer::drawSystemScreen;
|
||||||
indicatorIcons.push_back(icon_memory);
|
indicatorIcons.push_back(icon_system);
|
||||||
}
|
}
|
||||||
#if !defined(DISPLAY_CLOCK_FRAME)
|
#if !defined(DISPLAY_CLOCK_FRAME)
|
||||||
fsi.positions.clock = numframes;
|
if (!hiddenFrames.clock) {
|
||||||
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
|
fsi.positions.clock = numframes;
|
||||||
: graphics::ClockRenderer::drawDigitalClockFrame;
|
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
|
||||||
indicatorIcons.push_back(digital_icon_clock);
|
: graphics::ClockRenderer::drawDigitalClockFrame;
|
||||||
|
indicatorIcons.push_back(digital_icon_clock);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
if (!hiddenFrames.chirpy) {
|
||||||
|
fsi.positions.chirpy = numframes;
|
||||||
|
normalFrames[numframes++] = graphics::DebugRenderer::drawChirpy;
|
||||||
|
indicatorIcons.push_back(chirpy_small);
|
||||||
|
}
|
||||||
|
|
||||||
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
|
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
|
||||||
if (!dismissedFrames.wifi && isWifiAvailable()) {
|
if (!hiddenFrames.wifi && isWifiAvailable()) {
|
||||||
fsi.positions.wifi = numframes;
|
fsi.positions.wifi = numframes;
|
||||||
normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoWiFiTrampoline;
|
normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoWiFiTrampoline;
|
||||||
indicatorIcons.push_back(icon_wifi);
|
indicatorIcons.push_back(icon_wifi);
|
||||||
@@ -1011,27 +1162,29 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
if (numMeshNodes > 0)
|
if (numMeshNodes > 0)
|
||||||
numMeshNodes--;
|
numMeshNodes--;
|
||||||
|
|
||||||
// Temporary array to hold favorite node frames
|
if (!hiddenFrames.show_favorites) {
|
||||||
std::vector<FrameCallback> favoriteFrames;
|
// Temporary array to hold favorite node frames
|
||||||
|
std::vector<FrameCallback> favoriteFrames;
|
||||||
|
|
||||||
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||||
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
|
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
|
||||||
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
|
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
|
||||||
favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
|
favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Insert favorite frames *after* collecting them all
|
// Insert favorite frames *after* collecting them all
|
||||||
if (!favoriteFrames.empty()) {
|
if (!favoriteFrames.empty()) {
|
||||||
fsi.positions.firstFavorite = numframes;
|
fsi.positions.firstFavorite = numframes;
|
||||||
for (const auto &f : favoriteFrames) {
|
for (const auto &f : favoriteFrames) {
|
||||||
normalFrames[numframes++] = f;
|
normalFrames[numframes++] = f;
|
||||||
indicatorIcons.push_back(icon_node);
|
indicatorIcons.push_back(icon_node);
|
||||||
|
}
|
||||||
|
fsi.positions.lastFavorite = numframes - 1;
|
||||||
|
} else {
|
||||||
|
fsi.positions.firstFavorite = 255;
|
||||||
|
fsi.positions.lastFavorite = 255;
|
||||||
}
|
}
|
||||||
fsi.positions.lastFavorite = numframes - 1;
|
|
||||||
} else {
|
|
||||||
fsi.positions.firstFavorite = 255;
|
|
||||||
fsi.positions.lastFavorite = 255;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE
|
fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE
|
||||||
@@ -1070,7 +1223,7 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
ui->switchToFrame(fsi.positions.clock);
|
ui->switchToFrame(fsi.positions.clock);
|
||||||
break;
|
break;
|
||||||
case FOCUS_SYSTEM:
|
case FOCUS_SYSTEM:
|
||||||
ui->switchToFrame(fsi.positions.memory);
|
ui->switchToFrame(fsi.positions.system);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FOCUS_PRESERVE:
|
case FOCUS_PRESERVE:
|
||||||
@@ -1098,30 +1251,101 @@ void Screen::setFrameImmediateDraw(FrameCallback *drawFrames)
|
|||||||
setFastFramerate();
|
setFastFramerate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Screen::toggleFrameVisibility(const std::string &frameName)
|
||||||
|
{
|
||||||
|
#ifndef USE_EINK
|
||||||
|
if (frameName == "nodelist") {
|
||||||
|
hiddenFrames.nodelist = !hiddenFrames.nodelist;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_EINK
|
||||||
|
if (frameName == "nodelist_lastheard") {
|
||||||
|
hiddenFrames.nodelist_lastheard = !hiddenFrames.nodelist_lastheard;
|
||||||
|
}
|
||||||
|
if (frameName == "nodelist_hopsignal") {
|
||||||
|
hiddenFrames.nodelist_hopsignal = !hiddenFrames.nodelist_hopsignal;
|
||||||
|
}
|
||||||
|
if (frameName == "nodelist_distance") {
|
||||||
|
hiddenFrames.nodelist_distance = !hiddenFrames.nodelist_distance;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if HAS_GPS
|
||||||
|
if (frameName == "nodelist_bearings") {
|
||||||
|
hiddenFrames.nodelist_bearings = !hiddenFrames.nodelist_bearings;
|
||||||
|
}
|
||||||
|
if (frameName == "gps") {
|
||||||
|
hiddenFrames.gps = !hiddenFrames.gps;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (frameName == "lora") {
|
||||||
|
hiddenFrames.lora = !hiddenFrames.lora;
|
||||||
|
}
|
||||||
|
if (frameName == "clock") {
|
||||||
|
hiddenFrames.clock = !hiddenFrames.clock;
|
||||||
|
}
|
||||||
|
if (frameName == "show_favorites") {
|
||||||
|
hiddenFrames.show_favorites = !hiddenFrames.show_favorites;
|
||||||
|
}
|
||||||
|
if (frameName == "chirpy") {
|
||||||
|
hiddenFrames.chirpy = !hiddenFrames.chirpy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Screen::isFrameHidden(const std::string &frameName) const
|
||||||
|
{
|
||||||
|
#ifndef USE_EINK
|
||||||
|
if (frameName == "nodelist")
|
||||||
|
return hiddenFrames.nodelist;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_EINK
|
||||||
|
if (frameName == "nodelist_lastheard")
|
||||||
|
return hiddenFrames.nodelist_lastheard;
|
||||||
|
if (frameName == "nodelist_hopsignal")
|
||||||
|
return hiddenFrames.nodelist_hopsignal;
|
||||||
|
if (frameName == "nodelist_distance")
|
||||||
|
return hiddenFrames.nodelist_distance;
|
||||||
|
#endif
|
||||||
|
#if HAS_GPS
|
||||||
|
if (frameName == "nodelist_bearings")
|
||||||
|
return hiddenFrames.nodelist_bearings;
|
||||||
|
if (frameName == "gps")
|
||||||
|
return hiddenFrames.gps;
|
||||||
|
#endif
|
||||||
|
if (frameName == "lora")
|
||||||
|
return hiddenFrames.lora;
|
||||||
|
if (frameName == "clock")
|
||||||
|
return hiddenFrames.clock;
|
||||||
|
if (frameName == "show_favorites")
|
||||||
|
return hiddenFrames.show_favorites;
|
||||||
|
if (frameName == "chirpy")
|
||||||
|
return hiddenFrames.chirpy;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Dismisses the currently displayed screen frame, if possible
|
// Dismisses the currently displayed screen frame, if possible
|
||||||
// Relevant for text message, waypoint, others in future?
|
// Relevant for text message, waypoint, others in future?
|
||||||
// Triggered with a CardKB keycombo
|
// Triggered with a CardKB keycombo
|
||||||
void Screen::dismissCurrentFrame()
|
void Screen::hideCurrentFrame()
|
||||||
{
|
{
|
||||||
uint8_t currentFrame = ui->getUiState()->currentFrame;
|
uint8_t currentFrame = ui->getUiState()->currentFrame;
|
||||||
bool dismissed = false;
|
bool dismissed = false;
|
||||||
|
|
||||||
if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) {
|
if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) {
|
||||||
LOG_INFO("Dismiss Text Message");
|
LOG_INFO("Hide Text Message");
|
||||||
devicestate.has_rx_text_message = false;
|
devicestate.has_rx_text_message = false;
|
||||||
memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
|
memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
|
||||||
} else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) {
|
} else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) {
|
||||||
LOG_DEBUG("Dismiss Waypoint");
|
LOG_DEBUG("Hide Waypoint");
|
||||||
devicestate.has_rx_waypoint = false;
|
devicestate.has_rx_waypoint = false;
|
||||||
dismissedFrames.waypoint = true;
|
hiddenFrames.waypoint = true;
|
||||||
dismissed = true;
|
dismissed = true;
|
||||||
} else if (currentFrame == framesetInfo.positions.wifi) {
|
} else if (currentFrame == framesetInfo.positions.wifi) {
|
||||||
LOG_DEBUG("Dismiss WiFi Screen");
|
LOG_DEBUG("Hide WiFi Screen");
|
||||||
dismissedFrames.wifi = true;
|
hiddenFrames.wifi = true;
|
||||||
dismissed = true;
|
dismissed = true;
|
||||||
} else if (currentFrame == framesetInfo.positions.memory) {
|
} else if (currentFrame == framesetInfo.positions.lora) {
|
||||||
LOG_INFO("Dismiss Memory");
|
LOG_INFO("Hide LoRa");
|
||||||
dismissedFrames.memory = true;
|
hiddenFrames.lora = true;
|
||||||
dismissed = true;
|
dismissed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1154,7 +1378,8 @@ void Screen::blink()
|
|||||||
delay(50);
|
delay(50);
|
||||||
count = count - 1;
|
count = count - 1;
|
||||||
}
|
}
|
||||||
// The dispdev->setBrightness does not work for t-deck display, it seems to run the setBrightness function in OLEDDisplay.
|
// The dispdev->setBrightness does not work for t-deck display, it seems to run the setBrightness function in
|
||||||
|
// OLEDDisplay.
|
||||||
dispdev->setBrightness(brightness);
|
dispdev->setBrightness(brightness);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1264,6 +1489,9 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
|
|||||||
}
|
}
|
||||||
nodeDB->updateGUI = false;
|
nodeDB->updateGUI = false;
|
||||||
break;
|
break;
|
||||||
|
case STATUS_TYPE_POWER:
|
||||||
|
forceDisplay(true);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1277,7 +1505,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
|||||||
// Outgoing message (likely sent from phone)
|
// Outgoing message (likely sent from phone)
|
||||||
devicestate.has_rx_text_message = false;
|
devicestate.has_rx_text_message = false;
|
||||||
memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
|
memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
|
||||||
dismissedFrames.textMessage = true;
|
hiddenFrames.textMessage = true;
|
||||||
hasUnreadMessage = false; // Clear unread state when user replies
|
hasUnreadMessage = false; // Clear unread state when user replies
|
||||||
|
|
||||||
setFrames(FOCUS_PRESERVE); // Stay on same frame, silently update frame list
|
setFrames(FOCUS_PRESERVE); // Stay on same frame, silently update frame list
|
||||||
@@ -1294,28 +1522,36 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
|||||||
}
|
}
|
||||||
// === Prepare banner content ===
|
// === Prepare banner content ===
|
||||||
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
|
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
|
||||||
|
const meshtastic_Channel channel =
|
||||||
|
channels.getByIndex(packet->channel ? packet->channel : channels.getPrimaryIndex());
|
||||||
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
|
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
|
||||||
|
|
||||||
const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes);
|
const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes);
|
||||||
|
|
||||||
char banner[256];
|
char banner[256];
|
||||||
|
|
||||||
// Check for bell character in message to determine alert type
|
|
||||||
bool isAlert = false;
|
bool isAlert = false;
|
||||||
for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
|
|
||||||
if (msgRaw[i] == '\x07') {
|
|
||||||
isAlert = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (moduleConfig.external_notification.alert_bell || moduleConfig.external_notification.alert_bell_vibra ||
|
||||||
|
moduleConfig.external_notification.alert_bell_buzzer)
|
||||||
|
// Check for bell character to determine if this message is an alert
|
||||||
|
for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
|
||||||
|
if (msgRaw[i] == ASCII_BELL) {
|
||||||
|
isAlert = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlike generic messages, alerts (when enabled via the ext notif module) ignore any
|
||||||
|
// 'mute' preferences set to any specific node or channel.
|
||||||
if (isAlert) {
|
if (isAlert) {
|
||||||
if (longName && longName[0]) {
|
if (longName && longName[0]) {
|
||||||
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
|
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
|
||||||
} else {
|
} else {
|
||||||
strcpy(banner, "Alert Received");
|
strcpy(banner, "Alert Received");
|
||||||
}
|
}
|
||||||
} else {
|
screen->showSimpleBanner(banner, 3000);
|
||||||
|
} else if (!channel.settings.has_module_settings || !channel.settings.module_settings.is_muted) {
|
||||||
if (longName && longName[0]) {
|
if (longName && longName[0]) {
|
||||||
#if defined(M5STACK_UNITC6L)
|
#if defined(M5STACK_UNITC6L)
|
||||||
strcpy(banner, "New Message");
|
strcpy(banner, "New Message");
|
||||||
@@ -1326,14 +1562,21 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
|||||||
} else {
|
} else {
|
||||||
strcpy(banner, "New Message");
|
strcpy(banner, "New Message");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#if defined(M5STACK_UNITC6L)
|
#if defined(M5STACK_UNITC6L)
|
||||||
screen->setOn(true);
|
screen->setOn(true);
|
||||||
screen->showSimpleBanner(banner, 1500);
|
screen->showSimpleBanner(banner, 1500);
|
||||||
playLongBeep();
|
if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
|
||||||
|
(isAlert && moduleConfig.external_notification.alert_bell_buzzer) ||
|
||||||
|
(!isBroadcast(packet->to) && isToUs(packet))) {
|
||||||
|
// Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either
|
||||||
|
// - packet contains an alert and alert bell buzzer is enabled
|
||||||
|
// - packet is a non-broadcast that is addressed to this node
|
||||||
|
playLongBeep();
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
screen->showSimpleBanner(banner, 3000);
|
screen->showSimpleBanner(banner, 3000);
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1343,6 +1586,11 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
|||||||
// Triggered by MeshModules
|
// Triggered by MeshModules
|
||||||
int Screen::handleUIFrameEvent(const UIFrameEvent *event)
|
int Screen::handleUIFrameEvent(const UIFrameEvent *event)
|
||||||
{
|
{
|
||||||
|
// Block UI frame events when virtual keyboard is active
|
||||||
|
if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (showingNormalScreen) {
|
if (showingNormalScreen) {
|
||||||
// Regenerate the frameset, potentially honoring a module's internal requestFocus() call
|
// Regenerate the frameset, potentially honoring a module's internal requestFocus() call
|
||||||
if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET)
|
if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET)
|
||||||
@@ -1362,9 +1610,20 @@ int Screen::handleUIFrameEvent(const UIFrameEvent *event)
|
|||||||
|
|
||||||
int Screen::handleInputEvent(const InputEvent *event)
|
int Screen::handleInputEvent(const InputEvent *event)
|
||||||
{
|
{
|
||||||
|
LOG_INPUT("Screen Input event %u! kb %u", event->inputEvent, event->kbchar);
|
||||||
if (!screenOn)
|
if (!screenOn)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
// Handle text input notifications specially - pass input to virtual keyboard
|
||||||
|
if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
|
||||||
|
NotificationRenderer::inEvent = *event;
|
||||||
|
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
|
||||||
|
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
|
||||||
|
setFastFramerate(); // Draw ASAP
|
||||||
|
ui->update();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef USE_EINK // the screen is the last input handler, so if an event makes it here, we can assume it will prompt a screen draw.
|
#ifdef USE_EINK // the screen is the last input handler, so if an event makes it here, we can assume it will prompt a screen draw.
|
||||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
|
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
|
||||||
EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update
|
EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update
|
||||||
@@ -1402,7 +1661,7 @@ int Screen::handleInputEvent(const InputEvent *event)
|
|||||||
} else if (event->inputEvent == INPUT_BROKER_SELECT) {
|
} else if (event->inputEvent == INPUT_BROKER_SELECT) {
|
||||||
if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
|
if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
|
||||||
menuHandler::homeBaseMenu();
|
menuHandler::homeBaseMenu();
|
||||||
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.memory) {
|
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.system) {
|
||||||
menuHandler::systemBaseMenu();
|
menuHandler::systemBaseMenu();
|
||||||
#if HAS_GPS
|
#if HAS_GPS
|
||||||
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.gps && gps) {
|
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.gps && gps) {
|
||||||
@@ -1411,7 +1670,7 @@ int Screen::handleInputEvent(const InputEvent *event)
|
|||||||
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.clock) {
|
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.clock) {
|
||||||
menuHandler::clockMenu();
|
menuHandler::clockMenu();
|
||||||
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) {
|
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) {
|
||||||
menuHandler::LoraRegionPicker();
|
menuHandler::loraMenu();
|
||||||
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
|
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
|
||||||
if (devicestate.rx_text_message.from) {
|
if (devicestate.rx_text_message.from) {
|
||||||
menuHandler::messageResponseMenu();
|
menuHandler::messageResponseMenu();
|
||||||
@@ -1479,13 +1738,15 @@ bool shouldWakeOnReceivedMessage()
|
|||||||
/*
|
/*
|
||||||
The goal here is to determine when we do NOT wake up the screen on message received:
|
The goal here is to determine when we do NOT wake up the screen on message received:
|
||||||
- Any ext. notifications are turned on
|
- Any ext. notifications are turned on
|
||||||
- If role is not client / client_mute
|
- If role is not CLIENT / CLIENT_MUTE / CLIENT_HIDDEN / CLIENT_BASE
|
||||||
- If the battery level is very low
|
- If the battery level is very low
|
||||||
*/
|
*/
|
||||||
if (moduleConfig.external_notification.enabled) {
|
if (moduleConfig.external_notification.enabled) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!meshtastic_Config_DeviceConfig_Role_CLIENT && !meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE) {
|
if (!IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_CLIENT,
|
||||||
|
meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE, meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN,
|
||||||
|
meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (powerStatus && powerStatus->getBatteryChargePercent() < 10) {
|
if (powerStatus && powerStatus->getBatteryChargePercent() < 10) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
|
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
|
||||||
namespace graphics
|
namespace graphics
|
||||||
{
|
{
|
||||||
enum notificationTypeEnum { none, text_banner, selection_picker, node_picker, number_picker };
|
enum notificationTypeEnum { none, text_banner, selection_picker, node_picker, number_picker, text_input };
|
||||||
|
|
||||||
struct BannerOverlayOptions {
|
struct BannerOverlayOptions {
|
||||||
const char *message;
|
const char *message;
|
||||||
@@ -83,6 +83,8 @@ class Screen
|
|||||||
#include <ST7789Spi.h>
|
#include <ST7789Spi.h>
|
||||||
#elif defined(USE_SPISSD1306)
|
#elif defined(USE_SPISSD1306)
|
||||||
#include <SSD1306Spi.h>
|
#include <SSD1306Spi.h>
|
||||||
|
#elif defined(USE_ST7796)
|
||||||
|
#include <ST7796Spi.h>
|
||||||
#else
|
#else
|
||||||
// the SH1106/SSD1306 variant is auto-detected
|
// the SH1106/SSD1306 variant is auto-detected
|
||||||
#include <AutoOLEDWire.h>
|
#include <AutoOLEDWire.h>
|
||||||
@@ -249,6 +251,8 @@ class Screen : public concurrency::OSThread
|
|||||||
|
|
||||||
bool isOverlayBannerShowing();
|
bool isOverlayBannerShowing();
|
||||||
|
|
||||||
|
bool isScreenOn() { return screenOn; }
|
||||||
|
|
||||||
// Stores the last 4 of our hardware ID, to make finding the device for pairing easier
|
// Stores the last 4 of our hardware ID, to make finding the device for pairing easier
|
||||||
// FIXME: Needs refactoring and getMacAddr needs to be moved to a utility class
|
// FIXME: Needs refactoring and getMacAddr needs to be moved to a utility class
|
||||||
char ourId[5];
|
char ourId[5];
|
||||||
@@ -259,15 +263,7 @@ class Screen : public concurrency::OSThread
|
|||||||
void setup();
|
void setup();
|
||||||
|
|
||||||
/// Turns the screen on/off. Optionally, pass a custom screensaver frame for E-Ink
|
/// Turns the screen on/off. Optionally, pass a custom screensaver frame for E-Ink
|
||||||
void setOn(bool on, FrameCallback einkScreensaver = NULL)
|
void setOn(bool on, FrameCallback einkScreensaver = NULL);
|
||||||
{
|
|
||||||
if (!on)
|
|
||||||
// We handle off commands immediately, because they might be called because the CPU is shutting down
|
|
||||||
handleSetOn(false, einkScreensaver);
|
|
||||||
else
|
|
||||||
enqueueCmd(ScreenCmd{.cmd = Cmd::SET_ON});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare the display for the unit going to the lowest power mode possible. Most screens will just
|
* 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
|
* poweroff, but eink screens will show a "I'm sleeping" graphic, possibly with a QR code
|
||||||
@@ -315,6 +311,8 @@ class Screen : public concurrency::OSThread
|
|||||||
|
|
||||||
void showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback);
|
void showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback);
|
||||||
void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function<void(uint32_t)> bannerCallback);
|
void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function<void(uint32_t)> bannerCallback);
|
||||||
|
void showTextInput(const char *header, const char *initialText, uint32_t durationMs,
|
||||||
|
std::function<void(const std::string &)> textCallback);
|
||||||
|
|
||||||
void requestMenu(graphics::menuHandler::screenMenus menuToShow)
|
void requestMenu(graphics::menuHandler::screenMenus menuToShow)
|
||||||
{
|
{
|
||||||
@@ -593,7 +591,11 @@ class Screen : public concurrency::OSThread
|
|||||||
void setSSLFrames();
|
void setSSLFrames();
|
||||||
|
|
||||||
// Dismiss the currently focussed frame, if possible (e.g. text message, waypoint)
|
// Dismiss the currently focussed frame, if possible (e.g. text message, waypoint)
|
||||||
void dismissCurrentFrame();
|
void hideCurrentFrame();
|
||||||
|
|
||||||
|
// Menu-driven Show / Hide Toggle
|
||||||
|
void toggleFrameVisibility(const std::string &frameName);
|
||||||
|
bool isFrameHidden(const std::string &frameName) const;
|
||||||
|
|
||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
/// Draw an image to remain on E-Ink display after screen off
|
/// Draw an image to remain on E-Ink display after screen off
|
||||||
@@ -655,7 +657,7 @@ class Screen : public concurrency::OSThread
|
|||||||
uint8_t settings = 255;
|
uint8_t settings = 255;
|
||||||
uint8_t wifi = 255;
|
uint8_t wifi = 255;
|
||||||
uint8_t deviceFocused = 255;
|
uint8_t deviceFocused = 255;
|
||||||
uint8_t memory = 255;
|
uint8_t system = 255;
|
||||||
uint8_t gps = 255;
|
uint8_t gps = 255;
|
||||||
uint8_t home = 255;
|
uint8_t home = 255;
|
||||||
uint8_t textMessage = 255;
|
uint8_t textMessage = 255;
|
||||||
@@ -665,6 +667,7 @@ class Screen : public concurrency::OSThread
|
|||||||
uint8_t nodelist_distance = 255;
|
uint8_t nodelist_distance = 255;
|
||||||
uint8_t nodelist_bearings = 255;
|
uint8_t nodelist_bearings = 255;
|
||||||
uint8_t clock = 255;
|
uint8_t clock = 255;
|
||||||
|
uint8_t chirpy = 255;
|
||||||
uint8_t firstFavorite = 255;
|
uint8_t firstFavorite = 255;
|
||||||
uint8_t lastFavorite = 255;
|
uint8_t lastFavorite = 255;
|
||||||
uint8_t lora = 255;
|
uint8_t lora = 255;
|
||||||
@@ -673,12 +676,29 @@ class Screen : public concurrency::OSThread
|
|||||||
uint8_t frameCount = 0;
|
uint8_t frameCount = 0;
|
||||||
} framesetInfo;
|
} framesetInfo;
|
||||||
|
|
||||||
struct DismissedFrames {
|
struct hiddenFrames {
|
||||||
bool textMessage = false;
|
bool textMessage = false;
|
||||||
bool waypoint = false;
|
bool waypoint = false;
|
||||||
bool wifi = false;
|
bool wifi = false;
|
||||||
bool memory = false;
|
bool system = false;
|
||||||
} dismissedFrames;
|
bool home = false;
|
||||||
|
bool clock = false;
|
||||||
|
#ifndef USE_EINK
|
||||||
|
bool nodelist = false;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_EINK
|
||||||
|
bool nodelist_lastheard = false;
|
||||||
|
bool nodelist_hopsignal = false;
|
||||||
|
bool nodelist_distance = false;
|
||||||
|
#endif
|
||||||
|
#if HAS_GPS
|
||||||
|
bool nodelist_bearings = false;
|
||||||
|
bool gps = false;
|
||||||
|
#endif
|
||||||
|
bool lora = false;
|
||||||
|
bool show_favorites = false;
|
||||||
|
bool chirpy = true;
|
||||||
|
} hiddenFrames;
|
||||||
|
|
||||||
/// Try to start drawing ASAP
|
/// Try to start drawing ASAP
|
||||||
void setFastFramerate();
|
void setFastFramerate();
|
||||||
|
|||||||
@@ -73,7 +73,8 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)) && \
|
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \
|
||||||
|
defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796)) && \
|
||||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||||
// The screen is bigger so use bigger fonts
|
// The screen is bigger so use bigger fonts
|
||||||
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
|
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
#include "graphics/SharedUIDisplay.h"
|
#include "configuration.h"
|
||||||
|
#if HAS_SCREEN
|
||||||
|
#include "MeshService.h"
|
||||||
#include "RTC.h"
|
#include "RTC.h"
|
||||||
|
#include "draw/NodeListRenderer.h"
|
||||||
#include "graphics/ScreenFonts.h"
|
#include "graphics/ScreenFonts.h"
|
||||||
|
#include "graphics/SharedUIDisplay.h"
|
||||||
|
#include "graphics/draw/UIRenderer.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "meshtastic/config.pb.h"
|
#include "meshtastic/config.pb.h"
|
||||||
#include "power.h"
|
#include "power.h"
|
||||||
@@ -12,12 +17,17 @@ namespace graphics
|
|||||||
|
|
||||||
void determineResolution(int16_t screenheight, int16_t screenwidth)
|
void determineResolution(int16_t screenheight, int16_t screenwidth)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
#ifdef FORCE_LOW_RES
|
||||||
|
isHighResolution = false;
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (screenwidth > 128) {
|
if (screenwidth > 128) {
|
||||||
isHighResolution = true;
|
isHighResolution = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special case for Heltec Wireless Tracker v1.1
|
if (screenwidth > 128 && screenheight <= 64) {
|
||||||
if (screenwidth == 160 && screenheight == 80) {
|
|
||||||
isHighResolution = false;
|
isHighResolution = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,7 +63,7 @@ void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w,
|
|||||||
// *************************
|
// *************************
|
||||||
// * Common Header Drawing *
|
// * Common Header Drawing *
|
||||||
// *************************
|
// *************************
|
||||||
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool battery_only)
|
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool force_no_invert, bool show_date)
|
||||||
{
|
{
|
||||||
constexpr int HEADER_OFFSET_Y = 1;
|
constexpr int HEADER_OFFSET_Y = 1;
|
||||||
y += HEADER_OFFSET_Y;
|
y += HEADER_OFFSET_Y;
|
||||||
@@ -69,7 +79,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
const int screenW = display->getWidth();
|
const int screenW = display->getWidth();
|
||||||
const int screenH = display->getHeight();
|
const int screenH = display->getHeight();
|
||||||
|
|
||||||
if (!battery_only) {
|
if (!force_no_invert) {
|
||||||
// === Inverted Header Background ===
|
// === Inverted Header Background ===
|
||||||
if (isInverted) {
|
if (isInverted) {
|
||||||
display->setColor(BLACK);
|
display->setColor(BLACK);
|
||||||
@@ -187,13 +197,28 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
int timeStrWidth = display->getStringWidth("12:34"); // Default alignment
|
int timeStrWidth = display->getStringWidth("12:34"); // Default alignment
|
||||||
int timeX = screenW - xOffset - timeStrWidth + 4;
|
int timeX = screenW - xOffset - timeStrWidth + 4;
|
||||||
|
|
||||||
if (rtc_sec > 0 && !battery_only) {
|
if (rtc_sec > 0) {
|
||||||
// === Build Time String ===
|
// === Build Time String ===
|
||||||
long hms = (rtc_sec % SEC_PER_DAY + SEC_PER_DAY) % SEC_PER_DAY;
|
long hms = (rtc_sec % SEC_PER_DAY + SEC_PER_DAY) % SEC_PER_DAY;
|
||||||
int hour = hms / SEC_PER_HOUR;
|
int hour = hms / SEC_PER_HOUR;
|
||||||
int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
||||||
snprintf(timeStr, sizeof(timeStr), "%d:%02d", hour, minute);
|
snprintf(timeStr, sizeof(timeStr), "%d:%02d", hour, minute);
|
||||||
|
|
||||||
|
// === Build Date String ===
|
||||||
|
char datetimeStr[25];
|
||||||
|
UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, false);
|
||||||
|
char dateLine[40];
|
||||||
|
|
||||||
|
if (isHighResolution) {
|
||||||
|
snprintf(dateLine, sizeof(dateLine), "%s", datetimeStr);
|
||||||
|
} else {
|
||||||
|
if (hasUnreadMessage) {
|
||||||
|
snprintf(dateLine, sizeof(dateLine), "%s", &datetimeStr[5]);
|
||||||
|
} else {
|
||||||
|
snprintf(dateLine, sizeof(dateLine), "%s", &datetimeStr[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (config.display.use_12h_clock) {
|
if (config.display.use_12h_clock) {
|
||||||
bool isPM = hour >= 12;
|
bool isPM = hour >= 12;
|
||||||
hour %= 12;
|
hour %= 12;
|
||||||
@@ -202,7 +227,11 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
snprintf(timeStr, sizeof(timeStr), "%d:%02d%s", hour, minute, isPM ? "p" : "a");
|
snprintf(timeStr, sizeof(timeStr), "%d:%02d%s", hour, minute, isPM ? "p" : "a");
|
||||||
}
|
}
|
||||||
|
|
||||||
timeStrWidth = display->getStringWidth(timeStr);
|
if (show_date) {
|
||||||
|
timeStrWidth = display->getStringWidth(dateLine);
|
||||||
|
} else {
|
||||||
|
timeStrWidth = display->getStringWidth(timeStr);
|
||||||
|
}
|
||||||
timeX = screenW - xOffset - timeStrWidth + 3;
|
timeX = screenW - xOffset - timeStrWidth + 3;
|
||||||
|
|
||||||
// === Show Mail or Mute Icon to the Left of Time ===
|
// === Show Mail or Mute Icon to the Left of Time ===
|
||||||
@@ -229,7 +258,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
int iconW = 16, iconH = 12;
|
int iconW = 16, iconH = 12;
|
||||||
int iconX = iconRightEdge - iconW;
|
int iconX = iconRightEdge - iconW;
|
||||||
int iconY = textY + (FONT_HEIGHT_SMALL - iconH) / 2 - 1;
|
int iconY = textY + (FONT_HEIGHT_SMALL - iconH) / 2 - 1;
|
||||||
if (isInverted) {
|
if (isInverted && !force_no_invert) {
|
||||||
display->setColor(WHITE);
|
display->setColor(WHITE);
|
||||||
display->fillRect(iconX - 1, iconY - 1, iconW + 3, iconH + 2);
|
display->fillRect(iconX - 1, iconY - 1, iconW + 3, iconH + 2);
|
||||||
display->setColor(BLACK);
|
display->setColor(BLACK);
|
||||||
@@ -244,7 +273,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
} else {
|
} else {
|
||||||
int iconX = iconRightEdge - (mail_width - 2);
|
int iconX = iconRightEdge - (mail_width - 2);
|
||||||
int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
|
int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
|
||||||
if (isInverted) {
|
if (isInverted && !force_no_invert) {
|
||||||
display->setColor(WHITE);
|
display->setColor(WHITE);
|
||||||
display->fillRect(iconX - 1, iconY - 1, mail_width + 2, mail_height + 2);
|
display->fillRect(iconX - 1, iconY - 1, mail_width + 2, mail_height + 2);
|
||||||
display->setColor(BLACK);
|
display->setColor(BLACK);
|
||||||
@@ -260,7 +289,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
int iconX = iconRightEdge - mute_symbol_big_width;
|
int iconX = iconRightEdge - mute_symbol_big_width;
|
||||||
int iconY = textY + (FONT_HEIGHT_SMALL - mute_symbol_big_height) / 2;
|
int iconY = textY + (FONT_HEIGHT_SMALL - mute_symbol_big_height) / 2;
|
||||||
|
|
||||||
if (isInverted) {
|
if (isInverted && !force_no_invert) {
|
||||||
display->setColor(WHITE);
|
display->setColor(WHITE);
|
||||||
display->fillRect(iconX - 1, iconY - 1, mute_symbol_big_width + 2, mute_symbol_big_height + 2);
|
display->fillRect(iconX - 1, iconY - 1, mute_symbol_big_width + 2, mute_symbol_big_height + 2);
|
||||||
display->setColor(BLACK);
|
display->setColor(BLACK);
|
||||||
@@ -287,10 +316,17 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Draw Time ===
|
if (show_date) {
|
||||||
display->drawString(timeX, textY, timeStr);
|
// === Draw Date ===
|
||||||
if (isBold)
|
display->drawString(timeX, textY, dateLine);
|
||||||
display->drawString(timeX - 1, textY, timeStr);
|
if (isBold)
|
||||||
|
display->drawString(timeX - 1, textY, dateLine);
|
||||||
|
} else {
|
||||||
|
// === Draw Time ===
|
||||||
|
display->drawString(timeX, textY, timeStr);
|
||||||
|
if (isBold)
|
||||||
|
display->drawString(timeX - 1, textY, timeStr);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// === No Time Available: Mail/Mute Icon Moves to Far Right ===
|
// === No Time Available: Mail/Mute Icon Moves to Far Right ===
|
||||||
@@ -365,6 +401,43 @@ const int *getTextPositions(OLEDDisplay *display)
|
|||||||
return textPositions;
|
return textPositions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// *************************
|
||||||
|
// * Common Footer Drawing *
|
||||||
|
// *************************
|
||||||
|
void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y)
|
||||||
|
{
|
||||||
|
bool drawConnectionState = false;
|
||||||
|
if (service->api_state == service->STATE_BLE || service->api_state == service->STATE_WIFI ||
|
||||||
|
service->api_state == service->STATE_SERIAL || service->api_state == service->STATE_PACKET ||
|
||||||
|
service->api_state == service->STATE_HTTP || service->api_state == service->STATE_ETH) {
|
||||||
|
drawConnectionState = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drawConnectionState) {
|
||||||
|
if (isHighResolution) {
|
||||||
|
const int scale = 2;
|
||||||
|
const int bytesPerRow = (connection_icon_width + 7) / 8;
|
||||||
|
int iconX = 0;
|
||||||
|
int iconY = SCREEN_HEIGHT - (connection_icon_height * 2);
|
||||||
|
|
||||||
|
for (int yy = 0; yy < connection_icon_height; ++yy) {
|
||||||
|
const uint8_t *rowPtr = connection_icon + yy * bytesPerRow;
|
||||||
|
for (int xx = 0; xx < connection_icon_width; ++xx) {
|
||||||
|
const uint8_t byteVal = pgm_read_byte(rowPtr + (xx >> 3));
|
||||||
|
const uint8_t bitMask = 1U << (xx & 7); // XBM is LSB-first
|
||||||
|
if (byteVal & bitMask) {
|
||||||
|
display->fillRect(iconX + xx * scale, iconY + yy * scale, scale, scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
display->drawXbm(0, SCREEN_HEIGHT - connection_icon_height, connection_icon_width, connection_icon_height,
|
||||||
|
connection_icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool isAllowedPunctuation(char c)
|
bool isAllowedPunctuation(char c)
|
||||||
{
|
{
|
||||||
const std::string allowed = ".,!?;:-_()[]{}'\"@#$/\\&+=%~^ ";
|
const std::string allowed = ".,!?;:-_()[]{}'\"@#$/\\&+=%~^ ";
|
||||||
@@ -392,3 +465,4 @@ std::string sanitizeString(const std::string &input)
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace graphics
|
} // namespace graphics
|
||||||
|
#endif
|
||||||
@@ -49,7 +49,11 @@ void determineResolution(int16_t screenheight, int16_t screenwidth);
|
|||||||
void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w, int16_t h, int16_t r);
|
void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w, int16_t h, int16_t r);
|
||||||
|
|
||||||
// Shared battery/time/mail header
|
// Shared battery/time/mail header
|
||||||
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool battery_only = false);
|
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool force_no_invert = false,
|
||||||
|
bool show_date = false);
|
||||||
|
|
||||||
|
// Shared battery/time/mail header
|
||||||
|
void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y);
|
||||||
|
|
||||||
const int *getTextPositions(OLEDDisplay *display);
|
const int *getTextPositions(OLEDDisplay *display);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
#if USE_TFTDISPLAY
|
||||||
|
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
#include "platform/portduino/PortduinoGlue.h"
|
#include "platform/portduino/PortduinoGlue.h"
|
||||||
@@ -123,6 +124,11 @@ static void rak14014_tpIntHandle(void)
|
|||||||
_rak14014_touch_int = true;
|
_rak14014_touch_int = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#elif defined(HACKADAY_COMMUNICATOR)
|
||||||
|
#include <Arduino_GFX_Library.h>
|
||||||
|
Arduino_DataBus *bus = nullptr;
|
||||||
|
Arduino_GFX *tft = nullptr;
|
||||||
|
|
||||||
#elif defined(ST72xx_DE)
|
#elif defined(ST72xx_DE)
|
||||||
#include <LovyanGFX.hpp>
|
#include <LovyanGFX.hpp>
|
||||||
#include <TCA9534.h>
|
#include <TCA9534.h>
|
||||||
@@ -422,7 +428,57 @@ static LGFX *tft = nullptr;
|
|||||||
|
|
||||||
#elif defined(ST7789_CS)
|
#elif defined(ST7789_CS)
|
||||||
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
|
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
|
||||||
|
#ifdef HELTEC_V4_TFT
|
||||||
|
#include "chsc6x.h"
|
||||||
|
#include "lgfx/v1/Touch.hpp"
|
||||||
|
namespace lgfx
|
||||||
|
{
|
||||||
|
inline namespace v1
|
||||||
|
{
|
||||||
|
class TOUCH_CHSC6X : public ITouch
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TOUCH_CHSC6X(void)
|
||||||
|
{
|
||||||
|
_cfg.i2c_addr = TOUCH_SLAVE_ADDRESS;
|
||||||
|
_cfg.x_min = 0;
|
||||||
|
_cfg.x_max = 240;
|
||||||
|
_cfg.y_min = 0;
|
||||||
|
_cfg.y_max = 320;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool init(void) override
|
||||||
|
{
|
||||||
|
if (chsc6xTouch == nullptr) {
|
||||||
|
chsc6xTouch = new chsc6x(&Wire1, TOUCH_SDA_PIN, TOUCH_SCL_PIN, TOUCH_INT_PIN, TOUCH_RST_PIN);
|
||||||
|
}
|
||||||
|
chsc6xTouch->chsc6x_init();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override
|
||||||
|
{
|
||||||
|
uint16_t raw_x, raw_y;
|
||||||
|
if (chsc6xTouch->chsc6x_read_touch_info(&raw_x, &raw_y) == 0) {
|
||||||
|
tp[0].x = 320 - 1 - raw_y;
|
||||||
|
tp[0].y = 240 - 1 - raw_x;
|
||||||
|
tp[0].size = 1;
|
||||||
|
tp[0].id = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
tp[0].size = 0;
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void wakeup(void) override{};
|
||||||
|
void sleep(void) override{};
|
||||||
|
|
||||||
|
private:
|
||||||
|
chsc6x *chsc6xTouch = nullptr;
|
||||||
|
};
|
||||||
|
} // namespace v1
|
||||||
|
} // namespace lgfx
|
||||||
|
#endif
|
||||||
class LGFX : public lgfx::LGFX_Device
|
class LGFX : public lgfx::LGFX_Device
|
||||||
{
|
{
|
||||||
lgfx::Panel_ST7789 _panel_instance;
|
lgfx::Panel_ST7789 _panel_instance;
|
||||||
@@ -431,6 +487,8 @@ class LGFX : public lgfx::LGFX_Device
|
|||||||
#if HAS_TOUCHSCREEN
|
#if HAS_TOUCHSCREEN
|
||||||
#if defined(T_WATCH_S3) || defined(ELECROW)
|
#if defined(T_WATCH_S3) || defined(ELECROW)
|
||||||
lgfx::Touch_FT5x06 _touch_instance;
|
lgfx::Touch_FT5x06 _touch_instance;
|
||||||
|
#elif defined(HELTEC_V4_TFT)
|
||||||
|
lgfx::TOUCH_CHSC6X _touch_instance;
|
||||||
#else
|
#else
|
||||||
lgfx::Touch_GT911 _touch_instance;
|
lgfx::Touch_GT911 _touch_instance;
|
||||||
#endif
|
#endif
|
||||||
@@ -464,9 +522,9 @@ class LGFX : public lgfx::LGFX_Device
|
|||||||
{ // Set the display panel control.
|
{ // Set the display panel control.
|
||||||
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
|
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
|
||||||
|
|
||||||
cfg.pin_cs = ST7789_CS; // Pin number where CS is connected (-1 = disable)
|
cfg.pin_cs = ST7789_CS; // Pin number where CS is connected (-1 = disable)
|
||||||
cfg.pin_rst = -1; // Pin number where RST is connected (-1 = disable)
|
cfg.pin_rst = ST7789_RESET; // Pin number where RST is connected (-1 = disable)
|
||||||
cfg.pin_busy = -1; // Pin number where BUSY is connected (-1 = disable)
|
cfg.pin_busy = ST7789_BUSY; // Pin number where BUSY is connected (-1 = disable)
|
||||||
|
|
||||||
// The following setting values are general initial values for each panel, so please comment out any
|
// The following setting values are general initial values for each panel, so please comment out any
|
||||||
// unknown items and try them.
|
// unknown items and try them.
|
||||||
@@ -751,10 +809,8 @@ static LGFX *tft = nullptr;
|
|||||||
|
|
||||||
static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
|
static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
|
||||||
#elif ARCH_PORTDUINO
|
#elif ARCH_PORTDUINO
|
||||||
|
#include "Panel_sdl.hpp"
|
||||||
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
|
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
|
||||||
#if defined(LGFX_SDL)
|
|
||||||
#include <lgfx/v1/platforms/sdl/Panel_sdl.hpp>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class LGFX : public lgfx::LGFX_Device
|
class LGFX : public lgfx::LGFX_Device
|
||||||
{
|
{
|
||||||
@@ -767,26 +823,26 @@ class LGFX : public lgfx::LGFX_Device
|
|||||||
|
|
||||||
LGFX(void)
|
LGFX(void)
|
||||||
{
|
{
|
||||||
if (settingsMap[displayPanel] == st7789)
|
if (portduino_config.displayPanel == st7789)
|
||||||
_panel_instance = new lgfx::Panel_ST7789;
|
_panel_instance = new lgfx::Panel_ST7789;
|
||||||
else if (settingsMap[displayPanel] == st7735)
|
else if (portduino_config.displayPanel == st7735)
|
||||||
_panel_instance = new lgfx::Panel_ST7735;
|
_panel_instance = new lgfx::Panel_ST7735;
|
||||||
else if (settingsMap[displayPanel] == st7735s)
|
else if (portduino_config.displayPanel == st7735s)
|
||||||
_panel_instance = new lgfx::Panel_ST7735S;
|
_panel_instance = new lgfx::Panel_ST7735S;
|
||||||
else if (settingsMap[displayPanel] == st7796)
|
else if (portduino_config.displayPanel == st7796)
|
||||||
_panel_instance = new lgfx::Panel_ST7796;
|
_panel_instance = new lgfx::Panel_ST7796;
|
||||||
else if (settingsMap[displayPanel] == ili9341)
|
else if (portduino_config.displayPanel == ili9341)
|
||||||
_panel_instance = new lgfx::Panel_ILI9341;
|
_panel_instance = new lgfx::Panel_ILI9341;
|
||||||
else if (settingsMap[displayPanel] == ili9342)
|
else if (portduino_config.displayPanel == ili9342)
|
||||||
_panel_instance = new lgfx::Panel_ILI9342;
|
_panel_instance = new lgfx::Panel_ILI9342;
|
||||||
else if (settingsMap[displayPanel] == ili9488)
|
else if (portduino_config.displayPanel == ili9488)
|
||||||
_panel_instance = new lgfx::Panel_ILI9488;
|
_panel_instance = new lgfx::Panel_ILI9488;
|
||||||
else if (settingsMap[displayPanel] == hx8357d)
|
else if (portduino_config.displayPanel == hx8357d)
|
||||||
_panel_instance = new lgfx::Panel_HX8357D;
|
_panel_instance = new lgfx::Panel_HX8357D;
|
||||||
#if defined(LGFX_SDL)
|
#if defined(SDL_h_)
|
||||||
else if (settingsMap[displayPanel] == x11) {
|
|
||||||
|
else if (portduino_config.displayPanel == x11)
|
||||||
_panel_instance = new lgfx::Panel_sdl;
|
_panel_instance = new lgfx::Panel_sdl;
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
else {
|
else {
|
||||||
_panel_instance = new lgfx::Panel_NULL;
|
_panel_instance = new lgfx::Panel_NULL;
|
||||||
@@ -795,61 +851,62 @@ class LGFX : public lgfx::LGFX_Device
|
|||||||
|
|
||||||
auto buscfg = _bus_instance.config();
|
auto buscfg = _bus_instance.config();
|
||||||
buscfg.spi_mode = 0;
|
buscfg.spi_mode = 0;
|
||||||
buscfg.spi_host = settingsMap[displayspidev];
|
buscfg.spi_host = portduino_config.display_spi_dev_int;
|
||||||
|
|
||||||
buscfg.pin_dc = settingsMap[displayDC]; // Set SPI DC pin number (-1 = disable)
|
buscfg.pin_dc = portduino_config.displayDC.pin; // Set SPI DC pin number (-1 = disable)
|
||||||
|
|
||||||
_bus_instance.config(buscfg); // applies the set value to the bus.
|
_bus_instance.config(buscfg); // applies the set value to the bus.
|
||||||
_panel_instance->setBus(&_bus_instance); // set the bus on the panel.
|
if (portduino_config.displayPanel != x11)
|
||||||
|
_panel_instance->setBus(&_bus_instance); // set the bus on the panel.
|
||||||
|
|
||||||
auto cfg = _panel_instance->config(); // Gets a structure for display panel settings.
|
auto cfg = _panel_instance->config(); // Gets a structure for display panel settings.
|
||||||
LOG_DEBUG("Width: %d, Height: %d", settingsMap[displayWidth], settingsMap[displayHeight]);
|
LOG_DEBUG("Width: %d, Height: %d", portduino_config.displayWidth, portduino_config.displayHeight);
|
||||||
cfg.pin_cs = settingsMap[displayCS]; // Pin number where CS is connected (-1 = disable)
|
cfg.pin_cs = portduino_config.displayCS.pin; // Pin number where CS is connected (-1 = disable)
|
||||||
cfg.pin_rst = settingsMap[displayReset];
|
cfg.pin_rst = portduino_config.displayReset.pin;
|
||||||
if (settingsMap[displayRotate]) {
|
if (portduino_config.displayRotate) {
|
||||||
cfg.panel_width = settingsMap[displayHeight]; // actual displayable width
|
cfg.panel_width = portduino_config.displayHeight; // actual displayable width
|
||||||
cfg.panel_height = settingsMap[displayWidth]; // actual displayable height
|
cfg.panel_height = portduino_config.displayWidth; // actual displayable height
|
||||||
} else {
|
} else {
|
||||||
cfg.panel_width = settingsMap[displayWidth]; // actual displayable width
|
cfg.panel_width = portduino_config.displayWidth; // actual displayable width
|
||||||
cfg.panel_height = settingsMap[displayHeight]; // actual displayable height
|
cfg.panel_height = portduino_config.displayHeight; // actual displayable height
|
||||||
}
|
}
|
||||||
cfg.offset_x = settingsMap[displayOffsetX]; // Panel offset amount in X direction
|
cfg.offset_x = portduino_config.displayOffsetX; // Panel offset amount in X direction
|
||||||
cfg.offset_y = settingsMap[displayOffsetY]; // Panel offset amount in Y direction
|
cfg.offset_y = portduino_config.displayOffsetY; // Panel offset amount in Y direction
|
||||||
cfg.offset_rotation = settingsMap[displayOffsetRotate]; // Rotation direction value offset 0~7 (4~7 is mirrored)
|
cfg.offset_rotation = portduino_config.displayOffsetRotate; // Rotation direction value offset 0~7 (4~7 is mirrored)
|
||||||
cfg.invert = settingsMap[displayInvert]; // Set to true if the light/darkness of the panel is reversed
|
cfg.invert = portduino_config.displayInvert; // Set to true if the light/darkness of the panel is reversed
|
||||||
|
|
||||||
_panel_instance->config(cfg);
|
_panel_instance->config(cfg);
|
||||||
|
|
||||||
// Configure settings for touch control.
|
// Configure settings for touch control.
|
||||||
if (settingsMap[touchscreenModule]) {
|
if (portduino_config.touchscreenModule) {
|
||||||
if (settingsMap[touchscreenModule] == xpt2046) {
|
if (portduino_config.touchscreenModule == xpt2046) {
|
||||||
_touch_instance = new lgfx::Touch_XPT2046;
|
_touch_instance = new lgfx::Touch_XPT2046;
|
||||||
} else if (settingsMap[touchscreenModule] == stmpe610) {
|
} else if (portduino_config.touchscreenModule == stmpe610) {
|
||||||
_touch_instance = new lgfx::Touch_STMPE610;
|
_touch_instance = new lgfx::Touch_STMPE610;
|
||||||
} else if (settingsMap[touchscreenModule] == ft5x06) {
|
} else if (portduino_config.touchscreenModule == ft5x06) {
|
||||||
_touch_instance = new lgfx::Touch_FT5x06;
|
_touch_instance = new lgfx::Touch_FT5x06;
|
||||||
}
|
}
|
||||||
auto touch_cfg = _touch_instance->config();
|
auto touch_cfg = _touch_instance->config();
|
||||||
|
|
||||||
touch_cfg.pin_cs = settingsMap[touchscreenCS];
|
touch_cfg.pin_cs = portduino_config.touchscreenCS.pin;
|
||||||
touch_cfg.x_min = 0;
|
touch_cfg.x_min = 0;
|
||||||
touch_cfg.x_max = settingsMap[displayHeight] - 1;
|
touch_cfg.x_max = portduino_config.displayHeight - 1;
|
||||||
touch_cfg.y_min = 0;
|
touch_cfg.y_min = 0;
|
||||||
touch_cfg.y_max = settingsMap[displayWidth] - 1;
|
touch_cfg.y_max = portduino_config.displayWidth - 1;
|
||||||
touch_cfg.pin_int = settingsMap[touchscreenIRQ];
|
touch_cfg.pin_int = portduino_config.touchscreenIRQ.pin;
|
||||||
touch_cfg.bus_shared = true;
|
touch_cfg.bus_shared = true;
|
||||||
touch_cfg.offset_rotation = settingsMap[touchscreenRotate];
|
touch_cfg.offset_rotation = portduino_config.touchscreenRotate;
|
||||||
if (settingsMap[touchscreenI2CAddr] != -1) {
|
if (portduino_config.touchscreenI2CAddr != -1) {
|
||||||
touch_cfg.i2c_addr = settingsMap[touchscreenI2CAddr];
|
touch_cfg.i2c_addr = portduino_config.touchscreenI2CAddr;
|
||||||
} else {
|
} else {
|
||||||
touch_cfg.spi_host = settingsMap[touchscreenspidev];
|
touch_cfg.spi_host = portduino_config.touchscreen_spi_dev_int;
|
||||||
}
|
}
|
||||||
|
|
||||||
_touch_instance->config(touch_cfg);
|
_touch_instance->config(touch_cfg);
|
||||||
_panel_instance->setTouch(_touch_instance);
|
_panel_instance->setTouch(_touch_instance);
|
||||||
}
|
}
|
||||||
#if defined(LGFX_SDL)
|
#if defined(SDL_h_)
|
||||||
if (settingsMap[displayPanel] == x11) {
|
if (portduino_config.displayPanel == x11) {
|
||||||
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)_panel_instance;
|
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)_panel_instance;
|
||||||
sdl_panel_->setup();
|
sdl_panel_->setup();
|
||||||
sdl_panel_->addKeyCodeMapping(SDLK_RETURN, SDL_SCANCODE_KP_ENTER);
|
sdl_panel_->addKeyCodeMapping(SDLK_RETURN, SDL_SCANCODE_KP_ENTER);
|
||||||
@@ -1082,9 +1139,6 @@ static LGFX *tft = nullptr;
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ST7796_CS) || defined(ILI9341_DRIVER) || \
|
|
||||||
defined(ILI9342_DRIVER) || defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST72xx_DE) || \
|
|
||||||
(ARCH_PORTDUINO && HAS_SCREEN != 0)
|
|
||||||
#include "SPILock.h"
|
#include "SPILock.h"
|
||||||
#include "TFTDisplay.h"
|
#include "TFTDisplay.h"
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
@@ -1115,10 +1169,10 @@ TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY g
|
|||||||
backlightEnable = p;
|
backlightEnable = p;
|
||||||
|
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
if (settingsMap[displayRotate]) {
|
if (portduino_config.displayRotate) {
|
||||||
setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayWidth], settingsMap[configNames::displayHeight]);
|
setGeometry(GEOMETRY_RAWMODE, portduino_config.displayWidth, portduino_config.displayWidth);
|
||||||
} else {
|
} else {
|
||||||
setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayHeight], settingsMap[configNames::displayWidth]);
|
setGeometry(GEOMETRY_RAWMODE, portduino_config.displayHeight, portduino_config.displayHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
#elif defined(SCREEN_ROTATE)
|
#elif defined(SCREEN_ROTATE)
|
||||||
@@ -1220,12 +1274,15 @@ void TFTDisplay::display(bool fromBlank)
|
|||||||
x_LastPixelUpdate = x;
|
x_LastPixelUpdate = x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if defined(HACKADAY_COMMUNICATOR)
|
||||||
|
tft->draw16bitBeRGBBitmap(x_FirstPixelUpdate, y, &linePixelBuffer[x_FirstPixelUpdate],
|
||||||
|
(x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1);
|
||||||
|
#else
|
||||||
// Step 4: Send the changed pixels on this line to the screen as a single block transfer.
|
// Step 4: Send the changed pixels on this line to the screen as a single block transfer.
|
||||||
// This function accepts pixel data MSB first so it can dump the memory straight out the SPI port.
|
// This function accepts pixel data MSB first so it can dump the memory straight out the SPI port.
|
||||||
tft->pushRect(x_FirstPixelUpdate, y, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1,
|
tft->pushRect(x_FirstPixelUpdate, y, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1,
|
||||||
&linePixelBuffer[x_FirstPixelUpdate]);
|
&linePixelBuffer[x_FirstPixelUpdate]);
|
||||||
|
#endif
|
||||||
somethingChanged = true;
|
somethingChanged = true;
|
||||||
}
|
}
|
||||||
y++;
|
y++;
|
||||||
@@ -1237,37 +1294,36 @@ void TFTDisplay::display(bool fromBlank)
|
|||||||
|
|
||||||
void TFTDisplay::sdlLoop()
|
void TFTDisplay::sdlLoop()
|
||||||
{
|
{
|
||||||
#if defined(LGFX_SDL)
|
#if defined(SDL_h_)
|
||||||
static int lastPressed = 0;
|
static int lastPressed = 0;
|
||||||
static int shuttingDown = false;
|
static int shuttingDown = false;
|
||||||
if (settingsMap[displayPanel] == x11) {
|
if (portduino_config.displayPanel == x11) {
|
||||||
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)tft->_panel_instance;
|
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)tft->_panel_instance;
|
||||||
if (sdl_panel_->loop() && !shuttingDown) {
|
if (sdl_panel_->loop() && !shuttingDown) {
|
||||||
LOG_WARN("Window Closed!");
|
LOG_WARN("Window Closed!");
|
||||||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
|
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||||
inputBroker->injectInputEvent(&event);
|
inputBroker->injectInputEvent(&event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// debounce
|
// debounce
|
||||||
if (lastPressed != 0 && !lgfx::v1::gpio_in(lastPressed))
|
if (lastPressed != 0 && !sdl_panel_->gpio_in(lastPressed))
|
||||||
return;
|
return;
|
||||||
if (!lgfx::v1::gpio_in(37)) {
|
if (!sdl_panel_->gpio_in(37)) {
|
||||||
lastPressed = 37;
|
lastPressed = 37;
|
||||||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_RIGHT, .kbchar = 0, .touchX = 0, .touchY = 0};
|
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_RIGHT, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||||
inputBroker->injectInputEvent(&event);
|
inputBroker->injectInputEvent(&event);
|
||||||
} else if (!lgfx::v1::gpio_in(36)) {
|
} else if (!sdl_panel_->gpio_in(36)) {
|
||||||
lastPressed = 36;
|
lastPressed = 36;
|
||||||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_UP, .kbchar = 0, .touchX = 0, .touchY = 0};
|
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_UP, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||||
inputBroker->injectInputEvent(&event);
|
inputBroker->injectInputEvent(&event);
|
||||||
} else if (!lgfx::v1::gpio_in(38)) {
|
} else if (!sdl_panel_->gpio_in(38)) {
|
||||||
lastPressed = 38;
|
lastPressed = 38;
|
||||||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_DOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
|
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_DOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||||
inputBroker->injectInputEvent(&event);
|
inputBroker->injectInputEvent(&event);
|
||||||
} else if (!lgfx::v1::gpio_in(39)) {
|
} else if (!sdl_panel_->gpio_in(39)) {
|
||||||
lastPressed = 39;
|
lastPressed = 39;
|
||||||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_LEFT, .kbchar = 0, .touchX = 0, .touchY = 0};
|
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_LEFT, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||||
inputBroker->injectInputEvent(&event);
|
inputBroker->injectInputEvent(&event);
|
||||||
} else if (!lgfx::v1::gpio_in(SDL_SCANCODE_KP_ENTER)) {
|
} else if (!sdl_panel_->gpio_in(SDL_SCANCODE_KP_ENTER)) {
|
||||||
lastPressed = SDL_SCANCODE_KP_ENTER;
|
lastPressed = SDL_SCANCODE_KP_ENTER;
|
||||||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SELECT, .kbchar = 0, .touchX = 0, .touchY = 0};
|
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SELECT, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||||
inputBroker->injectInputEvent(&event);
|
inputBroker->injectInputEvent(&event);
|
||||||
@@ -1288,8 +1344,10 @@ void TFTDisplay::sendCommand(uint8_t com)
|
|||||||
backlightEnable->set(true);
|
backlightEnable->set(true);
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
display(true);
|
display(true);
|
||||||
if (settingsMap[displayBacklight] > 0)
|
if (portduino_config.displayBacklight.pin > 0)
|
||||||
digitalWrite(settingsMap[displayBacklight], TFT_BACKLIGHT_ON);
|
digitalWrite(portduino_config.displayBacklight.pin, TFT_BACKLIGHT_ON);
|
||||||
|
#elif defined(HACKADAY_COMMUNICATOR)
|
||||||
|
tft->displayOn();
|
||||||
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
|
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
|
||||||
tft->wakeup();
|
tft->wakeup();
|
||||||
tft->powerSaveOff();
|
tft->powerSaveOff();
|
||||||
@@ -1302,7 +1360,8 @@ void TFTDisplay::sendCommand(uint8_t com)
|
|||||||
unphone.backlight(true); // using unPhone library
|
unphone.backlight(true); // using unPhone library
|
||||||
#endif
|
#endif
|
||||||
#ifdef RAK14014
|
#ifdef RAK14014
|
||||||
#elif !defined(M5STACK) && !defined(ST7789_CS) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function
|
#elif !defined(M5STACK) && !defined(ST7789_CS) && \
|
||||||
|
!defined(HACKADAY_COMMUNICATOR) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function
|
||||||
tft->setBrightness(172);
|
tft->setBrightness(172);
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
@@ -1312,8 +1371,10 @@ void TFTDisplay::sendCommand(uint8_t com)
|
|||||||
backlightEnable->set(false);
|
backlightEnable->set(false);
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
tft->clear();
|
tft->clear();
|
||||||
if (settingsMap[displayBacklight] > 0)
|
if (portduino_config.displayBacklight.pin > 0)
|
||||||
digitalWrite(settingsMap[displayBacklight], !TFT_BACKLIGHT_ON);
|
digitalWrite(portduino_config.displayBacklight.pin, !TFT_BACKLIGHT_ON);
|
||||||
|
#elif defined(HACKADAY_COMMUNICATOR)
|
||||||
|
tft->displayOff();
|
||||||
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
|
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
|
||||||
tft->sleep();
|
tft->sleep();
|
||||||
tft->powerSaveOn();
|
tft->powerSaveOn();
|
||||||
@@ -1326,7 +1387,7 @@ void TFTDisplay::sendCommand(uint8_t com)
|
|||||||
unphone.backlight(false); // using unPhone library
|
unphone.backlight(false); // using unPhone library
|
||||||
#endif
|
#endif
|
||||||
#ifdef RAK14014
|
#ifdef RAK14014
|
||||||
#elif !defined(M5STACK)
|
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR)
|
||||||
tft->setBrightness(0);
|
tft->setBrightness(0);
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
@@ -1342,7 +1403,7 @@ void TFTDisplay::setDisplayBrightness(uint8_t _brightness)
|
|||||||
{
|
{
|
||||||
#ifdef RAK14014
|
#ifdef RAK14014
|
||||||
// todo
|
// todo
|
||||||
#else
|
#elif !defined(HACKADAY_COMMUNICATOR)
|
||||||
tft->setBrightness(_brightness);
|
tft->setBrightness(_brightness);
|
||||||
LOG_DEBUG("Brightness is set to value: %i ", _brightness);
|
LOG_DEBUG("Brightness is set to value: %i ", _brightness);
|
||||||
#endif
|
#endif
|
||||||
@@ -1360,7 +1421,7 @@ bool TFTDisplay::hasTouch(void)
|
|||||||
{
|
{
|
||||||
#ifdef RAK14014
|
#ifdef RAK14014
|
||||||
return true;
|
return true;
|
||||||
#elif !defined(M5STACK)
|
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR)
|
||||||
return tft->touch() != nullptr;
|
return tft->touch() != nullptr;
|
||||||
#else
|
#else
|
||||||
return false;
|
return false;
|
||||||
@@ -1379,7 +1440,7 @@ bool TFTDisplay::getTouch(int16_t *x, int16_t *y)
|
|||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#elif !defined(M5STACK)
|
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR)
|
||||||
return tft->getTouch(x, y);
|
return tft->getTouch(x, y);
|
||||||
#else
|
#else
|
||||||
return false;
|
return false;
|
||||||
@@ -1398,6 +1459,12 @@ bool TFTDisplay::connect()
|
|||||||
LOG_INFO("Do TFT init");
|
LOG_INFO("Do TFT init");
|
||||||
#ifdef RAK14014
|
#ifdef RAK14014
|
||||||
tft = new TFT_eSPI;
|
tft = new TFT_eSPI;
|
||||||
|
#elif defined(HACKADAY_COMMUNICATOR)
|
||||||
|
bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, 38 /* SCK */, 21 /* MOSI */, GFX_NOT_DEFINED /* MISO */, HSPI /* spi_num */);
|
||||||
|
tft = new Arduino_NV3007(bus, 40, 0 /* rotation */, false /* IPS */, 142 /* width */, 428 /* height */, 12 /* col offset 1 */,
|
||||||
|
0 /* row offset 1 */, 14 /* col offset 2 */, 0 /* row offset 2 */, nv3007_279_init_operations,
|
||||||
|
sizeof(nv3007_279_init_operations));
|
||||||
|
|
||||||
#else
|
#else
|
||||||
tft = new LGFX;
|
tft = new LGFX;
|
||||||
#endif
|
#endif
|
||||||
@@ -1408,8 +1475,15 @@ bool TFTDisplay::connect()
|
|||||||
#ifdef UNPHONE
|
#ifdef UNPHONE
|
||||||
unphone.backlight(true); // using unPhone library
|
unphone.backlight(true); // using unPhone library
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HACKADAY_COMMUNICATOR
|
||||||
|
bool beginStatus = tft->begin();
|
||||||
|
if (beginStatus)
|
||||||
|
LOG_DEBUG("TFT Success!");
|
||||||
|
else
|
||||||
|
LOG_ERROR("TFT Fail!");
|
||||||
|
#else
|
||||||
tft->init();
|
tft->init();
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(M5STACK)
|
#if defined(M5STACK)
|
||||||
tft->setRotation(0);
|
tft->setRotation(0);
|
||||||
@@ -1442,4 +1516,4 @@ bool TFTDisplay::connect()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif // USE_TFTDISPLAY
|
||||||
|
|||||||
@@ -101,3 +101,23 @@ void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength)
|
|||||||
else
|
else
|
||||||
snprintf(timeStr, maxLength, "unknown age");
|
snprintf(timeStr, maxLength, "unknown age");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void getUptimeStr(uint32_t uptimeMillis, const char *prefix, char *uptimeStr, uint8_t maxLength, bool includeSecs)
|
||||||
|
{
|
||||||
|
uint32_t days = uptimeMillis / 86400000;
|
||||||
|
uint32_t hours = (uptimeMillis % 86400000) / 3600000;
|
||||||
|
uint32_t mins = (uptimeMillis % 3600000) / 60000;
|
||||||
|
uint32_t secs = (uptimeMillis % 60000) / 1000;
|
||||||
|
|
||||||
|
if (days) {
|
||||||
|
snprintf(uptimeStr, maxLength, "%s: %ud %uh", prefix, days, hours);
|
||||||
|
} else if (hours) {
|
||||||
|
snprintf(uptimeStr, maxLength, "%s: %uh %um", prefix, hours, mins);
|
||||||
|
} else if (!includeSecs) {
|
||||||
|
snprintf(uptimeStr, maxLength, "%s: %um", prefix, mins);
|
||||||
|
} else if (mins) {
|
||||||
|
snprintf(uptimeStr, maxLength, "%s: %um %us", prefix, mins, secs);
|
||||||
|
} else {
|
||||||
|
snprintf(uptimeStr, maxLength, "%s: %us", prefix, secs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,3 +24,10 @@ bool deltaToTimestamp(uint32_t secondsAgo, uint8_t *hours, uint8_t *minutes, int
|
|||||||
* @param maxLength Maximum length of the resulting string buffer
|
* @param maxLength Maximum length of the resulting string buffer
|
||||||
*/
|
*/
|
||||||
void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength);
|
void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a compact human-readable string that only shows the largest non-zero time components.
|
||||||
|
* For example, 0 days 1 hour 2 minutes will display as "1h 2m" but 1 day 2 hours 3 minutes
|
||||||
|
* will display as "1d 2h".
|
||||||
|
*/
|
||||||
|
void getUptimeStr(uint32_t uptimeMillis, const char *prefix, char *uptimeStr, uint8_t maxLength, bool includeSecs = false);
|
||||||
|
|||||||
743
src/graphics/VirtualKeyboard.cpp
Normal file
743
src/graphics/VirtualKeyboard.cpp
Normal file
@@ -0,0 +1,743 @@
|
|||||||
|
#include "configuration.h"
|
||||||
|
#if HAS_SCREEN
|
||||||
|
#include "VirtualKeyboard.h"
|
||||||
|
#include "graphics/Screen.h"
|
||||||
|
#include "graphics/ScreenFonts.h"
|
||||||
|
#include "graphics/SharedUIDisplay.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace graphics
|
||||||
|
{
|
||||||
|
|
||||||
|
VirtualKeyboard::VirtualKeyboard() : cursorRow(0), cursorCol(0), lastActivityTime(millis())
|
||||||
|
{
|
||||||
|
initializeKeyboard();
|
||||||
|
// Set cursor to H(2, 5)
|
||||||
|
cursorRow = 2;
|
||||||
|
cursorCol = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
VirtualKeyboard::~VirtualKeyboard() {}
|
||||||
|
|
||||||
|
void VirtualKeyboard::initializeKeyboard()
|
||||||
|
{
|
||||||
|
// New 4 row, 11 column keyboard layout:
|
||||||
|
static const char LAYOUT[KEYBOARD_ROWS][KEYBOARD_COLS] = {{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '\b'},
|
||||||
|
{'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '\n'},
|
||||||
|
{'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', ' '},
|
||||||
|
{'z', 'x', 'c', 'v', 'b', 'n', 'm', '.', ',', '?', '\x1b'}};
|
||||||
|
|
||||||
|
// Derive layout dimensions and assert they match the configured keyboard grid
|
||||||
|
constexpr int LAYOUT_ROWS = (int)(sizeof(LAYOUT) / sizeof(LAYOUT[0]));
|
||||||
|
constexpr int LAYOUT_COLS = (int)(sizeof(LAYOUT[0]) / sizeof(LAYOUT[0][0]));
|
||||||
|
static_assert(LAYOUT_ROWS == KEYBOARD_ROWS, "LAYOUT rows must equal KEYBOARD_ROWS");
|
||||||
|
static_assert(LAYOUT_COLS == KEYBOARD_COLS, "LAYOUT cols must equal KEYBOARD_COLS");
|
||||||
|
|
||||||
|
// Initialize all keys to empty first
|
||||||
|
for (int row = 0; row < LAYOUT_ROWS; row++) {
|
||||||
|
for (int col = 0; col < LAYOUT_COLS; col++) {
|
||||||
|
keyboard[row][col] = {0, VK_CHAR, 0, 0, 0, 0};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill keyboard from the 2D layout
|
||||||
|
for (int row = 0; row < LAYOUT_ROWS; row++) {
|
||||||
|
for (int col = 0; col < LAYOUT_COLS; col++) {
|
||||||
|
char ch = LAYOUT[row][col];
|
||||||
|
// No empty slots in the simplified layout
|
||||||
|
|
||||||
|
VirtualKeyType type = VK_CHAR;
|
||||||
|
if (ch == '\b') {
|
||||||
|
type = VK_BACKSPACE;
|
||||||
|
} else if (ch == '\n') {
|
||||||
|
type = VK_ENTER;
|
||||||
|
} else if (ch == '\x1b') { // ESC
|
||||||
|
type = VK_ESC;
|
||||||
|
} else if (ch == ' ') {
|
||||||
|
type = VK_SPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make action keys wider to fit text while keeping the last column aligned
|
||||||
|
uint8_t width = (type == VK_BACKSPACE || type == VK_ENTER || type == VK_SPACE) ? (KEY_WIDTH * 3) : KEY_WIDTH;
|
||||||
|
keyboard[row][col] = {ch, type, (uint8_t)(col * KEY_WIDTH), (uint8_t)(row * KEY_HEIGHT), width, KEY_HEIGHT};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::draw(OLEDDisplay *display, int16_t offsetX, int16_t offsetY)
|
||||||
|
{
|
||||||
|
// Repeat ticking is driven by NotificationRenderer once per frame
|
||||||
|
// Base styles
|
||||||
|
display->setColor(WHITE);
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
|
// Screen geometry
|
||||||
|
const int screenW = display->getWidth();
|
||||||
|
const int screenH = display->getHeight();
|
||||||
|
|
||||||
|
// Decide wide-screen mode: if there is comfortable width, allow taller keys and reserve fixed width for last column labels
|
||||||
|
// Heuristic: if screen width >= 200px (e.g., 240x135), treat as wide
|
||||||
|
const bool isWide = screenW >= 200;
|
||||||
|
|
||||||
|
// Determine last-column label max width
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
const int wENTER = display->getStringWidth("ENTER");
|
||||||
|
int lastColLabelW = wENTER; // ENTER is usually the widest
|
||||||
|
// Smaller padding on very small screens to avoid excessive whitespace
|
||||||
|
const int lastColPad = (screenW <= 128 ? 2 : 6);
|
||||||
|
const int reservedLastColW = lastColLabelW + lastColPad; // reserved width for last column keys
|
||||||
|
|
||||||
|
// Always reserve width for the rightmost text column to avoid overlap on small screens
|
||||||
|
int cellW = 0;
|
||||||
|
int leftoverW = 0;
|
||||||
|
{
|
||||||
|
const int leftCols = KEYBOARD_COLS - 1; // 10 input characters
|
||||||
|
int usableW = screenW - reservedLastColW;
|
||||||
|
if (usableW < leftCols) {
|
||||||
|
// Guard: ensure at least 1px per left cell if labels are extremely wide (unlikely)
|
||||||
|
usableW = leftCols;
|
||||||
|
}
|
||||||
|
cellW = usableW / leftCols;
|
||||||
|
leftoverW = usableW - cellW * leftCols; // distribute extra pixels over left columns (left to right)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic key geometry
|
||||||
|
int cellH = KEY_HEIGHT;
|
||||||
|
int keyboardStartY = 0;
|
||||||
|
if (screenH <= 64) {
|
||||||
|
const int headerHeight = headerText.empty() ? 0 : (FONT_HEIGHT_SMALL - 2);
|
||||||
|
const int gapBelowHeader = 0;
|
||||||
|
const int singleLineBoxHeight = FONT_HEIGHT_SMALL;
|
||||||
|
const int gapAboveKeyboard = 0;
|
||||||
|
keyboardStartY = offsetY + headerHeight + gapBelowHeader + singleLineBoxHeight + gapAboveKeyboard;
|
||||||
|
if (keyboardStartY < 0)
|
||||||
|
keyboardStartY = 0;
|
||||||
|
if (keyboardStartY > screenH)
|
||||||
|
keyboardStartY = screenH;
|
||||||
|
int keyboardHeight = screenH - keyboardStartY;
|
||||||
|
cellH = std::max(1, keyboardHeight / KEYBOARD_ROWS);
|
||||||
|
} else if (isWide) {
|
||||||
|
// For wide screens (e.g., T114 240x135), prefer square keys: height equals left-column key width.
|
||||||
|
cellH = std::max((int)KEY_HEIGHT, cellW);
|
||||||
|
|
||||||
|
// Guarantee at least 2 lines of input are visible by reducing cell height minimally if needed.
|
||||||
|
// Replicate the spacing used in drawInputArea(): headerGap=1, box-to-header gap=1, gap above keyboard=1
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
const int headerHeight = headerText.empty() ? 0 : (FONT_HEIGHT_SMALL + 1);
|
||||||
|
const int headerToBoxGap = 1;
|
||||||
|
const int gapAboveKb = 1;
|
||||||
|
const int minBoxHeightForTwoLines = 2 * FONT_HEIGHT_SMALL + 2; // inner 1px top/bottom
|
||||||
|
int maxKeyboardHeight = screenH - (offsetY + headerHeight + headerToBoxGap + minBoxHeightForTwoLines + gapAboveKb);
|
||||||
|
int maxCellHAllowed = maxKeyboardHeight / KEYBOARD_ROWS;
|
||||||
|
if (maxCellHAllowed < (int)KEY_HEIGHT)
|
||||||
|
maxCellHAllowed = KEY_HEIGHT;
|
||||||
|
if (maxCellHAllowed > 0 && cellH > maxCellHAllowed) {
|
||||||
|
cellH = maxCellHAllowed;
|
||||||
|
}
|
||||||
|
// Keyboard placement from bottom for wide screens
|
||||||
|
int keyboardHeight = KEYBOARD_ROWS * cellH;
|
||||||
|
keyboardStartY = screenH - keyboardHeight;
|
||||||
|
if (keyboardStartY < 0)
|
||||||
|
keyboardStartY = 0;
|
||||||
|
} else {
|
||||||
|
// Default (non-wide, non-64px) behavior: use key height heuristic and place at bottom
|
||||||
|
cellH = KEY_HEIGHT;
|
||||||
|
int keyboardHeight = KEYBOARD_ROWS * cellH;
|
||||||
|
keyboardStartY = screenH - keyboardHeight;
|
||||||
|
if (keyboardStartY < 0)
|
||||||
|
keyboardStartY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw input area above keyboard
|
||||||
|
drawInputArea(display, offsetX, offsetY, keyboardStartY);
|
||||||
|
|
||||||
|
// Precompute per-column x and width with leftover distributed over left columns for even spacing
|
||||||
|
int colX[KEYBOARD_COLS];
|
||||||
|
int colW[KEYBOARD_COLS];
|
||||||
|
int runningX = offsetX;
|
||||||
|
for (int col = 0; col < KEYBOARD_COLS - 1; ++col) {
|
||||||
|
int wcol = cellW + (col < leftoverW ? 1 : 0);
|
||||||
|
colX[col] = runningX;
|
||||||
|
colW[col] = wcol;
|
||||||
|
runningX += wcol;
|
||||||
|
}
|
||||||
|
// Last column
|
||||||
|
colX[KEYBOARD_COLS - 1] = runningX;
|
||||||
|
colW[KEYBOARD_COLS - 1] = reservedLastColW;
|
||||||
|
|
||||||
|
// Draw keyboard grid
|
||||||
|
for (int row = 0; row < KEYBOARD_ROWS; row++) {
|
||||||
|
for (int col = 0; col < KEYBOARD_COLS; col++) {
|
||||||
|
const VirtualKey &k = keyboard[row][col];
|
||||||
|
if (k.character != 0 || k.type != VK_CHAR) {
|
||||||
|
const bool isLastCol = (col == KEYBOARD_COLS - 1);
|
||||||
|
int x = colX[col];
|
||||||
|
int w = colW[col];
|
||||||
|
int y = offsetY + keyboardStartY + row * cellH;
|
||||||
|
int h = cellH;
|
||||||
|
bool selected = (row == cursorRow && col == cursorCol);
|
||||||
|
drawKey(display, k, selected, x, y, (uint8_t)w, (uint8_t)h, isLastCol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::drawInputArea(OLEDDisplay *display, int16_t offsetX, int16_t offsetY, int16_t keyboardStartY)
|
||||||
|
{
|
||||||
|
display->setColor(WHITE);
|
||||||
|
|
||||||
|
const int screenWidth = display->getWidth();
|
||||||
|
const int screenHeight = display->getHeight();
|
||||||
|
// Use the standard small font metrics for input box sizing (restore original size)
|
||||||
|
const int inputLineH = FONT_HEIGHT_SMALL;
|
||||||
|
|
||||||
|
// Header uses the standard small (which may be larger on big screens)
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
int headerHeight = 0;
|
||||||
|
if (!headerText.empty()) {
|
||||||
|
// Draw header and reserve exact font height (plus a tighter gap) to maximize input area
|
||||||
|
display->drawString(offsetX + 2, offsetY, headerText.c_str());
|
||||||
|
if (screenHeight <= 64) {
|
||||||
|
headerHeight = FONT_HEIGHT_SMALL - 2; // 11px
|
||||||
|
} else {
|
||||||
|
headerHeight = FONT_HEIGHT_SMALL; // no extra padding baked in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int boxX = offsetX;
|
||||||
|
const int boxWidth = screenWidth;
|
||||||
|
int boxY;
|
||||||
|
int boxHeight;
|
||||||
|
if (screenHeight <= 64) {
|
||||||
|
const int gapBelowHeader = 0;
|
||||||
|
const int fixedBoxHeight = inputLineH;
|
||||||
|
const int gapAboveKeyboard = 0;
|
||||||
|
boxY = offsetY + headerHeight + gapBelowHeader;
|
||||||
|
boxHeight = fixedBoxHeight;
|
||||||
|
if (boxY + boxHeight + gapAboveKeyboard > keyboardStartY) {
|
||||||
|
int over = boxY + boxHeight + gapAboveKeyboard - keyboardStartY;
|
||||||
|
boxHeight = std::max(1, fixedBoxHeight - over);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const int gapBelowHeader = 1;
|
||||||
|
int gapAboveKeyboard = 1;
|
||||||
|
int tmpBoxY = offsetY + headerHeight + gapBelowHeader;
|
||||||
|
const int minBoxHeight = inputLineH + 2;
|
||||||
|
int availableH = keyboardStartY - tmpBoxY - gapAboveKeyboard;
|
||||||
|
if (availableH < minBoxHeight)
|
||||||
|
availableH = minBoxHeight;
|
||||||
|
boxY = tmpBoxY;
|
||||||
|
boxHeight = availableH;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw box border
|
||||||
|
display->drawRect(boxX, boxY, boxWidth, boxHeight);
|
||||||
|
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
|
// Text rendering: multi-line if space allows (>= 2 lines), else single-line with leading ellipsis
|
||||||
|
const int textX = boxX + 2;
|
||||||
|
const int maxTextWidth = boxWidth - 4;
|
||||||
|
const int maxLines = (boxHeight - 2) / inputLineH;
|
||||||
|
if (maxLines >= 2) {
|
||||||
|
// Inner bounds for caret clamping
|
||||||
|
const int innerLeft = boxX + 1;
|
||||||
|
const int innerRight = boxX + boxWidth - 2;
|
||||||
|
const int innerTop = boxY + 1;
|
||||||
|
const int innerBottom = boxY + boxHeight - 2;
|
||||||
|
|
||||||
|
// Wrap text greedily into lines that fit maxTextWidth
|
||||||
|
std::vector<std::string> lines;
|
||||||
|
{
|
||||||
|
std::string remaining = inputText;
|
||||||
|
while (!remaining.empty()) {
|
||||||
|
int bestLen = 0;
|
||||||
|
for (int len = 1; len <= (int)remaining.size(); ++len) {
|
||||||
|
int w = display->getStringWidth(remaining.substr(0, len).c_str());
|
||||||
|
if (w <= maxTextWidth)
|
||||||
|
bestLen = len;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (bestLen == 0) {
|
||||||
|
// At least show one character to make progress
|
||||||
|
bestLen = 1;
|
||||||
|
}
|
||||||
|
lines.emplace_back(remaining.substr(0, bestLen));
|
||||||
|
remaining.erase(0, bestLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool scrolledUp = ((int)lines.size() > maxLines);
|
||||||
|
int caretX = textX;
|
||||||
|
int caretY = innerTop;
|
||||||
|
|
||||||
|
// Leave a small top gap to render '...' without replacing the first line
|
||||||
|
const int topInset = 2;
|
||||||
|
const int lineStep = std::max(1, inputLineH - 1); // slightly tighter than font height
|
||||||
|
int lineY = innerTop + topInset;
|
||||||
|
|
||||||
|
if (scrolledUp) {
|
||||||
|
// Draw three small dots centered horizontally, vertically at the midpoint of the gap
|
||||||
|
// between the inner top and the first line's top baseline. This avoids using a tall glyph.
|
||||||
|
const int firstLineTop = lineY; // baseline top for the first visible line
|
||||||
|
const int gapMidY = innerTop + (firstLineTop - innerTop) / 2 + 1; // shift down 1px as requested
|
||||||
|
const int centerX = boxX + boxWidth / 2;
|
||||||
|
const int dotSpacing = 3; // px between dots
|
||||||
|
const int dotSize = 1; // small square dot
|
||||||
|
display->fillRect(centerX - dotSpacing, gapMidY, dotSize, dotSize);
|
||||||
|
display->fillRect(centerX, gapMidY, dotSize, dotSize);
|
||||||
|
display->fillRect(centerX + dotSpacing, gapMidY, dotSize, dotSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// How many lines fit with our top inset and tighter step
|
||||||
|
const int linesCapacity = std::max(1, (innerBottom - lineY + 1) / lineStep);
|
||||||
|
const int linesToShow = std::min((int)lines.size(), linesCapacity);
|
||||||
|
const int startIndex = scrolledUp ? ((int)lines.size() - linesToShow) : 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < linesToShow; ++i) {
|
||||||
|
const std::string &chunk = lines[startIndex + i];
|
||||||
|
display->drawString(textX, lineY, chunk.c_str());
|
||||||
|
caretX = textX + display->getStringWidth(chunk.c_str());
|
||||||
|
caretY = lineY;
|
||||||
|
lineY += lineStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw caret at end of the last visible line
|
||||||
|
int caretPadY = 2;
|
||||||
|
if (boxHeight >= inputLineH + 4)
|
||||||
|
caretPadY = 3;
|
||||||
|
int cursorTop = caretY + caretPadY;
|
||||||
|
// Use lineStep so caret height matches the row spacing
|
||||||
|
int cursorH = lineStep - caretPadY * 2;
|
||||||
|
if (cursorH < 1)
|
||||||
|
cursorH = 1;
|
||||||
|
// Clamp vertical bounds to stay inside the inner rect
|
||||||
|
if (cursorTop < innerTop)
|
||||||
|
cursorTop = innerTop;
|
||||||
|
if (cursorTop + cursorH - 1 > innerBottom)
|
||||||
|
cursorH = innerBottom - cursorTop + 1;
|
||||||
|
if (cursorH < 1)
|
||||||
|
cursorH = 1;
|
||||||
|
// Only draw if cursor is inside inner bounds
|
||||||
|
if (caretX >= innerLeft && caretX <= innerRight) {
|
||||||
|
display->drawVerticalLine(caretX, cursorTop, cursorH);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::string displayText = inputText;
|
||||||
|
int textW = display->getStringWidth(displayText.c_str());
|
||||||
|
std::string scrolled = displayText;
|
||||||
|
if (textW > maxTextWidth) {
|
||||||
|
// Trim from the left until it fits
|
||||||
|
while (textW > maxTextWidth && !scrolled.empty()) {
|
||||||
|
scrolled.erase(0, 1);
|
||||||
|
textW = display->getStringWidth(scrolled.c_str());
|
||||||
|
}
|
||||||
|
// Add leading ellipsis and ensure it still fits
|
||||||
|
if (scrolled != displayText) {
|
||||||
|
scrolled = "..." + scrolled;
|
||||||
|
textW = display->getStringWidth(scrolled.c_str());
|
||||||
|
// If adding ellipsis causes overflow, trim more after the ellipsis
|
||||||
|
while (textW > maxTextWidth && scrolled.size() > 3) {
|
||||||
|
scrolled.erase(3, 1); // remove chars after the ellipsis
|
||||||
|
textW = display->getStringWidth(scrolled.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Keep textW in sync with what we draw
|
||||||
|
textW = display->getStringWidth(scrolled.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
int textY;
|
||||||
|
if (screenHeight <= 64) {
|
||||||
|
textY = boxY + (boxHeight - inputLineH) / 2;
|
||||||
|
} else {
|
||||||
|
const int innerLeft = boxX + 1;
|
||||||
|
const int innerRight = boxX + boxWidth - 2;
|
||||||
|
const int innerTop = boxY + 1;
|
||||||
|
const int innerBottom = boxY + boxHeight - 2;
|
||||||
|
|
||||||
|
// Center text vertically within inner box for single-line, then clamp so it never overlaps borders
|
||||||
|
int innerH = innerBottom - innerTop + 1;
|
||||||
|
textY = innerTop + std::max(0, (innerH - inputLineH) / 2);
|
||||||
|
// Clamp fully inside the inner rect
|
||||||
|
if (textY < innerTop)
|
||||||
|
textY = innerTop;
|
||||||
|
int maxTop = innerBottom - inputLineH + 1;
|
||||||
|
if (textY > maxTop)
|
||||||
|
textY = maxTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scrolled.empty()) {
|
||||||
|
display->drawString(textX, textY, scrolled.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
int cursorX = textX + textW;
|
||||||
|
if (screenHeight > 64) {
|
||||||
|
const int innerRight = boxX + boxWidth - 2;
|
||||||
|
if (cursorX > innerRight)
|
||||||
|
cursorX = innerRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cursorTop, cursorH;
|
||||||
|
if (screenHeight <= 64) {
|
||||||
|
cursorH = 10;
|
||||||
|
cursorTop = boxY + (boxHeight - cursorH) / 2;
|
||||||
|
} else {
|
||||||
|
const int innerLeft = boxX + 1;
|
||||||
|
const int innerRight = boxX + boxWidth - 2;
|
||||||
|
const int innerTop = boxY + 1;
|
||||||
|
const int innerBottom = boxY + boxHeight - 2;
|
||||||
|
|
||||||
|
cursorTop = boxY + 2;
|
||||||
|
cursorH = boxHeight - 4;
|
||||||
|
if (cursorH < 1)
|
||||||
|
cursorH = 1;
|
||||||
|
if (cursorTop < innerTop)
|
||||||
|
cursorTop = innerTop;
|
||||||
|
if (cursorTop + cursorH - 1 > innerBottom)
|
||||||
|
cursorH = innerBottom - cursorTop + 1;
|
||||||
|
if (cursorH < 1)
|
||||||
|
cursorH = 1;
|
||||||
|
|
||||||
|
if (cursorX < innerLeft || cursorX > innerRight)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
display->drawVerticalLine(cursorX, cursorTop, cursorH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::drawKey(OLEDDisplay *display, const VirtualKey &key, bool selected, int16_t x, int16_t y, uint8_t width,
|
||||||
|
uint8_t height, bool isLastCol)
|
||||||
|
{
|
||||||
|
// Draw key content
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
const int fontH = FONT_HEIGHT_SMALL;
|
||||||
|
// Build label and metrics first
|
||||||
|
std::string keyText;
|
||||||
|
if (key.type == VK_BACKSPACE || key.type == VK_ENTER || key.type == VK_SPACE || key.type == VK_ESC) {
|
||||||
|
// Keep literal text labels for the action keys on the rightmost column
|
||||||
|
keyText = (key.type == VK_BACKSPACE) ? "BACK"
|
||||||
|
: (key.type == VK_ENTER) ? "ENTER"
|
||||||
|
: (key.type == VK_SPACE) ? "SPACE"
|
||||||
|
: (key.type == VK_ESC) ? "ESC"
|
||||||
|
: "";
|
||||||
|
} else {
|
||||||
|
char c = getCharForKey(key, false);
|
||||||
|
if (c >= 'a' && c <= 'z') {
|
||||||
|
c = c - 'a' + 'A';
|
||||||
|
}
|
||||||
|
keyText = (key.character == ' ' || key.character == '_') ? "_" : std::string(1, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
int textWidth = display->getStringWidth(keyText.c_str());
|
||||||
|
// Label alignment
|
||||||
|
// - Rightmost action column: right-align text with a small right padding (~2px) so it hugs screen edge neatly.
|
||||||
|
// - Other keys: center horizontally; use ceil-style rounding to avoid appearing left-biased on odd widths.
|
||||||
|
int textX;
|
||||||
|
if (isLastCol) {
|
||||||
|
const int rightPad = 1;
|
||||||
|
textX = x + width - textWidth - rightPad;
|
||||||
|
if (textX < x)
|
||||||
|
textX = x; // guard
|
||||||
|
} else {
|
||||||
|
if (display->getHeight() <= 64 && (key.character >= '0' && key.character <= '9')) {
|
||||||
|
textX = x + (width - textWidth + 1) / 2;
|
||||||
|
} else {
|
||||||
|
textX = x + (width - textWidth) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int contentTop = y;
|
||||||
|
int contentH = height;
|
||||||
|
if (selected) {
|
||||||
|
display->setColor(WHITE);
|
||||||
|
bool isAction = (key.type == VK_BACKSPACE || key.type == VK_ENTER || key.type == VK_SPACE || key.type == VK_ESC);
|
||||||
|
|
||||||
|
if (display->getHeight() <= 64 && !isAction) {
|
||||||
|
display->fillRect(x, y, width, height);
|
||||||
|
} else if (isAction) {
|
||||||
|
const int padX = 1;
|
||||||
|
const int padY = 2;
|
||||||
|
int hlW = textWidth + padX * 2;
|
||||||
|
int hlX = textX - padX;
|
||||||
|
|
||||||
|
if (hlX < x) {
|
||||||
|
hlW -= (x - hlX);
|
||||||
|
hlX = x;
|
||||||
|
}
|
||||||
|
int maxW = (x + width) - hlX;
|
||||||
|
if (hlW > maxW)
|
||||||
|
hlW = maxW;
|
||||||
|
if (hlW < 1)
|
||||||
|
hlW = 1;
|
||||||
|
|
||||||
|
int hlH = std::min(fontH + padY * 2, (int)height);
|
||||||
|
int hlY = y + (height - hlH) / 2;
|
||||||
|
display->fillRect(hlX, hlY, hlW, hlH);
|
||||||
|
contentTop = hlY;
|
||||||
|
contentH = hlH;
|
||||||
|
} else {
|
||||||
|
display->fillRect(x, y, width, height);
|
||||||
|
}
|
||||||
|
display->setColor(BLACK);
|
||||||
|
} else {
|
||||||
|
display->setColor(WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int centeredTextY;
|
||||||
|
if (display->getHeight() <= 64) {
|
||||||
|
centeredTextY = y + (height - fontH) / 2;
|
||||||
|
} else {
|
||||||
|
centeredTextY = contentTop + (contentH - fontH) / 2;
|
||||||
|
}
|
||||||
|
if (display->getHeight() > 64) {
|
||||||
|
if (centeredTextY < contentTop)
|
||||||
|
centeredTextY = contentTop;
|
||||||
|
if (centeredTextY + fontH > contentTop + contentH)
|
||||||
|
centeredTextY = std::max(contentTop, contentTop + contentH - fontH);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (display->getHeight() <= 64 && keyText.size() == 1) {
|
||||||
|
char ch = keyText[0];
|
||||||
|
if (ch == '.' || ch == ',' || ch == ';') {
|
||||||
|
centeredTextY -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef MUZI_BASE // Correct issue with character vertical position on MUZI_BASE
|
||||||
|
centeredTextY -= 2;
|
||||||
|
#endif
|
||||||
|
display->drawString(textX, centeredTextY, keyText.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
char VirtualKeyboard::getCharForKey(const VirtualKey &key, bool isLongPress)
|
||||||
|
{
|
||||||
|
if (key.type != VK_CHAR) {
|
||||||
|
return key.character;
|
||||||
|
}
|
||||||
|
|
||||||
|
char c = key.character;
|
||||||
|
|
||||||
|
// Long-press: only keep letter lowercase->uppercase conversion; remove other symbol mappings
|
||||||
|
if (isLongPress && c >= 'a' && c <= 'z') {
|
||||||
|
c = (char)(c - 'a' + 'A');
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::moveCursorDelta(int dRow, int dCol)
|
||||||
|
{
|
||||||
|
resetTimeout();
|
||||||
|
// wrap around rows and cols in the 4x11 grid
|
||||||
|
int r = (int)cursorRow + dRow;
|
||||||
|
int c = (int)cursorCol + dCol;
|
||||||
|
if (r < 0)
|
||||||
|
r = KEYBOARD_ROWS - 1;
|
||||||
|
else if (r >= KEYBOARD_ROWS)
|
||||||
|
r = 0;
|
||||||
|
if (c < 0)
|
||||||
|
c = KEYBOARD_COLS - 1;
|
||||||
|
else if (c >= KEYBOARD_COLS)
|
||||||
|
c = 0;
|
||||||
|
cursorRow = (uint8_t)r;
|
||||||
|
cursorCol = (uint8_t)c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::moveCursorUp()
|
||||||
|
{
|
||||||
|
moveCursorDelta(-1, 0);
|
||||||
|
}
|
||||||
|
void VirtualKeyboard::moveCursorDown()
|
||||||
|
{
|
||||||
|
moveCursorDelta(1, 0);
|
||||||
|
}
|
||||||
|
void VirtualKeyboard::moveCursorLeft()
|
||||||
|
{
|
||||||
|
resetTimeout();
|
||||||
|
|
||||||
|
if (cursorCol > 0) {
|
||||||
|
cursorCol--;
|
||||||
|
} else {
|
||||||
|
if (cursorRow > 0) {
|
||||||
|
cursorRow--;
|
||||||
|
cursorCol = KEYBOARD_COLS - 1;
|
||||||
|
} else {
|
||||||
|
cursorRow = KEYBOARD_ROWS - 1;
|
||||||
|
cursorCol = KEYBOARD_COLS - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void VirtualKeyboard::moveCursorRight()
|
||||||
|
{
|
||||||
|
resetTimeout();
|
||||||
|
|
||||||
|
if (cursorCol < KEYBOARD_COLS - 1) {
|
||||||
|
cursorCol++;
|
||||||
|
} else {
|
||||||
|
if (cursorRow < KEYBOARD_ROWS - 1) {
|
||||||
|
cursorRow++;
|
||||||
|
cursorCol = 0;
|
||||||
|
} else {
|
||||||
|
cursorRow = 0;
|
||||||
|
cursorCol = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::handlePress()
|
||||||
|
{
|
||||||
|
resetTimeout(); // Reset timeout on any input activity
|
||||||
|
|
||||||
|
const VirtualKey &key = keyboard[cursorRow][cursorCol];
|
||||||
|
|
||||||
|
// Don't handle press if the key is empty (but allow special keys)
|
||||||
|
if (key.character == 0 && key.type == VK_CHAR) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For character keys, insert lowercase character
|
||||||
|
if (key.type == VK_CHAR) {
|
||||||
|
insertCharacter(getCharForKey(key, false)); // false = lowercase/normal char
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle non-character keys immediately
|
||||||
|
switch (key.type) {
|
||||||
|
case VK_BACKSPACE:
|
||||||
|
deleteCharacter();
|
||||||
|
break;
|
||||||
|
case VK_ENTER:
|
||||||
|
submitText();
|
||||||
|
break;
|
||||||
|
case VK_SPACE:
|
||||||
|
insertCharacter(' ');
|
||||||
|
break;
|
||||||
|
case VK_ESC:
|
||||||
|
if (onTextEntered) {
|
||||||
|
std::function<void(const std::string &)> callback = onTextEntered;
|
||||||
|
onTextEntered = nullptr;
|
||||||
|
inputText = "";
|
||||||
|
callback("");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::handleLongPress()
|
||||||
|
{
|
||||||
|
resetTimeout(); // Reset timeout on any input activity
|
||||||
|
|
||||||
|
const VirtualKey &key = keyboard[cursorRow][cursorCol];
|
||||||
|
|
||||||
|
// Don't handle press if the key is empty (but allow special keys)
|
||||||
|
if (key.character == 0 && key.type == VK_CHAR) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For character keys, insert uppercase/alternate character
|
||||||
|
if (key.type == VK_CHAR) {
|
||||||
|
insertCharacter(getCharForKey(key, true)); // true = uppercase/alternate char
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (key.type) {
|
||||||
|
case VK_BACKSPACE:
|
||||||
|
// One-shot: delete up to 5 characters on long press
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
if (inputText.empty())
|
||||||
|
break;
|
||||||
|
deleteCharacter();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case VK_ENTER:
|
||||||
|
submitText();
|
||||||
|
break;
|
||||||
|
case VK_SPACE:
|
||||||
|
insertCharacter(' ');
|
||||||
|
break;
|
||||||
|
case VK_ESC:
|
||||||
|
if (onTextEntered) {
|
||||||
|
onTextEntered("");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::insertCharacter(char c)
|
||||||
|
{
|
||||||
|
if (inputText.length() < 160) { // Reasonable text length limit
|
||||||
|
inputText += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::deleteCharacter()
|
||||||
|
{
|
||||||
|
if (!inputText.empty()) {
|
||||||
|
inputText.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::submitText()
|
||||||
|
{
|
||||||
|
LOG_INFO("Virtual keyboard: submitting text '%s'", inputText.c_str());
|
||||||
|
|
||||||
|
// Only submit if text is not empty
|
||||||
|
if (!inputText.empty() && onTextEntered) {
|
||||||
|
// Store callback and text to submit before clearing callback
|
||||||
|
std::function<void(const std::string &)> callback = onTextEntered;
|
||||||
|
std::string textToSubmit = inputText;
|
||||||
|
onTextEntered = nullptr;
|
||||||
|
// Don't clear inputText here - let the calling module handle cleanup
|
||||||
|
// inputText = ""; // Removed: keep text visible until module cleans up
|
||||||
|
callback(textToSubmit);
|
||||||
|
} else if (inputText.empty()) {
|
||||||
|
// For empty text, just ignore the submission - don't clear callback
|
||||||
|
// This keeps the virtual keyboard responsive for further input
|
||||||
|
LOG_INFO("Virtual keyboard: empty text submitted, ignoring - keyboard remains active");
|
||||||
|
} else {
|
||||||
|
// No callback available
|
||||||
|
if (screen) {
|
||||||
|
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::setInputText(const std::string &text)
|
||||||
|
{
|
||||||
|
inputText = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string VirtualKeyboard::getInputText() const
|
||||||
|
{
|
||||||
|
return inputText;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::setHeader(const std::string &header)
|
||||||
|
{
|
||||||
|
headerText = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::setCallback(std::function<void(const std::string &)> callback)
|
||||||
|
{
|
||||||
|
onTextEntered = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::resetTimeout()
|
||||||
|
{
|
||||||
|
lastActivityTime = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VirtualKeyboard::isTimedOut() const
|
||||||
|
{
|
||||||
|
return (millis() - lastActivityTime) > TIMEOUT_MS;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace graphics
|
||||||
|
#endif
|
||||||
80
src/graphics/VirtualKeyboard.h
Normal file
80
src/graphics/VirtualKeyboard.h
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
#include <OLEDDisplay.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace graphics
|
||||||
|
{
|
||||||
|
|
||||||
|
enum VirtualKeyType { VK_CHAR, VK_BACKSPACE, VK_ENTER, VK_SHIFT, VK_ESC, VK_SPACE };
|
||||||
|
|
||||||
|
struct VirtualKey {
|
||||||
|
char character;
|
||||||
|
VirtualKeyType type;
|
||||||
|
uint8_t x;
|
||||||
|
uint8_t y;
|
||||||
|
uint8_t width;
|
||||||
|
uint8_t height;
|
||||||
|
};
|
||||||
|
|
||||||
|
class VirtualKeyboard
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
VirtualKeyboard();
|
||||||
|
~VirtualKeyboard();
|
||||||
|
|
||||||
|
void draw(OLEDDisplay *display, int16_t offsetX, int16_t offsetY);
|
||||||
|
void setInputText(const std::string &text);
|
||||||
|
std::string getInputText() const;
|
||||||
|
void setHeader(const std::string &header);
|
||||||
|
void setCallback(std::function<void(const std::string &)> callback);
|
||||||
|
|
||||||
|
// Navigation methods for encoder input
|
||||||
|
void moveCursorUp();
|
||||||
|
void moveCursorDown();
|
||||||
|
void moveCursorLeft();
|
||||||
|
void moveCursorRight();
|
||||||
|
void handlePress();
|
||||||
|
void handleLongPress();
|
||||||
|
|
||||||
|
// Timeout management
|
||||||
|
void resetTimeout();
|
||||||
|
bool isTimedOut() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const uint8_t KEYBOARD_ROWS = 4;
|
||||||
|
static const uint8_t KEYBOARD_COLS = 11;
|
||||||
|
static const uint8_t KEY_WIDTH = 9;
|
||||||
|
static const uint8_t KEY_HEIGHT = 9; // Compressed to fit 4 rows on 64px displays
|
||||||
|
static const uint8_t KEYBOARD_START_Y = 26; // Start just below input box bottom
|
||||||
|
|
||||||
|
VirtualKey keyboard[KEYBOARD_ROWS][KEYBOARD_COLS];
|
||||||
|
|
||||||
|
std::string inputText;
|
||||||
|
std::string headerText;
|
||||||
|
std::function<void(const std::string &)> onTextEntered;
|
||||||
|
|
||||||
|
uint8_t cursorRow;
|
||||||
|
uint8_t cursorCol;
|
||||||
|
|
||||||
|
// Timeout management for auto-exit
|
||||||
|
uint32_t lastActivityTime;
|
||||||
|
static const uint32_t TIMEOUT_MS = 60000; // 1 minute timeout
|
||||||
|
|
||||||
|
void initializeKeyboard();
|
||||||
|
void drawKey(OLEDDisplay *display, const VirtualKey &key, bool selected, int16_t x, int16_t y, uint8_t w, uint8_t h,
|
||||||
|
bool isLastCol);
|
||||||
|
void drawInputArea(OLEDDisplay *display, int16_t offsetX, int16_t offsetY, int16_t keyboardStartY);
|
||||||
|
|
||||||
|
// Unified cursor movement helper
|
||||||
|
void moveCursorDelta(int dRow, int dCol);
|
||||||
|
|
||||||
|
char getCharForKey(const VirtualKey &key, bool isLongPress = false);
|
||||||
|
void insertCharacter(char c);
|
||||||
|
void deleteCharacter();
|
||||||
|
void submitText();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace graphics
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "gps/RTC.h"
|
#include "gps/RTC.h"
|
||||||
#include "graphics/ScreenFonts.h"
|
#include "graphics/ScreenFonts.h"
|
||||||
#include "graphics/SharedUIDisplay.h"
|
#include "graphics/SharedUIDisplay.h"
|
||||||
|
#include "graphics/draw/UIRenderer.h"
|
||||||
#include "graphics/emotes.h"
|
#include "graphics/emotes.h"
|
||||||
#include "graphics/images.h"
|
#include "graphics/images.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
@@ -190,19 +191,15 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
|||||||
// === Set Title, Blank for Clock
|
// === Set Title, Blank for Clock
|
||||||
const char *titleStr = "";
|
const char *titleStr = "";
|
||||||
// === Header ===
|
// === Header ===
|
||||||
graphics::drawCommonHeader(display, x, y, titleStr, true);
|
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
|
||||||
|
int line = 0;
|
||||||
#ifdef T_WATCH_S3
|
|
||||||
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
|
|
||||||
graphics::ClockRenderer::drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
|
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
|
||||||
char timeString[16];
|
char timeString[16];
|
||||||
int hour = 0;
|
int hour = 0;
|
||||||
int minute = 0;
|
int minute = 0;
|
||||||
int second = 0;
|
int second = 0;
|
||||||
|
|
||||||
if (rtc_sec > 0) {
|
if (rtc_sec > 0) {
|
||||||
long hms = rtc_sec % SEC_PER_DAY;
|
long hms = rtc_sec % SEC_PER_DAY;
|
||||||
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
|
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
|
||||||
@@ -213,11 +210,11 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isPM = hour >= 12;
|
bool isPM = hour >= 12;
|
||||||
// hour = hour > 12 ? hour - 12 : hour;
|
|
||||||
if (config.display.use_12h_clock) {
|
if (config.display.use_12h_clock) {
|
||||||
hour %= 12;
|
hour %= 12;
|
||||||
if (hour == 0)
|
if (hour == 0) {
|
||||||
hour = 12;
|
hour = 12;
|
||||||
|
}
|
||||||
snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute);
|
snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute);
|
||||||
} else {
|
} else {
|
||||||
snprintf(timeString, sizeof(timeString), "%02d:%02d", hour, minute);
|
snprintf(timeString, sizeof(timeString), "%02d:%02d", hour, minute);
|
||||||
@@ -227,24 +224,56 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
|||||||
char secondString[8];
|
char secondString[8];
|
||||||
snprintf(secondString, sizeof(secondString), "%02d", second);
|
snprintf(secondString, sizeof(secondString), "%02d", second);
|
||||||
|
|
||||||
#ifdef T_WATCH_S3
|
static bool scaleInitialized = false;
|
||||||
float scale = 1.5;
|
static float scale = 0.75f;
|
||||||
#elif defined(CHATTER_2)
|
static float segmentWidth = SEGMENT_WIDTH * 0.75f;
|
||||||
float scale = 1.1;
|
static float segmentHeight = SEGMENT_HEIGHT * 0.75f;
|
||||||
#else
|
|
||||||
float scale = 0.75;
|
|
||||||
if (isHighResolution) {
|
|
||||||
scale = 1.5;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
|
if (!scaleInitialized) {
|
||||||
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
|
float screenwidth_target_ratio = 0.80f; // Target 80% of display width (adjustable)
|
||||||
|
float max_scale = 3.5f; // Safety limit to avoid runaway scaling
|
||||||
|
float step = 0.05f; // Step increment per iteration
|
||||||
|
|
||||||
|
float target_width = display->getWidth() * screenwidth_target_ratio;
|
||||||
|
float target_height =
|
||||||
|
display->getHeight() -
|
||||||
|
(isHighResolution
|
||||||
|
? 46
|
||||||
|
: 33); // Be careful adjusting this number, we have to account for header and the text under the time
|
||||||
|
|
||||||
|
float calculated_width_size = 0.0f;
|
||||||
|
float calculated_height_size = 0.0f;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
segmentWidth = SEGMENT_WIDTH * scale;
|
||||||
|
segmentHeight = SEGMENT_HEIGHT * scale;
|
||||||
|
|
||||||
|
calculated_width_size = segmentHeight + ((segmentWidth + (segmentHeight * 2) + 4) * 4);
|
||||||
|
calculated_height_size = segmentHeight + ((segmentHeight + (segmentHeight * 2) + 4) * 2);
|
||||||
|
|
||||||
|
if (calculated_width_size >= target_width || calculated_height_size >= target_height || scale >= max_scale) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
scale += step;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we overshot width, back off one step and recompute segment sizes
|
||||||
|
if (calculated_width_size > target_width || calculated_height_size > target_height) {
|
||||||
|
scale -= step;
|
||||||
|
segmentWidth = SEGMENT_WIDTH * scale;
|
||||||
|
segmentHeight = SEGMENT_HEIGHT * scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
scaleInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = strlen(timeString);
|
||||||
|
|
||||||
// calculate hours:minutes string width
|
// calculate hours:minutes string width
|
||||||
uint16_t timeStringWidth = strlen(timeString) * 5;
|
uint16_t timeStringWidth = len * 5; // base spacing between characters
|
||||||
|
|
||||||
for (uint8_t i = 0; i < strlen(timeString); i++) {
|
for (size_t i = 0; i < len; i++) {
|
||||||
char character = timeString[i];
|
char character = timeString[i];
|
||||||
|
|
||||||
if (character == ':') {
|
if (character == ':') {
|
||||||
@@ -255,19 +284,21 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint16_t hourMinuteTextX = (display->getWidth() / 2) - (timeStringWidth / 2);
|
uint16_t hourMinuteTextX = (display->getWidth() / 2) - (timeStringWidth / 2);
|
||||||
|
|
||||||
uint16_t startingHourMinuteTextX = hourMinuteTextX;
|
uint16_t startingHourMinuteTextX = hourMinuteTextX;
|
||||||
|
|
||||||
uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2);
|
uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2) + 2;
|
||||||
|
|
||||||
// iterate over characters in hours:minutes string and draw segmented characters
|
// iterate over characters in hours:minutes string and draw segmented characters
|
||||||
for (uint8_t i = 0; i < strlen(timeString); i++) {
|
for (size_t i = 0; i < len; i++) {
|
||||||
char character = timeString[i];
|
char character = timeString[i];
|
||||||
|
|
||||||
if (character == ':') {
|
if (character == ':') {
|
||||||
drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale);
|
drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale);
|
||||||
|
|
||||||
hourMinuteTextX += segmentHeight + 6;
|
hourMinuteTextX += segmentHeight + 6;
|
||||||
|
if (scale >= 2.0f) {
|
||||||
|
hourMinuteTextX += (uint16_t)(4.5f * scale);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character - '0', scale);
|
drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character - '0', scale);
|
||||||
|
|
||||||
@@ -277,33 +308,27 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
|||||||
hourMinuteTextX += 5;
|
hourMinuteTextX += 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw seconds string
|
// draw seconds string + AM/PM
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
int xOffset = (isHighResolution) ? 0 : -1;
|
int xOffset = (isHighResolution) ? 0 : -1;
|
||||||
if (hour >= 10) {
|
if (hour >= 10) {
|
||||||
xOffset += (isHighResolution) ? 32 : 18;
|
xOffset += (isHighResolution) ? 32 : 18;
|
||||||
}
|
}
|
||||||
int yOffset = (isHighResolution) ? 3 : 1;
|
|
||||||
#ifdef SENSECAP_INDICATOR
|
|
||||||
yOffset -= 3;
|
|
||||||
#endif
|
|
||||||
#ifdef T_DECK
|
|
||||||
yOffset -= 5;
|
|
||||||
#endif
|
|
||||||
if (config.display.use_12h_clock) {
|
if (config.display.use_12h_clock) {
|
||||||
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2,
|
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - 1, isPM ? "pm" : "am");
|
||||||
isPM ? "pm" : "am");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef USE_EINK
|
#ifndef USE_EINK
|
||||||
xOffset = (isHighResolution) ? 18 : 10;
|
xOffset = (isHighResolution) ? 18 : 10;
|
||||||
display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset,
|
if (scale >= 2.0f) {
|
||||||
|
xOffset -= (int)(4.5f * scale);
|
||||||
|
}
|
||||||
|
display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - 1,
|
||||||
secondString);
|
secondString);
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y)
|
graphics::drawCommonFooter(display, x, y);
|
||||||
{
|
|
||||||
display->drawFastImage(x, y, 18, 14, bluetoothConnectedIcon);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw an analog clock
|
// Draw an analog clock
|
||||||
@@ -313,13 +338,9 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
// === Set Title, Blank for Clock
|
// === Set Title, Blank for Clock
|
||||||
const char *titleStr = "";
|
const char *titleStr = "";
|
||||||
// === Header ===
|
// === Header ===
|
||||||
graphics::drawCommonHeader(display, x, y, titleStr, true);
|
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
|
||||||
|
int line = 0;
|
||||||
|
|
||||||
#ifdef T_WATCH_S3
|
|
||||||
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
|
|
||||||
drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// clock face center coordinates
|
// clock face center coordinates
|
||||||
int16_t centerX = display->getWidth() / 2;
|
int16_t centerX = display->getWidth() / 2;
|
||||||
int16_t centerY = display->getHeight() / 2;
|
int16_t centerY = display->getHeight() / 2;
|
||||||
@@ -512,6 +533,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
display->drawLine(centerX, centerY, secondX, secondY);
|
display->drawLine(centerX, centerY, secondX, secondY);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
graphics::drawCommonFooter(display, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ClockRenderer
|
} // namespace ClockRenderer
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user