mirror of
https://github.com/meshtastic/python.git
synced 2026-07-31 17:16:45 -04:00
Compare commits
543 Commits
2.4.3
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d94ae4067a | ||
|
|
ae710478fb | ||
|
|
3b084d7df2 | ||
|
|
3f62c099ae | ||
|
|
a509bca86d | ||
|
|
dca0b1fced | ||
|
|
11b71e9587 | ||
|
|
19d066970e | ||
|
|
485d860911 | ||
|
|
e6f07123f6 | ||
|
|
f623732694 | ||
|
|
82220ba49b | ||
|
|
4339cda6bd | ||
|
|
5760fb3ad9 | ||
|
|
2281ea5b64 | ||
|
|
7950798378 | ||
|
|
bffaff1c71 | ||
|
|
ceca7b3b10 | ||
|
|
15438c8a6e | ||
|
|
2f9b285dd5 | ||
|
|
9b892711df | ||
|
|
1cd1aed33c | ||
|
|
ab14fb2a86 | ||
|
|
7a7353ca15 | ||
|
|
e2844cb8d6 | ||
|
|
441c6e80a1 | ||
|
|
b05e545396 | ||
|
|
b6a13d58fe | ||
|
|
8c5efb926c | ||
|
|
6d76edf8a7 | ||
|
|
74cce6bab9 | ||
|
|
1309f4f344 | ||
|
|
fa01cb7a5d | ||
|
|
dcdbda0524 | ||
|
|
9479fb2a77 | ||
|
|
990ecc2350 | ||
|
|
7fc69e957c | ||
|
|
f0de977be5 | ||
|
|
c118334933 | ||
|
|
307d8d81e7 | ||
|
|
405a6bbc5d | ||
|
|
db746a0981 | ||
|
|
13b8cdcb04 | ||
|
|
9d445098f4 | ||
|
|
8c84074c1d | ||
|
|
1cffae6add | ||
|
|
e4f0fb222b | ||
|
|
6eeb90066c | ||
|
|
e216848c91 | ||
|
|
dd1df473c7 | ||
|
|
953d01206b | ||
|
|
c71d3df332 | ||
|
|
0413d00825 | ||
|
|
8f0faf5c3f | ||
|
|
ab656d54a8 | ||
|
|
c575547fae | ||
|
|
0bef1370d7 | ||
|
|
f9a1357816 | ||
|
|
d1f3552f64 | ||
|
|
81ae8b6c87 | ||
|
|
9c1573f1b3 | ||
|
|
ff26f9769d | ||
|
|
e5449be38e | ||
|
|
e350c238c5 | ||
|
|
2fca36b3fe | ||
|
|
b16441378e | ||
|
|
6037944215 | ||
|
|
6909d3d21f | ||
|
|
dbf4cabee1 | ||
|
|
435e53eae2 | ||
|
|
12509bef30 | ||
|
|
02485a88fb | ||
|
|
4d3553b955 | ||
|
|
95bd3ea7af | ||
|
|
2c443c082d | ||
|
|
ce5bbcda68 | ||
|
|
c2a01f8294 | ||
|
|
14f53398d7 | ||
|
|
4d8091a7e3 | ||
|
|
15ce4968db | ||
|
|
9c0b253103 | ||
|
|
c7f43b3100 | ||
|
|
8c7eb118fc | ||
|
|
badf7279af | ||
|
|
13c9f34589 | ||
|
|
781764553e | ||
|
|
f5e7faea3b | ||
|
|
e86a495b50 | ||
|
|
5dde67ef82 | ||
|
|
a7d13eb2c1 | ||
|
|
3916009dbd | ||
|
|
280323da8b | ||
|
|
89d81c9c7a | ||
|
|
fddaad316a | ||
|
|
bfe38ac0c7 | ||
|
|
c5fc51ea37 | ||
|
|
cec79a7c1f | ||
|
|
6805fee667 | ||
|
|
3cb9ce2a56 | ||
|
|
b05a6a6017 | ||
|
|
8a842ed82b | ||
|
|
eb964d78bb | ||
|
|
80c40ef893 | ||
|
|
2494bb4c63 | ||
|
|
1e4822fe87 | ||
|
|
c7ee644ad2 | ||
|
|
9af5f22837 | ||
|
|
1d3bdf143a | ||
|
|
7129a9f963 | ||
|
|
d8e438b225 | ||
|
|
6511d06e2f | ||
|
|
94c531e7ee | ||
|
|
ad4f3f557e | ||
|
|
1c9cf37974 | ||
|
|
cd9199bc00 | ||
|
|
04a23ae882 | ||
|
|
b003214d1b | ||
|
|
414a621091 | ||
|
|
a87065a081 | ||
|
|
942ce115f3 | ||
|
|
d5eaecea07 | ||
|
|
545c3ab192 | ||
|
|
fee2b6ccbd | ||
|
|
d0ccb1a597 | ||
|
|
5721859314 | ||
|
|
07172f88f3 | ||
|
|
3ea1994cf5 | ||
|
|
4de19f50d9 | ||
|
|
4d8430d360 | ||
|
|
bf580c36ae | ||
|
|
1eb13c9953 | ||
|
|
1fc2407c5e | ||
|
|
25ffd25fda | ||
|
|
cdf893e618 | ||
|
|
9b9df9e585 | ||
|
|
b26d80f186 | ||
|
|
da559f0e37 | ||
|
|
a73f432f42 | ||
|
|
c3c5ce64dd | ||
|
|
683dd23d63 | ||
|
|
57d43b84e4 | ||
|
|
4f6d183ed1 | ||
|
|
9a72e36ca6 | ||
|
|
4d54ee7431 | ||
|
|
c8b1b8ea6f | ||
|
|
d9057c0aaf | ||
|
|
77db30125a | ||
|
|
5cc0dae394 | ||
|
|
1a50377d34 | ||
|
|
2a44be9269 | ||
|
|
776debcc86 | ||
|
|
1088cc607e | ||
|
|
aeec5447ed | ||
|
|
096fec95c8 | ||
|
|
dea5f788a2 | ||
|
|
f15a0bdc0b | ||
|
|
1214d5010b | ||
|
|
0906fc6bc0 | ||
|
|
ccb530574b | ||
|
|
76418b8e57 | ||
|
|
3be73b42e2 | ||
|
|
2245ac8d97 | ||
|
|
040f332078 | ||
|
|
f3f17a7d50 | ||
|
|
79334e83e6 | ||
|
|
dbc0101a7a | ||
|
|
debbb8caeb | ||
|
|
3c6dba78a3 | ||
|
|
8a3b114153 | ||
|
|
0bb04100e1 | ||
|
|
ad04c26d13 | ||
|
|
0e67ef37aa | ||
|
|
e924afd140 | ||
|
|
87682c153b | ||
|
|
e6c276fe96 | ||
|
|
b4662251ed | ||
|
|
a17cfe9d2b | ||
|
|
471e3ce145 | ||
|
|
39c9864682 | ||
|
|
1d3a7d39f7 | ||
|
|
2065598754 | ||
|
|
4cac15686b | ||
|
|
9285f7d13f | ||
|
|
f6f1b748dc | ||
|
|
d2d9c03bc8 | ||
|
|
c44f9b1bb4 | ||
|
|
6f35eb3923 | ||
|
|
dcd077d85e | ||
|
|
da416fcd20 | ||
|
|
93da1da386 | ||
|
|
2de7c30a27 | ||
|
|
43a685f012 | ||
|
|
7554c03a26 | ||
|
|
38a13f300f | ||
|
|
eef8a37703 | ||
|
|
63b940defb | ||
|
|
e5efa94264 | ||
|
|
efa841b08c | ||
|
|
6ff13eb95c | ||
|
|
49783d9108 | ||
|
|
3615135e97 | ||
|
|
cfb23788e1 | ||
|
|
14ff3eb9c4 | ||
|
|
47e5b04d3b | ||
|
|
e5159f1156 | ||
|
|
593b05dbcd | ||
|
|
f519d1f2d2 | ||
|
|
8b36561406 | ||
|
|
e2b4948d45 | ||
|
|
7e3d347b63 | ||
|
|
c6efccdbd2 | ||
|
|
fe093ac34b | ||
|
|
2b10459db0 | ||
|
|
dd238dcbe3 | ||
|
|
e330afc899 | ||
|
|
a63f3f6e94 | ||
|
|
52eb112b95 | ||
|
|
d53ced216c | ||
|
|
f5ecd28705 | ||
|
|
82ad9b2f51 | ||
|
|
cbf7b9befe | ||
|
|
f3ba660cf4 | ||
|
|
4b143030d3 | ||
|
|
03aaa4c98e | ||
|
|
a79e17a575 | ||
|
|
38b163fa89 | ||
|
|
af4947d020 | ||
|
|
db1891b651 | ||
|
|
d7d9c7219a | ||
|
|
c60b5d4b05 | ||
|
|
83d82c518a | ||
|
|
8a95ce4636 | ||
|
|
0261313fc5 | ||
|
|
c1a6234694 | ||
|
|
66e32f812a | ||
|
|
eb85439000 | ||
|
|
885eb4898d | ||
|
|
172c123990 | ||
|
|
fcdd83838b | ||
|
|
58967e1d91 | ||
|
|
17f7e8e20e | ||
|
|
9b5a889676 | ||
|
|
ce7c61861f | ||
|
|
4adcbb6787 | ||
|
|
125f63419e | ||
|
|
cad5d18aff | ||
|
|
706d0649c1 | ||
|
|
167044907d | ||
|
|
ab1669994f | ||
|
|
6f67f33378 | ||
|
|
e60c8ea105 | ||
|
|
d633f8c895 | ||
|
|
ca82e1ce2b | ||
|
|
0ae23eec7e | ||
|
|
2fa85bac1f | ||
|
|
58fc614fb7 | ||
|
|
795b652069 | ||
|
|
213faa0cae | ||
|
|
68a2009e0e | ||
|
|
c76e4dac87 | ||
|
|
428be9fbce | ||
|
|
d83f7b2307 | ||
|
|
eb453a2e8a | ||
|
|
308ac93399 | ||
|
|
84417f0bb1 | ||
|
|
0bb3389b3b | ||
|
|
22b3062151 | ||
|
|
373b8a3139 | ||
|
|
db21942244 | ||
|
|
c55f1ef610 | ||
|
|
51b543ff40 | ||
|
|
8752a0de6e | ||
|
|
7160e79fbf | ||
|
|
b73fcbff88 | ||
|
|
1b5b07e752 | ||
|
|
ab997aac84 | ||
|
|
a097161dbc | ||
|
|
e6750507c8 | ||
|
|
0deb98b4c6 | ||
|
|
04a0ff6322 | ||
|
|
b4764d3bc3 | ||
|
|
9281c4a335 | ||
|
|
3c2dd6f4ff | ||
|
|
8e48d141c8 | ||
|
|
8a6ee5fb35 | ||
|
|
aa786c7ebd | ||
|
|
23be2d2189 | ||
|
|
622a435465 | ||
|
|
56680f8da6 | ||
|
|
321a960c13 | ||
|
|
4668852b0b | ||
|
|
c3973117c8 | ||
|
|
d456e4ce30 | ||
|
|
ec78f62992 | ||
|
|
dfc9547ffc | ||
|
|
ee0f73a20e | ||
|
|
2e73fe310c | ||
|
|
d4bc39153a | ||
|
|
2e8f823431 | ||
|
|
675169167c | ||
|
|
75f8e27799 | ||
|
|
f426699d2b | ||
|
|
bc1664dade | ||
|
|
32a61b0209 | ||
|
|
0c38a9eb0b | ||
|
|
e591cc184f | ||
|
|
b42d33824a | ||
|
|
ed908fc4b6 | ||
|
|
3710e6e909 | ||
|
|
faa8064ccc | ||
|
|
2f44351945 | ||
|
|
f5fa30cb22 | ||
|
|
46a8db286c | ||
|
|
852949575b | ||
|
|
acff793f28 | ||
|
|
e2c9c1315e | ||
|
|
f41ef042a9 | ||
|
|
84bec5a7c4 | ||
|
|
985366c812 | ||
|
|
3954fbd404 | ||
|
|
5f174b2850 | ||
|
|
23ea19c00b | ||
|
|
acc47146c9 | ||
|
|
dd8803793d | ||
|
|
68ec588804 | ||
|
|
53b58500f7 | ||
|
|
2f48594dc3 | ||
|
|
c7c3c69fc3 | ||
|
|
53e40d5aab | ||
|
|
dd3da6a670 | ||
|
|
e500b399f4 | ||
|
|
27ac28e300 | ||
|
|
6bc5f5e305 | ||
|
|
c844e4e0fe | ||
|
|
060df86bb6 | ||
|
|
7d87d5037e | ||
|
|
4ec7698d94 | ||
|
|
7cc65aa08a | ||
|
|
cc411ce0bb | ||
|
|
edff956f9d | ||
|
|
bd68739158 | ||
|
|
530d92ead2 | ||
|
|
60f9dc6266 | ||
|
|
f9ae021e43 | ||
|
|
317d81c983 | ||
|
|
5837bd0172 | ||
|
|
5487f7a791 | ||
|
|
c6d8a540eb | ||
|
|
0962c9b058 | ||
|
|
4f98602ac2 | ||
|
|
6ebddb67c0 | ||
|
|
82554a1f18 | ||
|
|
8c115dc636 | ||
|
|
e2fe359527 | ||
|
|
5600ce92b0 | ||
|
|
efb848adf9 | ||
|
|
0d8646189f | ||
|
|
d0023df8ca | ||
|
|
b522abf33e | ||
|
|
c086b6372e | ||
|
|
6ec506fe3b | ||
|
|
fc3b81dfde | ||
|
|
9c53ea017c | ||
|
|
1e6625d062 | ||
|
|
0487ce5e1a | ||
|
|
aac19b2ecc | ||
|
|
0fb72b8ad1 | ||
|
|
872fbef5d6 | ||
|
|
ec4fbe3a59 | ||
|
|
6bab385380 | ||
|
|
b8178d513a | ||
|
|
f4c085fc50 | ||
|
|
57f0598082 | ||
|
|
55d3188408 | ||
|
|
7b64fbb71b | ||
|
|
7f85eb0285 | ||
|
|
d05ef17ab3 | ||
|
|
d161291ca4 | ||
|
|
4e267c75b0 | ||
|
|
f56b9eefa6 | ||
|
|
2de1f1921c | ||
|
|
227507780e | ||
|
|
9f286c9023 | ||
|
|
0b1545393e | ||
|
|
245a9e40b1 | ||
|
|
749c6a70bc | ||
|
|
afd071c24e | ||
|
|
29f355bd61 | ||
|
|
4b6d7a8587 | ||
|
|
7cc18e9df6 | ||
|
|
a765bccf4d | ||
|
|
f950ecae2d | ||
|
|
7c7170a5dd | ||
|
|
df191e149b | ||
|
|
843abe587f | ||
|
|
ff7dcc3afb | ||
|
|
d66b8fa9dd | ||
|
|
f6f8ccfcbc | ||
|
|
cace959ca4 | ||
|
|
01ffd83d64 | ||
|
|
9284a848f2 | ||
|
|
18ac0d6d5c | ||
|
|
7c89e231bd | ||
|
|
4673824236 | ||
|
|
d87eddfd33 | ||
|
|
31f322f1c2 | ||
|
|
89b41c1a19 | ||
|
|
1a5ca789c2 | ||
|
|
74c911cb75 | ||
|
|
579383cd5a | ||
|
|
03ac322583 | ||
|
|
c63814358a | ||
|
|
663fabce74 | ||
|
|
6243965044 | ||
|
|
b180b6fb15 | ||
|
|
7bb8e4e9dd | ||
|
|
4c7ac60be6 | ||
|
|
0b086d10f8 | ||
|
|
426795fccd | ||
|
|
fb88ee114c | ||
|
|
a4630b53eb | ||
|
|
646aa981d5 | ||
|
|
9381acd6ac | ||
|
|
384063db19 | ||
|
|
20d75d9023 | ||
|
|
0deb1d788f | ||
|
|
1070d9202b | ||
|
|
b90de8b73b | ||
|
|
2e79ecf759 | ||
|
|
578d3e4b24 | ||
|
|
4ca13bcede | ||
|
|
6ceae7c72f | ||
|
|
4c29d7dd0f | ||
|
|
839bbbcad2 | ||
|
|
1abb9fb213 | ||
|
|
7fcbbe9b80 | ||
|
|
073274cb00 | ||
|
|
92a3986a8f | ||
|
|
f08ec1885b | ||
|
|
feca49faed | ||
|
|
dfaf1a275d | ||
|
|
da7fa31805 | ||
|
|
12fd29b203 | ||
|
|
a43dd201ba | ||
|
|
a64a9d203a | ||
|
|
10136962d7 | ||
|
|
3afb294f9b | ||
|
|
ee405fec41 | ||
|
|
3eabaf91d0 | ||
|
|
78b92cecc9 | ||
|
|
7088b90514 | ||
|
|
2ae81f8602 | ||
|
|
923f5e82d0 | ||
|
|
05731128fa | ||
|
|
0523d4c94f | ||
|
|
90e901de79 | ||
|
|
6606851135 | ||
|
|
33fecbd74d | ||
|
|
6b9db7abd9 | ||
|
|
ece6286d82 | ||
|
|
e335f12a3b | ||
|
|
0da405168f | ||
|
|
58d9039a04 | ||
|
|
f77e788aa8 | ||
|
|
aba381fb54 | ||
|
|
0bb4b31b6a | ||
|
|
915066e0af | ||
|
|
6be3969577 | ||
|
|
b73cc1f499 | ||
|
|
65305af184 | ||
|
|
3fb1e67357 | ||
|
|
cbd3c119fe | ||
|
|
bbd6d6a541 | ||
|
|
6e1217c7ca | ||
|
|
81db38956b | ||
|
|
27729995d2 | ||
|
|
d875a574b6 | ||
|
|
40019a9712 | ||
|
|
de657bab24 | ||
|
|
40353a387e | ||
|
|
9949d144a1 | ||
|
|
48a06c6e1e | ||
|
|
c696d59b90 | ||
|
|
4fdbcb9679 | ||
|
|
5d6dfb877b | ||
|
|
951edfe27b | ||
|
|
5cc9627e21 | ||
|
|
bf904c6906 | ||
|
|
2026212a00 | ||
|
|
34f9be255e | ||
|
|
e561222ea7 | ||
|
|
73a1bbc7d5 | ||
|
|
8f2c397fbf | ||
|
|
62f5201a38 | ||
|
|
9e7d5e96ab | ||
|
|
aa74db46cb | ||
|
|
1967519deb | ||
|
|
662aea049a | ||
|
|
44cfd72a80 | ||
|
|
abe1dd47ca | ||
|
|
584a14f578 | ||
|
|
f7724295f9 | ||
|
|
cc2067b729 | ||
|
|
180ddbcd1a | ||
|
|
3bbd02c915 | ||
|
|
23e6eca056 | ||
|
|
21ff4a1a4a | ||
|
|
ef6db0e48c | ||
|
|
43b0993aaa | ||
|
|
8ba92da7cf | ||
|
|
0d26c26f7e | ||
|
|
399dd477b8 | ||
|
|
3811226a61 | ||
|
|
b6547c9737 | ||
|
|
9612aea9b9 | ||
|
|
4b60c5b457 | ||
|
|
c92474cf36 | ||
|
|
b2acc84717 | ||
|
|
1981f0e899 | ||
|
|
84dff75399 | ||
|
|
df12b8a659 | ||
|
|
eedf42b904 | ||
|
|
e7ed254d9d | ||
|
|
dfa29bbb7c | ||
|
|
5a06888cc7 | ||
|
|
a9e2168f1d | ||
|
|
eec745c861 | ||
|
|
4cc283d004 | ||
|
|
bc508ff9e6 | ||
|
|
ff72fc4804 | ||
|
|
78399503c5 | ||
|
|
216fd7ddc4 | ||
|
|
688693d2fb | ||
|
|
15b5e93563 | ||
|
|
1bbcc452ae | ||
|
|
1abe00d0b2 | ||
|
|
c8cf8094c3 | ||
|
|
477690edde | ||
|
|
58466f2ab7 | ||
|
|
bb6f51eb43 | ||
|
|
48987c38e2 | ||
|
|
60de9dddb1 | ||
|
|
a29ee840f2 |
16
.coderabbit.yaml
Normal file
16
.coderabbit.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
language: en-US
|
||||
reviews:
|
||||
profile: chill
|
||||
high_level_summary: true
|
||||
poem: false
|
||||
auto_review:
|
||||
enabled: true
|
||||
drafts: false
|
||||
ignore_usernames:
|
||||
- renovate
|
||||
- renovate[bot]
|
||||
abort_on_close: true
|
||||
|
||||
path_filters:
|
||||
- "!/protobufs/**"
|
||||
- "!/meshtastic/protobuf/**"
|
||||
11
.dockerignore
Normal file
11
.dockerignore
Normal file
@@ -0,0 +1,11 @@
|
||||
*
|
||||
!Containerfile*
|
||||
!Dockerfile
|
||||
!README.md
|
||||
!LICENSE.md
|
||||
!bin/container-entrypoint.sh
|
||||
!examples/
|
||||
!extra/
|
||||
!meshtastic/
|
||||
!poetry.lock
|
||||
!pyproject.toml
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -2,3 +2,4 @@
|
||||
*.{cmd,[cC][mM][dD]} text eol=crlf
|
||||
*.{bat,[bB][aA][tT]} text eol=crlf
|
||||
*.{sh,[sS][hH]} text eol=lf
|
||||
meshtastic/protobuf/* linguist-generated=true
|
||||
|
||||
22
.github/CONTRIBUTING.md
vendored
Normal file
22
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# Contributing to Meshtastic Python
|
||||
|
||||
## Development resources
|
||||
- [API Documentation](https://python.meshtastic.org/)
|
||||
- [Meshtastic Python Development](https://meshtastic.org/docs/development/python/)
|
||||
- [Building Meshtastic Python](https://meshtastic.org/docs/development/python/building/)
|
||||
- [Using the Meshtastic Python Library](https://meshtastic.org/docs/development/python/library/)
|
||||
|
||||
## How to check your code (pytest/pylint) before a PR
|
||||
- [Pre-requisites](https://meshtastic.org/docs/development/python/building/#pre-requisites)
|
||||
- also execute `poetry install --all-extras --with dev,powermon` for all optional dependencies
|
||||
- check your code with github ci actions locally
|
||||
- You need to have act installed. You can get it at https://nektosact.com/
|
||||
- on linux: `act -P ubuntu-latest=-self-hosted --matrix "python-version:3.12"`
|
||||
- on windows:
|
||||
- linux checks (linux docker): `act --matrix "python-version:3.12"`
|
||||
- windows checks (windows host): `act -P ubuntu-latest=-self-hosted --matrix "python-version:3.12"`
|
||||
- or run all locally:
|
||||
- run `poetry run pylint meshtastic examples/ --ignore-patterns ".*_pb2.pyi?$"`
|
||||
- run `poetry run mypy meshtastic/`
|
||||
- run `poetry run pytest`
|
||||
- more commands see [CI workflow](https://github.com/meshtastic/python/blob/master/.github/workflows/ci.yml)
|
||||
206
.github/copilot-instructions.md
vendored
Normal file
206
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
# Copilot Instructions for Meshtastic Python
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is the Meshtastic Python library and CLI - a Python API for interacting with Meshtastic mesh radio devices. It supports communication via Serial, TCP, and BLE interfaces.
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- **Language**: Python 3.9 - 3.14
|
||||
- **Package Manager**: Poetry
|
||||
- **Testing**: pytest with hypothesis for property-based testing
|
||||
- **Linting**: pylint
|
||||
- **Type Checking**: mypy (working toward strict mode)
|
||||
- **Documentation**: pdoc3
|
||||
- **License**: GPL-3.0
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
meshtastic/ # Main library package
|
||||
├── __init__.py # Core interface classes and pub/sub topics
|
||||
├── __main__.py # CLI entry point
|
||||
├── mesh_interface.py # Base interface class for all connection types
|
||||
├── serial_interface.py
|
||||
├── tcp_interface.py
|
||||
├── ble_interface.py
|
||||
├── node.py # Node representation and configuration
|
||||
├── protobuf/ # Generated Protocol Buffer files (*_pb2.py, *_pb2.pyi)
|
||||
├── tests/ # Unit and integration tests
|
||||
├── powermon/ # Power monitoring tools
|
||||
└── analysis/ # Data analysis tools
|
||||
examples/ # Usage examples
|
||||
protobufs/ # Protocol Buffer source definitions
|
||||
```
|
||||
|
||||
## Coding Standards
|
||||
|
||||
### Style Guidelines
|
||||
|
||||
- Follow PEP 8 style conventions
|
||||
- Use type hints for function parameters and return values
|
||||
- Document public functions and classes with docstrings
|
||||
- Prefer explicit imports over wildcard imports
|
||||
- Use `logging` module instead of print statements for debug output
|
||||
|
||||
### Type Annotations
|
||||
|
||||
- Add type hints to all new code
|
||||
- Use `Optional[T]` for nullable types
|
||||
- Use `Dict`, `List`, `Tuple` from `typing` module for Python 3.9 compatibility
|
||||
- Protobuf types are in `meshtastic.protobuf.*_pb2` modules
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
- Classes: `PascalCase` (e.g., `MeshInterface`, `SerialInterface`)
|
||||
- Functions/methods: `camelCase` for public API (e.g., `sendText`, `sendData`)
|
||||
- Internal functions: `snake_case` with leading underscore (e.g., `_send_packet`)
|
||||
- Constants: `UPPER_SNAKE_CASE` (e.g., `BROADCAST_ADDR`, `LOCAL_ADDR`)
|
||||
|
||||
### Error Handling
|
||||
|
||||
- Use custom exception classes when appropriate (e.g., `MeshInterface.MeshInterfaceError`)
|
||||
- Provide meaningful error messages
|
||||
- Use `our_exit()` from `meshtastic.util` for CLI exits with error codes
|
||||
|
||||
## Testing
|
||||
|
||||
### Test Organization
|
||||
|
||||
Tests are in `meshtastic/tests/` and use pytest markers:
|
||||
|
||||
- `@pytest.mark.unit` - Fast unit tests (default)
|
||||
- `@pytest.mark.unitslow` - Slower unit tests
|
||||
- `@pytest.mark.int` - Integration tests
|
||||
- `@pytest.mark.smoke1` - Single device smoke tests
|
||||
- `@pytest.mark.smoke2` - Two device smoke tests
|
||||
- `@pytest.mark.smokevirt` - Virtual device smoke tests
|
||||
- `@pytest.mark.examples` - Example validation tests
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run unit tests only (default)
|
||||
make test
|
||||
# or
|
||||
pytest -m unit
|
||||
|
||||
# Run all tests
|
||||
pytest
|
||||
|
||||
# Run with coverage
|
||||
make cov
|
||||
```
|
||||
|
||||
### Writing Tests
|
||||
|
||||
- Use `pytest` fixtures from `conftest.py`
|
||||
- Use `hypothesis` for property-based testing where appropriate
|
||||
- Mock external dependencies (serial ports, network connections)
|
||||
- Test file naming: `test_<module_name>.py`
|
||||
|
||||
## Pub/Sub Events
|
||||
|
||||
The library uses pypubsub for event handling. Key topics:
|
||||
|
||||
- `meshtastic.connection.established` - Connection successful
|
||||
- `meshtastic.connection.lost` - Connection lost
|
||||
- `meshtastic.receive.text(packet)` - Text message received
|
||||
- `meshtastic.receive.position(packet)` - Position update received
|
||||
- `meshtastic.receive.data.portnum(packet)` - Data packet by port number
|
||||
- `meshtastic.node.updated(node)` - Node database changed
|
||||
- `meshtastic.log.line(line)` - Raw log line from device
|
||||
|
||||
## Protocol Buffers
|
||||
|
||||
- Protobuf definitions are in `protobufs/meshtastic/`
|
||||
- Generated Python files are in `meshtastic/protobuf/`
|
||||
- Never edit `*_pb2.py` or `*_pb2.pyi` files directly
|
||||
- Regenerate with: `make protobufs` or `./bin/regen-protobufs.sh`
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Creating an Interface
|
||||
|
||||
```python
|
||||
import meshtastic.serial_interface
|
||||
|
||||
# Auto-detect device
|
||||
iface = meshtastic.serial_interface.SerialInterface()
|
||||
|
||||
# Specific device
|
||||
iface = meshtastic.serial_interface.SerialInterface(devPath="/dev/ttyUSB0")
|
||||
|
||||
# Always close when done
|
||||
iface.close()
|
||||
|
||||
# Or use context manager
|
||||
with meshtastic.serial_interface.SerialInterface() as iface:
|
||||
iface.sendText("Hello mesh")
|
||||
```
|
||||
|
||||
### Sending Messages
|
||||
|
||||
```python
|
||||
# Text message (broadcast)
|
||||
iface.sendText("Hello")
|
||||
|
||||
# Text message to specific node
|
||||
iface.sendText("Hello", destinationId="!abcd1234")
|
||||
|
||||
# Binary data
|
||||
iface.sendData(data, portNum=portnums_pb2.PRIVATE_APP)
|
||||
```
|
||||
|
||||
### Subscribing to Events
|
||||
|
||||
```python
|
||||
from pubsub import pub
|
||||
|
||||
def on_receive(packet, interface):
|
||||
print(f"Received: {packet}")
|
||||
|
||||
pub.subscribe(on_receive, "meshtastic.receive")
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. Install dependencies: `poetry install --all-extras --with dev`
|
||||
2. Make changes
|
||||
3. Run linting: `poetry run pylint meshtastic examples/`
|
||||
4. Run type checking: `poetry run mypy meshtastic/`
|
||||
5. Run tests: `poetry run pytest -m unit`
|
||||
6. Update documentation if needed
|
||||
|
||||
## CLI Development
|
||||
|
||||
The CLI is in `meshtastic/__main__.py`. When adding new CLI commands:
|
||||
|
||||
- Use argparse for argument parsing
|
||||
- Support `--dest` for specifying target node
|
||||
- Provide `--help` documentation
|
||||
- Handle errors gracefully with meaningful messages
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Required
|
||||
- `pyserial` - Serial port communication
|
||||
- `protobuf` - Protocol Buffers
|
||||
- `pypubsub` - Pub/sub messaging
|
||||
- `bleak` - BLE communication
|
||||
- `tabulate` - Table formatting
|
||||
- `pyyaml` - YAML config support
|
||||
- `requests` - HTTP requests
|
||||
|
||||
### Optional (extras)
|
||||
- `cli` extra: `pyqrcode`, `print-color`, `dotmap`, `argcomplete`
|
||||
- `tunnel` extra: `pytap2`
|
||||
- `analysis` extra: `dash`, `pandas`
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Always test with actual Meshtastic hardware when possible
|
||||
- Be mindful of radio regulations in your region
|
||||
- The nodedb (`interface.nodes`) is read-only
|
||||
- Packet IDs are random 32-bit integers
|
||||
- Default timeout is 300 seconds for operations
|
||||
BIN
.github/meshtastic_logo.png
vendored
Normal file
BIN
.github/meshtastic_logo.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
71
.github/workflows/ci.yml
vendored
71
.github/workflows/ci.yml
vendored
@@ -1,4 +1,7 @@
|
||||
name: CI
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -8,7 +11,29 @@ on:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.12"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install meshtastic from local
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip3 install poetry
|
||||
poetry install --all-extras --with dev,powermon
|
||||
poetry run meshtastic --version
|
||||
|
||||
build:
|
||||
needs: validate
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -17,10 +42,14 @@ jobs:
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
- "3.14"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Python 3
|
||||
- name: Install Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Uninstall meshtastic
|
||||
run: |
|
||||
pip3 uninstall -y meshtastic
|
||||
@@ -32,15 +61,8 @@ jobs:
|
||||
run: |
|
||||
poetry install --all-extras --with dev,powermon
|
||||
poetry run meshtastic --version
|
||||
- name: Run pylint
|
||||
run: poetry run pylint meshtastic examples/ --ignore-patterns ".*_pb2.pyi?$"
|
||||
- name: Check types with mypy
|
||||
run: poetry run mypy meshtastic/
|
||||
- name: Run tests with pytest
|
||||
run: poetry run pytest --cov=meshtastic
|
||||
- name: Generate coverage report
|
||||
run: |
|
||||
poetry run pytest --cov=meshtastic --cov-report=xml
|
||||
- name: Run lint, type check, and tests in parallel
|
||||
run: make -j3 --output-sync=target ci
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
@@ -51,22 +73,37 @@ jobs:
|
||||
name: codecov-umbrella
|
||||
fail_ci_if_error: true
|
||||
verbose: true
|
||||
validate:
|
||||
|
||||
simradio_testing:
|
||||
needs: [validate, build]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
channel:
|
||||
- beta
|
||||
- alpha
|
||||
- daily
|
||||
continue-on-error: ${{ matrix.channel == 'daily' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install Python 3
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Install meshtastic from local
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip3 install poetry
|
||||
poetry install
|
||||
poetry install --all-extras --with dev
|
||||
poetry run meshtastic --version
|
||||
- name: Install meshtasticd (${{ matrix.channel }}) from PPA
|
||||
run: |
|
||||
sudo add-apt-repository -y ppa:meshtastic/${{ matrix.channel }}
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y meshtasticd
|
||||
- name: Run firmware smoke tests
|
||||
run: poetry run pytest -m "smokevirt or smokemesh" -v
|
||||
timeout-minutes: 15
|
||||
|
||||
79
.github/workflows/container-build.yaml
vendored
Normal file
79
.github/workflows/container-build.yaml
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
name: Create and publish Container image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*.*.*'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- container: Containerfile.debian
|
||||
autotag: false
|
||||
suffix: -debian
|
||||
- container: Containerfile.alpine
|
||||
autotag: ${{ github.ref == 'refs/heads/master' && 'true' || 'auto' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Container registry
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=edge
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
flavor: |
|
||||
latest=${{ matrix.autotag }}
|
||||
suffix=${{ matrix.suffix }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
context: .
|
||||
file: ${{ matrix.container }}
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
36
.github/workflows/daily_simradio.yml
vendored
Normal file
36
.github/workflows/daily_simradio.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Daily SimRadio Tests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: 0 6 * * *
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
simradio_testing:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'meshtastic/python'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install Python 3
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Install meshtastic from local
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip3 install poetry
|
||||
poetry install --all-extras --with dev
|
||||
poetry run meshtastic --version
|
||||
- name: Install meshtasticd (daily) from PPA
|
||||
run: |
|
||||
sudo add-apt-repository -y ppa:meshtastic/daily
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y meshtasticd
|
||||
- name: Run firmware smoke tests
|
||||
run: poetry run pytest -m "smokevirt or smokemesh" -v
|
||||
timeout-minutes: 15
|
||||
33
.github/workflows/update_protobufs.yml
vendored
33
.github/workflows/update_protobufs.yml
vendored
@@ -1,6 +1,9 @@
|
||||
name: "Update protobufs"
|
||||
on: workflow_dispatch
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-protobufs:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -9,23 +12,34 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
||||
- name: Update Submodule
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install Poetry
|
||||
run: |
|
||||
git pull --recurse-submodules
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install poetry
|
||||
|
||||
- name: Update protobuf submodule
|
||||
run: |
|
||||
git submodule sync --recursive
|
||||
git submodule update --init --recursive
|
||||
git submodule update --remote --recursive
|
||||
|
||||
- name: Download nanopb
|
||||
run: |
|
||||
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.8-linux-x86.tar.gz
|
||||
curl -L -o nanopb-0.4.8-linux-x86.tar.gz https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.8-linux-x86.tar.gz
|
||||
tar xvzf nanopb-0.4.8-linux-x86.tar.gz
|
||||
mv nanopb-0.4.8-linux-x86 nanopb-0.4.8
|
||||
|
||||
- name: Install poetry (needed by regen-protobufs.sh)
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip3 install poetry
|
||||
poetry install --with dev
|
||||
|
||||
- name: Re-generate protocol buffers
|
||||
run: |
|
||||
@@ -38,4 +52,9 @@ jobs:
|
||||
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
|
||||
git add protobufs
|
||||
git add meshtastic/protobuf
|
||||
git commit -m "Update protobuf submodule" && git push || echo "No changes to commit"
|
||||
if [[ -n "$(git status --porcelain)" ]]; then
|
||||
git commit -m "Update protobufs"
|
||||
git push
|
||||
else
|
||||
echo "No changes to commit"
|
||||
fi
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -17,4 +17,5 @@ examples/__pycache__
|
||||
meshtastic.spec
|
||||
.hypothesis/
|
||||
coverage.xml
|
||||
.ipynb_checkpoints
|
||||
.ipynb_checkpoints
|
||||
.cursor/
|
||||
27
.vscode/launch.json
vendored
27
.vscode/launch.json
vendored
@@ -4,6 +4,7 @@
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "meshtastic BLE",
|
||||
"type": "debugpy",
|
||||
@@ -245,6 +246,30 @@
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
"args": ["--debug", "--nodes"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "meshtastic nodes table",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
"args": ["--nodes"]
|
||||
},
|
||||
{
|
||||
"name": "meshtastic nodes table with show-fields",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
"args": ["--nodes", "--show-fields", "AKA,Pubkey,Role,Role,Role,Latitude,Latitude,deviceMetrics.voltage"]
|
||||
},
|
||||
{
|
||||
"name": "meshtastic --export-config",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
"args": ["--export-config", "config.json"]
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
1
Containerfile
Symbolic link
1
Containerfile
Symbolic link
@@ -0,0 +1 @@
|
||||
Containerfile.alpine
|
||||
40
Containerfile.alpine
Normal file
40
Containerfile.alpine
Normal file
@@ -0,0 +1,40 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Copyright (C) 2025 Olliver Schinagl <oliver@schinagl.nl>
|
||||
|
||||
ARG TARGET_VERSION="3.11-alpine"
|
||||
ARG TARGET_ARCH="library"
|
||||
|
||||
FROM docker.io/${TARGET_ARCH}/python:${TARGET_VERSION} AS builder
|
||||
|
||||
WORKDIR /tmp/build
|
||||
|
||||
COPY pyproject.toml poetry.lock /tmp/build/
|
||||
|
||||
RUN apk add --no-cache --virtual .build-deps build-base libffi-dev && \
|
||||
pip install --no-cache-dir 'poetry==2.4.1' && \
|
||||
poetry config virtualenvs.create false && \
|
||||
poetry install --without dev --extras cli --extras tunnel --no-interaction --no-ansi --no-root
|
||||
|
||||
COPY . /tmp/build/
|
||||
|
||||
RUN poetry build --format wheel --no-interaction
|
||||
|
||||
FROM docker.io/${TARGET_ARCH}/python:${TARGET_VERSION}
|
||||
|
||||
RUN apk add --no-cache libffi && \
|
||||
addgroup -S meshtastic && \
|
||||
adduser -S -G meshtastic -h /home/meshtastic meshtastic
|
||||
|
||||
COPY --from=builder /tmp/build/dist/*.whl /tmp/
|
||||
|
||||
RUN wheel=$(echo /tmp/meshtastic-*.whl) && pip install --no-cache-dir "${wheel}[cli,tunnel]" && \
|
||||
rm -f /tmp/meshtastic-*.whl
|
||||
|
||||
COPY "./bin/container-entrypoint.sh" "/init"
|
||||
RUN chmod 0755 /init
|
||||
|
||||
WORKDIR /home/meshtastic
|
||||
USER meshtastic
|
||||
|
||||
ENTRYPOINT [ "/init" ]
|
||||
37
Containerfile.debian
Normal file
37
Containerfile.debian
Normal file
@@ -0,0 +1,37 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Copyright (C) 2025 Olliver Schinagl <oliver@schinagl.nl>
|
||||
|
||||
ARG TARGET_VERSION="3.11"
|
||||
ARG TARGET_ARCH="library"
|
||||
|
||||
FROM docker.io/${TARGET_ARCH}/python:${TARGET_VERSION} AS builder
|
||||
|
||||
WORKDIR /tmp/build
|
||||
|
||||
COPY pyproject.toml poetry.lock /tmp/build/
|
||||
|
||||
RUN pip install --no-cache-dir 'poetry==2.4.1' && \
|
||||
poetry config virtualenvs.create false && \
|
||||
poetry install --without dev --extras cli --extras tunnel --no-interaction --no-ansi --no-root
|
||||
|
||||
COPY . /tmp/build/
|
||||
|
||||
RUN poetry build --format wheel --no-interaction
|
||||
|
||||
FROM docker.io/${TARGET_ARCH}/python:${TARGET_VERSION}
|
||||
|
||||
RUN useradd --system --create-home --home-dir /home/meshtastic meshtastic
|
||||
|
||||
COPY --from=builder /tmp/build/dist/*.whl /tmp/
|
||||
|
||||
RUN wheel=$(echo /tmp/meshtastic-*.whl) && pip install --no-cache-dir "${wheel}[cli,tunnel]" && \
|
||||
rm -f /tmp/meshtastic-*.whl
|
||||
|
||||
COPY "./bin/container-entrypoint.sh" "/init"
|
||||
RUN chmod 0755 /init
|
||||
|
||||
WORKDIR /home/meshtastic
|
||||
USER meshtastic
|
||||
|
||||
ENTRYPOINT [ "/init" ]
|
||||
1
Dockerfile
Symbolic link
1
Dockerfile
Symbolic link
@@ -0,0 +1 @@
|
||||
Containerfile
|
||||
14
Makefile
14
Makefile
@@ -42,5 +42,19 @@ cov:
|
||||
examples: FORCE
|
||||
pytest -mexamples
|
||||
|
||||
# CI targets (run via poetry, executed in parallel by the CI workflow)
|
||||
.PHONY: ci-pylint ci-mypy ci-test ci
|
||||
|
||||
ci-pylint:
|
||||
poetry run pylint meshtastic examples/ --ignore-patterns ".*_pb2.pyi?$$"
|
||||
|
||||
ci-mypy:
|
||||
poetry run mypy meshtastic/
|
||||
|
||||
ci-test:
|
||||
poetry run pytest --cov=meshtastic --cov-report=xml
|
||||
|
||||
ci: ci-pylint ci-mypy ci-test
|
||||
|
||||
# Makefile hack to get the examples to always run
|
||||
FORCE: ;
|
||||
|
||||
35
README.md
35
README.md
@@ -1,4 +1,10 @@
|
||||
# Meshtastic Python
|
||||
<div align="center" markdown="1">
|
||||
|
||||
<img src=".github/meshtastic_logo.png" alt="Meshtastic Logo" width="80"/>
|
||||
|
||||
<h1 align="center"> Meshtastic Python
|
||||
</h1>
|
||||
<p style="font-size:15px;" align="center">A Python library and client for use with Meshtastic devices. </p>
|
||||
|
||||
[](https://codecov.io/gh/meshtastic/python)
|
||||

|
||||
@@ -7,16 +13,37 @@
|
||||
[](https://opencollective.com/meshtastic/)
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://meshtastic.org/docs/software/python/cli/installation">Getting Started Guide</a>
|
||||
-
|
||||
<a href="https://python.meshtastic.org">API Documentation</a>
|
||||
</div>
|
||||
|
||||
## Overview
|
||||
|
||||
A Python client for use with Meshtastic devices.
|
||||
This small library (and example application) provides an easy API for sending and receiving messages over mesh radios.
|
||||
It also provides access to any of the operations/data available in the device user interface or the Android application.
|
||||
Events are delivered using a publish-subscribe model, and you can subscribe to only the message types you are interested in.
|
||||
|
||||
**[Getting Started Guide](https://meshtastic.org/docs/software/python/cli/installation)**
|
||||
## Container usage
|
||||
|
||||
(Documentation/API Reference is currently offline)
|
||||
Container images are published to GHCR for this repository. The container entrypoint defaults to running `meshtastic`,
|
||||
so CLI flags can be passed directly:
|
||||
|
||||
```bash
|
||||
docker run --rm ghcr.io/meshtastic/python --help
|
||||
```
|
||||
|
||||
To run another command, pass it explicitly (for example, a shell):
|
||||
|
||||
```bash
|
||||
docker run --rm -it --entrypoint /bin/sh ghcr.io/meshtastic/python
|
||||
```
|
||||
|
||||
The container runs as a non-root user by default. When talking to local hardware, pass the serial device through
|
||||
explicitly (for example `--device /dev/ttyUSB0:/dev/ttyUSB0`) and ensure host device permissions allow access.
|
||||
|
||||
## Call for Contributors
|
||||
|
||||
|
||||
24
bin/container-entrypoint.sh
Executable file
24
bin/container-entrypoint.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Copyright (C) 2025 Olliver Schinagl <oliver@schinagl.nl>
|
||||
#
|
||||
# A beginning user should be able to docker run image bash (or sh) without
|
||||
# needing to learn about --entrypoint
|
||||
# https://github.com/docker-library/official-images#consistency
|
||||
|
||||
set -eu
|
||||
|
||||
bin='meshtastic'
|
||||
|
||||
# run command if it is not starting with a "-" and is an executable in PATH
|
||||
if [ "${#}" -le 0 ] || \
|
||||
[ "${1#-}" != "${1}" ] || \
|
||||
[ -d "${1}" ] || \
|
||||
! command -v "${1}" > '/dev/null' 2>&1; then
|
||||
entrypoint='true'
|
||||
fi
|
||||
|
||||
exec ${entrypoint:+${bin:?}} "${@}"
|
||||
|
||||
exit 0
|
||||
332
bin/inject_nanopb_options.py
Normal file
332
bin/inject_nanopb_options.py
Normal file
@@ -0,0 +1,332 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Inject nanopb .options constraints as inline field options into a .proto file.
|
||||
|
||||
The nanopb .options file format is specific to the nanopb C generator and is
|
||||
ignored by standard protoc (including --python_out). By injecting the options
|
||||
directly into the proto file's field declarations, protoc will embed them in
|
||||
the serialized descriptor, making them accessible in Python via:
|
||||
|
||||
from meshtastic.protobuf import mesh_pb2, nanopb_pb2
|
||||
field = mesh_pb2.DESCRIPTOR.message_types_by_name['User'].fields_by_name['long_name']
|
||||
opts = field.GetOptions().Extensions[nanopb_pb2.nanopb]
|
||||
print(opts.max_size) # 40
|
||||
|
||||
Usage:
|
||||
inject_nanopb_options.py <options_file> <proto_file>
|
||||
|
||||
The proto_file is modified in-place. Intended to operate on temporary copies
|
||||
generated by regen-protobufs.sh, not the source .proto files.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
# IntSize enum values from nanopb.proto
|
||||
INT_SIZE_ENUM = {8: "IS_8", 16: "IS_16", 32: "IS_32", 64: "IS_64"}
|
||||
|
||||
# Options that are valid proto FieldOptions and useful outside of C code generation.
|
||||
# We skip C-only options (anonymous_oneof, no_unions, skip_message, packed_struct,
|
||||
# packed_enum, mangle_names, callback_datatype, callback_function, descriptorsize,
|
||||
# type_override) since they either don't apply to proto fields or are C-specific.
|
||||
FIELD_OPTIONS = frozenset(
|
||||
{
|
||||
"max_size",
|
||||
"max_length",
|
||||
"max_count",
|
||||
"int_size",
|
||||
"fixed_length",
|
||||
"fixed_count",
|
||||
"long_names",
|
||||
"proto3",
|
||||
"default_has",
|
||||
"sort_by_tag",
|
||||
"msgid",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def parse_value(s: str) -> Any:
|
||||
"""Convert an option value string to an appropriate Python type."""
|
||||
if re.fullmatch(r"-?[0-9]+", s):
|
||||
return int(s)
|
||||
if s.lower() == "true":
|
||||
return True
|
||||
if s.lower() == "false":
|
||||
return False
|
||||
return s
|
||||
|
||||
|
||||
def parse_options_file(
|
||||
path: Path,
|
||||
) -> Tuple[Dict[Tuple[str, ...], Dict[str, Any]], Dict[str, Dict[str, Any]]]:
|
||||
"""Parse a nanopb .options file.
|
||||
|
||||
Returns:
|
||||
specific: maps (message_path..., field_name) -> {option: value}
|
||||
e.g. ('User', 'long_name') or ('Route', 'Link', 'uid')
|
||||
wildcard: maps field_name -> {option: value}
|
||||
applies to any field with this name in the same proto file
|
||||
"""
|
||||
specific: Dict[Tuple[str, ...], Dict[str, Any]] = {}
|
||||
wildcard: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
with open(path, encoding="utf-8") as f:
|
||||
for line in f:
|
||||
# Strip inline comments
|
||||
comment_pos = line.find("#")
|
||||
if comment_pos >= 0:
|
||||
line = line[:comment_pos]
|
||||
line = line.strip().lstrip("*").strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
tokens = line.split()
|
||||
if len(tokens) < 2:
|
||||
continue
|
||||
|
||||
pattern = tokens[0]
|
||||
opts: Dict[str, Any] = {}
|
||||
for tok in tokens[1:]:
|
||||
if ":" in tok:
|
||||
k, v = tok.split(":", 1)
|
||||
if k in FIELD_OPTIONS:
|
||||
opts[k] = parse_value(v)
|
||||
|
||||
if not opts:
|
||||
continue
|
||||
|
||||
if "." in pattern:
|
||||
# e.g. "User.long_name" -> key=('User', 'long_name')
|
||||
# or "Route.Link.uid" -> key=('Route', 'Link', 'uid')
|
||||
parts = tuple(pattern.split("."))
|
||||
if parts in specific:
|
||||
specific[parts].update(opts)
|
||||
else:
|
||||
specific[parts] = opts
|
||||
else:
|
||||
# wildcard: applies to any field with this name
|
||||
if pattern in wildcard:
|
||||
wildcard[pattern].update(opts)
|
||||
else:
|
||||
wildcard[pattern] = opts
|
||||
|
||||
return specific, wildcard
|
||||
|
||||
|
||||
def format_nanopb_opts(opts: Dict[str, Any]) -> str:
|
||||
"""Format a dict of nanopb options as a proto field options annotation string."""
|
||||
parts = []
|
||||
for k, v in opts.items():
|
||||
if k == "int_size":
|
||||
enum_val = INT_SIZE_ENUM.get(v, f"IS_{v}")
|
||||
parts.append(f"(nanopb).int_size = {enum_val}")
|
||||
elif isinstance(v, bool):
|
||||
parts.append(f"(nanopb).{k} = {'true' if v else 'false'}")
|
||||
else:
|
||||
parts.append(f"(nanopb).{k} = {v}")
|
||||
return ", ".join(parts)
|
||||
|
||||
|
||||
def message_path_matches(
|
||||
context_stack: List[Tuple[str, str]], msg_path: Tuple[str, ...]
|
||||
) -> bool:
|
||||
"""Check if the current message context ends with msg_path.
|
||||
|
||||
context_stack entries are ('message'|'oneof'|'enum', name).
|
||||
msg_path is the tuple of message names from the options pattern,
|
||||
e.g. ('User',) or ('Route', 'Link').
|
||||
"""
|
||||
msg_names = [name for kind, name in context_stack if kind == "message"]
|
||||
n = len(msg_path)
|
||||
return len(msg_names) >= n and tuple(msg_names[-n:]) == msg_path
|
||||
|
||||
|
||||
def inject_into_proto(
|
||||
content: str,
|
||||
specific: Dict[Tuple[str, ...], Dict[str, Any]],
|
||||
wildcard: Dict[str, Dict[str, Any]],
|
||||
nanopb_import_path: str,
|
||||
) -> str:
|
||||
"""Inject nanopb field options into a proto file's text content.
|
||||
|
||||
Adds an import for nanopb.proto if not already present.
|
||||
Returns the modified content.
|
||||
"""
|
||||
if not specific and not wildcard:
|
||||
return content
|
||||
|
||||
lines = content.split("\n")
|
||||
|
||||
# Check if nanopb is already imported (after sed fixup, it will be
|
||||
# 'meshtastic/protobuf/nanopb.proto')
|
||||
nanopb_already_imported = any(
|
||||
"nanopb.proto" in line
|
||||
for line in lines
|
||||
if line.strip().startswith("import")
|
||||
)
|
||||
|
||||
# Track the index of the last import line so we can insert after it
|
||||
last_import_idx = max(
|
||||
(
|
||||
i
|
||||
for i, line in enumerate(lines)
|
||||
if line.strip().startswith("import ") and line.strip().endswith(";")
|
||||
),
|
||||
default=-1,
|
||||
)
|
||||
|
||||
# State
|
||||
context_stack: List[Tuple[str, str]] = [] # ('message'|'oneof'|'enum', name)
|
||||
result: List[str] = []
|
||||
import_added = nanopb_already_imported
|
||||
|
||||
# Patterns for proto structural elements
|
||||
message_re = re.compile(r"^(\s*)message\s+(\w+)\s*\{")
|
||||
oneof_re = re.compile(r"^(\s*)oneof\s+(\w+)\s*\{")
|
||||
enum_re = re.compile(r"^(\s*)enum\s+(\w+)\s*\{")
|
||||
close_re = re.compile(r"^\s*\}")
|
||||
|
||||
# Pattern for field declarations:
|
||||
# indent [optional|repeated] type name = number [options] ;
|
||||
# We exclude map<> fields (different syntax, nanopb handles them differently)
|
||||
# and enum value lines (no type keyword before the name).
|
||||
field_re = re.compile(
|
||||
r"^(\s*)" # (1) indent
|
||||
r"(optional\s+|repeated\s+)?" # (2) optional qualifier
|
||||
r"([\w.]+)\s+" # (3) field type (possibly qualified like google.protobuf.Any)
|
||||
r"(\w+)\s*" # (4) field name
|
||||
r"=\s*(\d+)" # (5) field number
|
||||
r"(?:\s*\[([^\]]*)\])?" # (6) existing options, without brackets
|
||||
r"\s*;" # trailing semicolon
|
||||
)
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
# Insert nanopb import right after the last existing import line.
|
||||
# Only do this when there IS an existing import (last_import_idx >= 0);
|
||||
# if there are no imports we fall through to the syntax-line fallback below.
|
||||
if not import_added and last_import_idx >= 0 and i == last_import_idx + 1:
|
||||
result.append(f'import "{nanopb_import_path}";')
|
||||
import_added = True
|
||||
|
||||
# --- Track message/oneof/enum nesting ---
|
||||
m = message_re.match(line)
|
||||
if m:
|
||||
context_stack.append(("message", m.group(2)))
|
||||
result.append(line)
|
||||
continue
|
||||
|
||||
m = oneof_re.match(line)
|
||||
if m:
|
||||
context_stack.append(("oneof", m.group(2)))
|
||||
result.append(line)
|
||||
continue
|
||||
|
||||
m = enum_re.match(line)
|
||||
if m:
|
||||
context_stack.append(("enum", m.group(2)))
|
||||
result.append(line)
|
||||
continue
|
||||
|
||||
if close_re.match(line) and context_stack:
|
||||
context_stack.pop()
|
||||
result.append(line)
|
||||
continue
|
||||
|
||||
# Skip field injection inside enum bodies (enum values look like fields
|
||||
# but should not have nanopb options added)
|
||||
in_enum = bool(context_stack) and context_stack[-1][0] == "enum"
|
||||
|
||||
# --- Try to match and modify a field declaration ---
|
||||
m = field_re.match(line)
|
||||
if m and not in_enum:
|
||||
indent = m.group(1)
|
||||
qualifier = m.group(2) or ""
|
||||
ftype = m.group(3)
|
||||
fname = m.group(4)
|
||||
fnum = m.group(5)
|
||||
existing_opts = m.group(6) or ""
|
||||
|
||||
# Collect applicable nanopb options (wildcard < specific)
|
||||
extra: Dict[str, Any] = {}
|
||||
|
||||
# 1. Wildcard: any field with this name in this proto file
|
||||
if fname in wildcard:
|
||||
extra.update(wildcard[fname])
|
||||
|
||||
# 2. Specific: check all keys whose last element is fname and whose
|
||||
# preceding path matches the current message context
|
||||
for key, opts in specific.items():
|
||||
if key[-1] == fname:
|
||||
msg_path = key[:-1]
|
||||
if message_path_matches(context_stack, msg_path):
|
||||
extra.update(opts)
|
||||
break
|
||||
|
||||
if extra:
|
||||
nanopb_str = format_nanopb_opts(extra)
|
||||
if existing_opts.strip():
|
||||
opts_block = f"[{existing_opts}, {nanopb_str}]"
|
||||
else:
|
||||
opts_block = f"[{nanopb_str}]"
|
||||
qual = qualifier.rstrip()
|
||||
sep = " " if qual else ""
|
||||
line = f"{indent}{qual}{sep}{ftype} {fname} = {fnum} {opts_block};"
|
||||
|
||||
result.append(line)
|
||||
|
||||
# Edge case: if there were no import lines, add nanopb import after syntax line
|
||||
if not import_added:
|
||||
for i, line in enumerate(result):
|
||||
if line.strip().startswith("syntax") and line.strip().endswith(";"):
|
||||
result.insert(i + 1, f'import "{nanopb_import_path}";')
|
||||
break
|
||||
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""Parse an .options file and inject its constraints into a .proto file in-place."""
|
||||
if len(sys.argv) != 3:
|
||||
print(
|
||||
f"Usage: {sys.argv[0]} <options_file> <proto_file>",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
opts_path = Path(sys.argv[1])
|
||||
proto_path = Path(sys.argv[2])
|
||||
|
||||
if not opts_path.exists():
|
||||
print(f"Options file not found: {opts_path}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if not proto_path.exists():
|
||||
print(f"Proto file not found: {proto_path}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
specific, wildcard = parse_options_file(opts_path)
|
||||
total = len(specific) + len(wildcard)
|
||||
|
||||
if total == 0:
|
||||
print(f" [{opts_path.name}] No injectable options found, skipping.")
|
||||
return 0
|
||||
|
||||
content = proto_path.read_text(encoding="utf-8")
|
||||
|
||||
# After regen-protobufs.sh's sed fixup, the nanopb import path is:
|
||||
nanopb_import_path = "meshtastic/protobuf/nanopb.proto"
|
||||
|
||||
modified = inject_into_proto(content, specific, wildcard, nanopb_import_path)
|
||||
proto_path.write_text(modified, encoding="utf-8")
|
||||
|
||||
print(
|
||||
f" [{opts_path.name}] Injected {len(specific)} specific + "
|
||||
f"{len(wildcard)} wildcard option(s) into {proto_path.name}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -6,6 +6,12 @@ set -e
|
||||
#gsed -i 's/import "\//import ".\//g' ./protobufs/meshtastic/*
|
||||
#gsed -i 's/package meshtastic;//g' ./protobufs/meshtastic/*
|
||||
|
||||
POETRYDIR=$(poetry env info --path)
|
||||
|
||||
if [[ -z "${POETRYDIR}" ]]; then
|
||||
poetry install
|
||||
fi
|
||||
|
||||
# protoc looks for mypy plugin in the python path
|
||||
source $(poetry env info --path)/bin/activate
|
||||
|
||||
@@ -22,6 +28,8 @@ OUTDIR=${TMPDIR}/out
|
||||
PYIDIR=${TMPDIR}/out
|
||||
mkdir -p "${OUTDIR}" "${INDIR}" "${PYIDIR}"
|
||||
cp ./protobufs/meshtastic/*.proto "${INDIR}"
|
||||
cp ./protobufs/meshtastic/*.options "${INDIR}"
|
||||
cp ./protobufs/nanopb.proto "${INDIR}"
|
||||
|
||||
# OS-X sed is apparently a little different and expects an arg for -i
|
||||
if [[ $OSTYPE == 'darwin'* ]]; then
|
||||
@@ -36,6 +44,21 @@ $SEDCMD 's/^package meshtastic;/package meshtastic.protobuf;/' "${INDIR}/"*.prot
|
||||
# fix the imports to match
|
||||
$SEDCMD 's/^import "meshtastic\//import "meshtastic\/protobuf\//' "${INDIR}/"*.proto
|
||||
|
||||
$SEDCMD 's/^import "nanopb.proto"/import "meshtastic\/protobuf\/nanopb.proto"/' "${INDIR}/"*.proto
|
||||
|
||||
# Inject nanopb .options constraints as inline proto field options so that
|
||||
# protoc --python_out embeds them in the generated descriptors. Python code
|
||||
# can then read them via:
|
||||
# field.GetOptions().Extensions[nanopb_pb2.nanopb].max_size
|
||||
echo "Injecting nanopb options into proto files..."
|
||||
for OPTS_FILE in "${INDIR}"/*.options; do
|
||||
BASENAME=$(basename "${OPTS_FILE}" .options)
|
||||
PROTO_FILE="${INDIR}/${BASENAME}.proto"
|
||||
if [ -f "${PROTO_FILE}" ]; then
|
||||
python3 ./bin/inject_nanopb_options.py "${OPTS_FILE}" "${PROTO_FILE}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Generate the python files
|
||||
./nanopb-0.4.8/generator-bin/protoc -I=$TMPDIR/in --python_out "${OUTDIR}" "--mypy_out=${PYIDIR}" $INDIR/*.proto
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ owner_short: BOB
|
||||
|
||||
channel_url: https://www.meshtastic.org/e/#CgMSAQESCDgBQANIAVAe
|
||||
|
||||
canned_messages: Hi|Bye|Yes|No|Ok
|
||||
ringtone: 24:d=32,o=5,b=565:f6,p,f6,4p,p,f6,p,f6,2p,p,b6,p,b6,p,b6,p,b6,p,b,p,b,p,b,p,b,p,b,p,b,p,b,p,b,1p.,2p.,p
|
||||
|
||||
location:
|
||||
lat: 35.88888
|
||||
lon: -93.88888
|
||||
|
||||
70
examples/CONTRIBUTING.md
Normal file
70
examples/CONTRIBUTING.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Contributing Example Scripts
|
||||
|
||||
Use this guide when adding or updating scripts in `examples/`.
|
||||
|
||||
## Must-have checklist before opening a PR
|
||||
|
||||
1. Script teaches one clear thing (its primary learning goal).
|
||||
2. File name matches that goal.
|
||||
3. Top docstring states purpose, transport scope, behavior, and expected output.
|
||||
4. Script has safe shutdown (`with ...` or `finally`) and graceful `KeyboardInterrupt` handling.
|
||||
5. Argument handling is clear (`argparse` preferred).
|
||||
6. Errors are explicit (no bare `except:`).
|
||||
7. The script is not a near-duplicate of an existing example.
|
||||
|
||||
## Choose the right teaching goal
|
||||
|
||||
Each example should have one primary lesson. Keep it focused.
|
||||
|
||||
- Good: "Send one text message over serial."
|
||||
- Good: "Print inbound text messages."
|
||||
- Avoid: discovery + chat + config mutation all in one script unless that combined flow is the lesson.
|
||||
|
||||
## Transport scope must be explicit
|
||||
|
||||
State exactly what transports are supported and why.
|
||||
|
||||
- Serial-only when that keeps the example simplest.
|
||||
- Multi-transport (Serial/TCP/BLE) only when transport selection is part of the lesson.
|
||||
- If TCP/BLE are supported, expose explicit flags (`--host`, `--ble`) and document defaults.
|
||||
|
||||
## Behavior and output should be predictable
|
||||
|
||||
Readers should know if the script sends, receives, mutates config, or combines those.
|
||||
|
||||
- Receive examples: subscribe to the narrowest pubsub topic that matches the lesson.
|
||||
- Send examples: clarify destination behavior (broadcast default vs explicit destination).
|
||||
- Mutation examples: clearly document side effects.
|
||||
|
||||
Output should make success easy to confirm:
|
||||
|
||||
- Print concise, stable status/event lines.
|
||||
- Avoid noisy debug output unless the script is specifically diagnostic-focused.
|
||||
|
||||
## Cleanup and error handling
|
||||
|
||||
- Use context managers where practical; otherwise close interfaces in `finally`.
|
||||
- Handle `KeyboardInterrupt` cleanly.
|
||||
- Exit non-zero for invalid args, connection/setup failures, or command failures.
|
||||
|
||||
## Naming guidance
|
||||
|
||||
Use descriptive names tied to the teaching goal.
|
||||
|
||||
- Prefer names like `tcp_connection_info_once.py` over `pub_sub_example.py`.
|
||||
- Prefer names like `tcp_pubsub_send_and_receive.py` over `pub_sub_example2.py`.
|
||||
- Avoid generic names such as `example2.py`.
|
||||
|
||||
Keep existing filenames only when compatibility or discoverability outweighs clarity.
|
||||
|
||||
## New script vs extending an existing one
|
||||
|
||||
Create a new script when:
|
||||
|
||||
- The learning goal is genuinely distinct.
|
||||
- Combining behaviors would make either example harder to understand.
|
||||
|
||||
Extend an existing script when:
|
||||
|
||||
- The change deepens the same lesson.
|
||||
- The resulting script remains focused and readable.
|
||||
@@ -1,21 +1,42 @@
|
||||
"""Simple program to demo how to use meshtastic library.
|
||||
To run: python examples/get_hw.py
|
||||
"""Print the local node hardware model.
|
||||
|
||||
Purpose: show the narrowest read-only local hardware lookup.
|
||||
Transport scope: Serial only.
|
||||
Behavior: reads local node metadata and prints hwModel.
|
||||
Expected output: one hardware model line, if available.
|
||||
Cleanup/error handling: exits with code 3 for bad args and closes interface on exit.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
import meshtastic
|
||||
import meshtastic.serial_interface
|
||||
|
||||
# simple arg check
|
||||
if len(sys.argv) != 1:
|
||||
print(f"usage: {sys.argv[0]}")
|
||||
print("Print the hardware model for the local node.")
|
||||
sys.exit(3)
|
||||
|
||||
iface = meshtastic.serial_interface.SerialInterface()
|
||||
if iface.nodes:
|
||||
for n in iface.nodes.values():
|
||||
if n["num"] == iface.myInfo.my_node_num:
|
||||
print(n["user"]["hwModel"])
|
||||
iface.close()
|
||||
def main() -> int:
|
||||
"""Print the hardware model for the local node."""
|
||||
if len(sys.argv) != 1:
|
||||
print(f"usage: {sys.argv[0]}")
|
||||
print("Print the hardware model for the local node.")
|
||||
return 3
|
||||
|
||||
parser = argparse.ArgumentParser(description="Print local Meshtastic hardware model")
|
||||
parser.parse_args()
|
||||
|
||||
try:
|
||||
with meshtastic.serial_interface.SerialInterface() as iface:
|
||||
if iface.nodes:
|
||||
for node in iface.nodes.values():
|
||||
if node["num"] == iface.myInfo.my_node_num:
|
||||
print(node["user"]["hwModel"])
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
return 0
|
||||
except Exception as exc:
|
||||
print(f"Error: Could not read hardware model: {exc}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
@@ -1,19 +1,38 @@
|
||||
"""Simple program to demo how to use meshtastic library.
|
||||
To run: python examples/hello_world_serial.py
|
||||
"""Send one text message over serial.
|
||||
|
||||
Purpose: minimal send-only example.
|
||||
Transport scope: Serial only.
|
||||
Behavior: sends one message and exits.
|
||||
Expected output: no output on success.
|
||||
Cleanup/error handling: exits with code 3 for bad args, closes interface on exit.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
import meshtastic
|
||||
import meshtastic.serial_interface
|
||||
|
||||
# simple arg check
|
||||
if len(sys.argv) < 2:
|
||||
print(f"usage: {sys.argv[0]} message")
|
||||
sys.exit(3)
|
||||
|
||||
# By default will try to find a meshtastic device,
|
||||
# otherwise provide a device path like /dev/ttyUSB0
|
||||
iface = meshtastic.serial_interface.SerialInterface()
|
||||
iface.sendText(sys.argv[1])
|
||||
iface.close()
|
||||
def main() -> int:
|
||||
"""Parse arguments and send one text message."""
|
||||
if len(sys.argv) < 2:
|
||||
print(f"usage: {sys.argv[0]} message")
|
||||
return 3
|
||||
|
||||
parser = argparse.ArgumentParser(description="Send one Meshtastic text message over serial")
|
||||
parser.add_argument("message", help="Message text to broadcast")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
with meshtastic.serial_interface.SerialInterface() as iface:
|
||||
iface.sendText(args.message)
|
||||
except KeyboardInterrupt:
|
||||
return 0
|
||||
except Exception as exc:
|
||||
print(f"Error: Could not send message: {exc}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
@@ -1,20 +1,46 @@
|
||||
"""Simple program to demo how to use meshtastic library.
|
||||
To run: python examples/info.py
|
||||
"""Show a concise local node summary over serial.
|
||||
|
||||
Purpose: read local node identity and metadata in one place.
|
||||
Transport scope: Serial only.
|
||||
Behavior: reads node database, prints local node ID/name/hardware model.
|
||||
Expected output: 1-3 summary lines describing the local node.
|
||||
Cleanup/error handling: closes interface on exit and prints clear errors on failure.
|
||||
"""
|
||||
|
||||
import meshtastic
|
||||
import meshtastic.serial_interface
|
||||
|
||||
iface = meshtastic.serial_interface.SerialInterface()
|
||||
|
||||
# call showInfo() just to ensure values are populated
|
||||
# info = iface.showInfo()
|
||||
def main() -> int:
|
||||
"""Print local node summary fields."""
|
||||
try:
|
||||
with meshtastic.serial_interface.SerialInterface() as iface:
|
||||
local_num = iface.myInfo.my_node_num
|
||||
local_node = None
|
||||
if iface.nodes:
|
||||
for node in iface.nodes.values():
|
||||
if node["num"] == local_num:
|
||||
local_node = node
|
||||
break
|
||||
|
||||
if not local_node:
|
||||
print(f"Local node not found in node database (node num: {local_num}).")
|
||||
return 1
|
||||
|
||||
user = local_node.get("user", {})
|
||||
print(f"Node number: {local_num}")
|
||||
print(f"Node ID: {local_node.get('id', 'unknown')}")
|
||||
print(
|
||||
"Name: "
|
||||
f"{user.get('longName', 'unknown')} ({user.get('shortName', 'unknown')})"
|
||||
)
|
||||
print(f"Hardware model: {user.get('hwModel', 'unknown')}")
|
||||
except KeyboardInterrupt:
|
||||
return 0
|
||||
except Exception as exc:
|
||||
print(f"Error: Could not read local node summary: {exc}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if iface.nodes:
|
||||
for n in iface.nodes.values():
|
||||
if n["num"] == iface.myInfo.my_node_num:
|
||||
print(n["user"]["hwModel"])
|
||||
break
|
||||
|
||||
iface.close()
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
64
examples/meshtastic_serial_message_reader.py
Normal file
64
examples/meshtastic_serial_message_reader.py
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Passively monitor incoming text messages over serial.
|
||||
|
||||
Purpose: receive-only monitor for text messages.
|
||||
Transport scope: Serial only.
|
||||
Behavior: subscribes to text receive events and prints timestamp/channel/sender/text.
|
||||
Expected output: one line per received text message.
|
||||
Cleanup/error handling: graceful Ctrl+C exit and explicit connection errors.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional
|
||||
|
||||
from pubsub import pub
|
||||
import meshtastic.serial_interface
|
||||
|
||||
_TZ_NAME = time.tzname[time.localtime().tm_isdst > 0]
|
||||
|
||||
|
||||
def on_receive(packet: dict[str, Any], interface: Any) -> None: # pylint: disable=unused-argument
|
||||
"""Print a compact line for each received text packet."""
|
||||
decoded = packet.get("decoded", {})
|
||||
if decoded.get("portnum") != "TEXT_MESSAGE_APP":
|
||||
return
|
||||
|
||||
message = decoded.get("text")
|
||||
if not message:
|
||||
return
|
||||
|
||||
channel_num = packet.get("channel", 0)
|
||||
sender_id = packet.get("fromId", "unknown")
|
||||
message_time = datetime.now().strftime(f"%a %b %d %Y %H:%M:%S {_TZ_NAME}")
|
||||
print(f"{message_time} : {channel_num} : {sender_id} : {message}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""Connect over serial and print inbound text messages."""
|
||||
parser = argparse.ArgumentParser(description="Read incoming Meshtastic text over serial")
|
||||
parser.add_argument("--port", default=None, help="Serial port path (default: auto-detect)")
|
||||
args = parser.parse_args()
|
||||
|
||||
pub.subscribe(on_receive, "meshtastic.receive")
|
||||
|
||||
iface: Optional[meshtastic.serial_interface.SerialInterface] = None
|
||||
try:
|
||||
iface = meshtastic.serial_interface.SerialInterface(devPath=args.port)
|
||||
print("Connected. Listening for text messages. Press Ctrl+C to exit.")
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
return 0
|
||||
except Exception as exc:
|
||||
print(f"Error: Could not monitor serial messages: {exc}")
|
||||
return 1
|
||||
finally:
|
||||
if iface:
|
||||
iface.close()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,30 +0,0 @@
|
||||
"""Simple program to demo how to use meshtastic library.
|
||||
To run: python examples/pub_sub_example.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from pubsub import pub
|
||||
|
||||
import meshtastic
|
||||
import meshtastic.tcp_interface
|
||||
|
||||
# simple arg check
|
||||
if len(sys.argv) < 2:
|
||||
print(f"usage: {sys.argv[0]} host")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=unused-argument
|
||||
"""This is called when we (re)connect to the radio."""
|
||||
print(interface.myInfo)
|
||||
interface.close()
|
||||
|
||||
|
||||
pub.subscribe(onConnection, "meshtastic.connection.established")
|
||||
|
||||
try:
|
||||
iface = meshtastic.tcp_interface.TCPInterface(sys.argv[1])
|
||||
except:
|
||||
print(f"Error: Could not connect to {sys.argv[1]}")
|
||||
sys.exit(1)
|
||||
@@ -1,39 +0,0 @@
|
||||
"""Simple program to demo how to use meshtastic library.
|
||||
To run: python examples/pub_sub_example2.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
from pubsub import pub
|
||||
|
||||
import meshtastic
|
||||
import meshtastic.tcp_interface
|
||||
|
||||
# simple arg check
|
||||
if len(sys.argv) < 2:
|
||||
print(f"usage: {sys.argv[0]} host")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def onReceive(packet, interface): # pylint: disable=unused-argument
|
||||
"""called when a packet arrives"""
|
||||
print(f"Received: {packet}")
|
||||
|
||||
|
||||
def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=unused-argument
|
||||
"""called when we (re)connect to the radio"""
|
||||
# defaults to broadcast, specify a destination ID if you wish
|
||||
interface.sendText("hello mesh")
|
||||
|
||||
|
||||
pub.subscribe(onReceive, "meshtastic.receive")
|
||||
pub.subscribe(onConnection, "meshtastic.connection.established")
|
||||
try:
|
||||
iface = meshtastic.tcp_interface.TCPInterface(hostname=sys.argv[1])
|
||||
while True:
|
||||
time.sleep(1000)
|
||||
iface.close()
|
||||
except Exception as ex:
|
||||
print(f"Error: Could not connect to {sys.argv[1]} {ex}")
|
||||
sys.exit(1)
|
||||
73
examples/replymessage.py
Normal file
73
examples/replymessage.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""Auto-reply to received text messages.
|
||||
|
||||
Purpose: demonstrate receive callback + generated reply flow.
|
||||
Transport scope: Serial default, optional TCP/BLE.
|
||||
Behavior: listens for text, prints message metadata, sends one reply per text message.
|
||||
Expected output: "Connected..." plus message/reply lines while running.
|
||||
Cleanup/error handling: clear connect failures and graceful Ctrl+C close.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import time
|
||||
from typing import Any, Optional, Union
|
||||
from pubsub import pub
|
||||
import meshtastic.serial_interface
|
||||
import meshtastic.tcp_interface
|
||||
import meshtastic.ble_interface
|
||||
from meshtastic.mesh_interface import MeshInterface
|
||||
|
||||
def onReceive(packet: dict, interface: MeshInterface) -> None:
|
||||
"""Reply to every received packet with some info"""
|
||||
text: Optional[str] = packet.get("decoded", {}).get("text")
|
||||
if text:
|
||||
rx_snr: Any = packet.get("rxSnr", "unknown")
|
||||
hop_limit: Any = packet.get("hopLimit", "unknown")
|
||||
print(f"message: {text}")
|
||||
reply: str = f"got msg '{text}' with rxSnr: {rx_snr} and hopLimit: {hop_limit}"
|
||||
print("Sending reply: ", reply)
|
||||
interface.sendText(reply)
|
||||
|
||||
def onConnection(interface: MeshInterface, topic: Any = pub.AUTO_TOPIC) -> None: # pylint: disable=unused-argument
|
||||
"""called when we (re)connect to the radio"""
|
||||
print("Connected. Will auto-reply to all messages while running.")
|
||||
|
||||
parser = argparse.ArgumentParser(description="Meshtastic Auto-Reply Feature Demo")
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument("--host", help="Connect via TCP to this hostname or IP")
|
||||
group.add_argument("--ble", help="Connect via BLE to this MAC address")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
pub.subscribe(onReceive, "meshtastic.receive")
|
||||
pub.subscribe(onConnection, "meshtastic.connection.established")
|
||||
|
||||
iface: Optional[Union[
|
||||
meshtastic.tcp_interface.TCPInterface,
|
||||
meshtastic.ble_interface.BLEInterface,
|
||||
meshtastic.serial_interface.SerialInterface
|
||||
]] = None
|
||||
|
||||
# defaults to serial, use --host for TCP or --ble for Bluetooth
|
||||
try:
|
||||
if args.host:
|
||||
# note: timeout only applies after connection, not during the initial connect attempt
|
||||
# TCPInterface.myConnect() calls socket.create_connection() without a timeout
|
||||
iface = meshtastic.tcp_interface.TCPInterface(hostname=args.host, timeout=10)
|
||||
elif args.ble:
|
||||
iface = meshtastic.ble_interface.BLEInterface(address=args.ble, timeout=10)
|
||||
else:
|
||||
iface = meshtastic.serial_interface.SerialInterface(timeout=10)
|
||||
except KeyboardInterrupt as exc:
|
||||
raise SystemExit(0) from exc
|
||||
except Exception as e:
|
||||
print(f"Error: Could not connect. {e}")
|
||||
raise SystemExit(1) from e
|
||||
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
if iface:
|
||||
iface.close()
|
||||
@@ -1,7 +1,13 @@
|
||||
"""Program to scan for hardware
|
||||
To run: python examples/scan_for_devices.py
|
||||
"""Scan host serial hardware for supported Meshtastic devices.
|
||||
|
||||
Purpose: host-side discovery without opening a radio session.
|
||||
Transport scope: none (OS/device scanning only).
|
||||
Behavior: scans vendor IDs, lists matched devices, and candidate active ports.
|
||||
Expected output: vendor ID list, zero-or-more detected devices, and port list.
|
||||
Cleanup/error handling: exits with code 3 for bad args and code 1 on scan errors.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
from meshtastic.util import (
|
||||
@@ -10,20 +16,38 @@ from meshtastic.util import (
|
||||
get_unique_vendor_ids,
|
||||
)
|
||||
|
||||
# simple arg check
|
||||
if len(sys.argv) != 1:
|
||||
print(f"usage: {sys.argv[0]}")
|
||||
print("Detect which device we might have.")
|
||||
sys.exit(3)
|
||||
|
||||
vids = get_unique_vendor_ids()
|
||||
print(f"Searching for all devices with these vendor ids {vids}")
|
||||
def main() -> int:
|
||||
"""Run device detection and print candidate ports."""
|
||||
if len(sys.argv) != 1:
|
||||
print(f"usage: {sys.argv[0]}")
|
||||
print("Detect which device we might have.")
|
||||
return 3
|
||||
|
||||
sds = detect_supported_devices()
|
||||
if len(sds) > 0:
|
||||
print("Detected possible devices:")
|
||||
for d in sds:
|
||||
print(f" name:{d.name}{d.version} firmware:{d.for_firmware}")
|
||||
parser = argparse.ArgumentParser(description="Scan host for supported Meshtastic devices")
|
||||
parser.parse_args()
|
||||
|
||||
ports = active_ports_on_supported_devices(sds)
|
||||
print(f"ports:{ports}")
|
||||
try:
|
||||
vids = get_unique_vendor_ids()
|
||||
print(f"Searching for all devices with these vendor ids {vids}")
|
||||
|
||||
supported_devices = detect_supported_devices()
|
||||
if supported_devices:
|
||||
print("Detected possible devices:")
|
||||
for device in supported_devices:
|
||||
print(
|
||||
f" name:{device.name}{device.version} firmware:{device.for_firmware}"
|
||||
)
|
||||
else:
|
||||
print("Detected possible devices: none")
|
||||
|
||||
ports = active_ports_on_supported_devices(supported_devices)
|
||||
print(f"ports:{ports}")
|
||||
except Exception as exc:
|
||||
print(f"Error: device scan failed: {exc}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
@@ -1,21 +1,40 @@
|
||||
"""Simple program to demo how to use meshtastic library.
|
||||
To run: python examples/set_owner.py Bobby 333
|
||||
"""Set local owner long/short name over serial.
|
||||
|
||||
Purpose: demonstrate a local config mutation workflow.
|
||||
Transport scope: Serial only.
|
||||
Behavior: updates owner long name and optional short name.
|
||||
Expected output: prints the owner values being applied.
|
||||
Cleanup/error handling: exits with code 3 for bad args and closes interface on exit.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
import meshtastic
|
||||
import meshtastic.serial_interface
|
||||
|
||||
# simple arg check
|
||||
if len(sys.argv) < 2:
|
||||
print(f"usage: {sys.argv[0]} long_name [short_name]")
|
||||
sys.exit(3)
|
||||
|
||||
iface = meshtastic.serial_interface.SerialInterface()
|
||||
long_name = sys.argv[1]
|
||||
short_name = None
|
||||
if len(sys.argv) > 2:
|
||||
short_name = sys.argv[2]
|
||||
iface.localNode.setOwner(long_name, short_name)
|
||||
iface.close()
|
||||
def main() -> int:
|
||||
"""Parse args and set owner fields."""
|
||||
if len(sys.argv) < 2:
|
||||
print(f"usage: {sys.argv[0]} long_name [short_name]")
|
||||
return 3
|
||||
|
||||
parser = argparse.ArgumentParser(description="Set Meshtastic local owner information")
|
||||
parser.add_argument("long_name", help="Owner long name")
|
||||
parser.add_argument("short_name", nargs="?", default=None, help="Owner short name")
|
||||
args = parser.parse_args()
|
||||
|
||||
print(f"Setting owner long_name={args.long_name}, short_name={args.short_name}")
|
||||
try:
|
||||
with meshtastic.serial_interface.SerialInterface() as iface:
|
||||
iface.localNode.setOwner(args.long_name, args.short_name)
|
||||
except KeyboardInterrupt:
|
||||
return 0
|
||||
except Exception as exc:
|
||||
print(f"Error: Could not set owner: {exc}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
"""Simple program to show serial ports.
|
||||
"""List serial ports currently visible to Meshtastic helpers.
|
||||
|
||||
Purpose: fastest host-side serial port enumeration.
|
||||
Transport scope: none (host serial listing only).
|
||||
Behavior: prints result of `findPorts()`.
|
||||
Expected output: list-like representation of available candidate ports.
|
||||
Cleanup/error handling: exits with code 1 on unexpected scan error.
|
||||
"""
|
||||
|
||||
from meshtastic.util import findPorts
|
||||
|
||||
print(findPorts())
|
||||
|
||||
def main() -> int:
|
||||
"""Print discovered serial ports."""
|
||||
try:
|
||||
print(findPorts())
|
||||
except Exception as exc:
|
||||
print(f"Error: Could not list ports: {exc}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
41
examples/tcp_connection_info_once.py
Normal file
41
examples/tcp_connection_info_once.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""Connect over TCP, print connection info once, then exit.
|
||||
|
||||
Purpose: demonstrate pubsub connection lifecycle callback.
|
||||
Transport scope: TCP only.
|
||||
Behavior: subscribe to `meshtastic.connection.established`, print `myInfo`, then close.
|
||||
Expected output: one object/line showing local radio info after connect.
|
||||
Cleanup/error handling: explicit connect failure message and clean close on callback.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
|
||||
from pubsub import pub
|
||||
import meshtastic.tcp_interface
|
||||
|
||||
|
||||
def on_connection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=unused-argument
|
||||
"""Print local radio info when connected, then close."""
|
||||
print(interface.myInfo)
|
||||
interface.close()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""Parse args, connect, and wait for established callback."""
|
||||
parser = argparse.ArgumentParser(description="Print radio info on TCP connect and exit")
|
||||
parser.add_argument("host", help="TCP hostname or IP of the Meshtastic node")
|
||||
args = parser.parse_args()
|
||||
|
||||
pub.subscribe(on_connection, "meshtastic.connection.established")
|
||||
|
||||
try:
|
||||
meshtastic.tcp_interface.TCPInterface(args.host)
|
||||
except KeyboardInterrupt:
|
||||
return 0
|
||||
except Exception as exc:
|
||||
print(f"Error: Could not connect to {args.host}: {exc}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,14 +1,47 @@
|
||||
"""Demonstration of how to look up a radio's location via its LAN connection.
|
||||
Before running, connect your machine to the same WiFi network as the radio.
|
||||
"""
|
||||
"""Look up local node position over TCP.
|
||||
|
||||
Purpose: demonstrate read-only position lookup via LAN/TCP.
|
||||
Transport scope: TCP only.
|
||||
Behavior: connects, reads local node position, prints it, then exits.
|
||||
Expected output: position dict for local node.
|
||||
Cleanup/error handling: explicit connect/read failures and clean close.
|
||||
"""
|
||||
# pylint: disable=duplicate-code
|
||||
|
||||
import argparse
|
||||
|
||||
import meshtastic
|
||||
import meshtastic.tcp_interface
|
||||
|
||||
radio_hostname = "meshtastic.local" # Can also be an IP
|
||||
iface = meshtastic.tcp_interface.TCPInterface(radio_hostname)
|
||||
my_node_num = iface.myInfo.my_node_num
|
||||
pos = iface.nodesByNum[my_node_num]["position"]
|
||||
print(pos)
|
||||
|
||||
iface.close()
|
||||
def main() -> int:
|
||||
"""Connect over TCP and print local node position."""
|
||||
parser = argparse.ArgumentParser(description="Print local node position over TCP")
|
||||
parser.add_argument(
|
||||
"--host",
|
||||
default="meshtastic.local",
|
||||
help="TCP hostname or IP (default: meshtastic.local)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
iface = None
|
||||
try:
|
||||
iface = meshtastic.tcp_interface.TCPInterface(args.host)
|
||||
my_node_num = iface.myInfo.my_node_num
|
||||
pos = iface.nodesByNum[my_node_num].get("position")
|
||||
if pos is None:
|
||||
print(f"No position available for local node {my_node_num}")
|
||||
return 1
|
||||
print(pos)
|
||||
except KeyboardInterrupt:
|
||||
return 0
|
||||
except Exception as exc:
|
||||
print(f"Error: Could not read position from {args.host}: {exc}")
|
||||
return 1
|
||||
finally:
|
||||
if iface:
|
||||
iface.close()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
54
examples/tcp_pubsub_send_and_receive.py
Normal file
54
examples/tcp_pubsub_send_and_receive.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""Send once on connect and print received packets over TCP.
|
||||
|
||||
Purpose: demonstrate pubsub send-on-connect plus receive callback flow.
|
||||
Transport scope: TCP only.
|
||||
Behavior: sends "hello mesh" at connect, prints packets while running.
|
||||
Expected output: "Connected..." plus "Received: ..." lines for inbound packets.
|
||||
Cleanup/error handling: graceful Ctrl+C exit and clean interface close.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import time
|
||||
|
||||
from pubsub import pub
|
||||
from meshtastic.tcp_interface import TCPInterface
|
||||
|
||||
|
||||
def on_receive(packet, interface): # pylint: disable=unused-argument
|
||||
"""Print each inbound packet."""
|
||||
print(f"Received: {packet}")
|
||||
|
||||
|
||||
def on_connection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=unused-argument
|
||||
"""Send a broadcast text when connected."""
|
||||
print("Connected. Sending one broadcast message.")
|
||||
interface.sendText("hello mesh")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""Parse args, connect via TCP, and run callbacks."""
|
||||
parser = argparse.ArgumentParser(description="TCP pubsub send-and-receive example")
|
||||
parser.add_argument("host", help="TCP hostname or IP of the Meshtastic node")
|
||||
args = parser.parse_args()
|
||||
|
||||
pub.subscribe(on_receive, "meshtastic.receive")
|
||||
pub.subscribe(on_connection, "meshtastic.connection.established")
|
||||
|
||||
iface = None
|
||||
try:
|
||||
iface = TCPInterface(hostname=args.host)
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
return 0
|
||||
except Exception as exc:
|
||||
print(f"Error: Could not connect to {args.host}: {exc}")
|
||||
return 1
|
||||
finally:
|
||||
if iface:
|
||||
iface.close()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
73
examples/textchat.py
Normal file
73
examples/textchat.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""Interactive text chat demo.
|
||||
|
||||
Purpose: demonstrate bidirectional text chat loop.
|
||||
Transport scope: Serial default, optional TCP/BLE.
|
||||
Behavior: prints incoming messages and sends each typed line as text.
|
||||
Expected output: incoming sender/text lines and sent messages reaching peers.
|
||||
Cleanup/error handling: explicit connect errors and graceful Ctrl+C / EOF close.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
from typing import Any, Optional, Union
|
||||
from pubsub import pub
|
||||
import meshtastic.serial_interface
|
||||
import meshtastic.tcp_interface
|
||||
import meshtastic.ble_interface
|
||||
from meshtastic.mesh_interface import MeshInterface
|
||||
|
||||
def onReceive(packet: dict, interface: MeshInterface) -> None: # pylint: disable=unused-argument
|
||||
"""called when a packet arrives"""
|
||||
text: Optional[str] = packet.get("decoded", {}).get("text")
|
||||
if text:
|
||||
sender: str = packet.get("fromId", "unknown")
|
||||
print(f"{sender}: {text}")
|
||||
|
||||
def onConnection(interface: MeshInterface, topic: Any = pub.AUTO_TOPIC) -> None: # pylint: disable=unused-argument
|
||||
"""called when we (re)connect to the radio"""
|
||||
print("Connected. Type a message and press Enter to send. Ctrl+C to exit.")
|
||||
|
||||
parser = argparse.ArgumentParser(description="Meshtastic text chat demo")
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument("--host", help="Connect via TCP to this hostname or IP")
|
||||
group.add_argument("--ble", help="Connect via BLE to this MAC address or device name")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
pub.subscribe(onReceive, "meshtastic.receive")
|
||||
pub.subscribe(onConnection, "meshtastic.connection.established")
|
||||
|
||||
iface: Optional[Union[
|
||||
meshtastic.tcp_interface.TCPInterface,
|
||||
meshtastic.ble_interface.BLEInterface,
|
||||
meshtastic.serial_interface.SerialInterface
|
||||
]] = None
|
||||
|
||||
# defaults to serial, use --host for TCP or --ble for Bluetooth
|
||||
try:
|
||||
if args.host:
|
||||
# note: timeout only applies after connection, not during the initial connect attempt
|
||||
# TCPInterface.myConnect() calls socket.create_connection() without a timeout
|
||||
iface = meshtastic.tcp_interface.TCPInterface(hostname=args.host, timeout=10)
|
||||
elif args.ble:
|
||||
iface = meshtastic.ble_interface.BLEInterface(address=args.ble, timeout=10)
|
||||
else:
|
||||
iface = meshtastic.serial_interface.SerialInterface(timeout=10)
|
||||
except KeyboardInterrupt as exc:
|
||||
raise SystemExit(0) from exc
|
||||
except Exception as e:
|
||||
print(f"Error: Could not connect. {e}")
|
||||
raise SystemExit(1) from e
|
||||
|
||||
assert iface is not None
|
||||
try:
|
||||
while True:
|
||||
line = input()
|
||||
if line:
|
||||
iface.sendText(line)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
except EOFError:
|
||||
pass
|
||||
finally:
|
||||
if iface:
|
||||
iface.close()
|
||||
60
examples/waypoint.py
Normal file
60
examples/waypoint.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""Create or delete a waypoint.
|
||||
|
||||
Purpose: demonstrate waypoint mutation API (create/delete).
|
||||
Transport scope: Serial only.
|
||||
Behavior: sends waypoint create/delete request and prints API response.
|
||||
Expected output: request response object printed to stdout.
|
||||
Cleanup/error handling: explicit argument parsing and clean interface close.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import sys
|
||||
|
||||
import meshtastic
|
||||
import meshtastic.serial_interface
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="waypoint", description="Create and delete Meshtastic waypoint"
|
||||
)
|
||||
parser.add_argument("--port", default=None)
|
||||
parser.add_argument("--debug", default=False, action="store_true")
|
||||
|
||||
subparsers = parser.add_subparsers(dest="cmd", required=True)
|
||||
parser_delete = subparsers.add_parser("delete", help="Delete a waypoint")
|
||||
parser_delete.add_argument("id", type=int, help="ID of the waypoint")
|
||||
|
||||
parser_create = subparsers.add_parser("create", help="Create a new waypoint")
|
||||
parser_create.add_argument("id", type=int, help="ID of the waypoint")
|
||||
parser_create.add_argument("name", help="Name of the waypoint")
|
||||
parser_create.add_argument("description", help="Description of the waypoint")
|
||||
parser_create.add_argument("icon", help="Icon of the waypoint")
|
||||
parser_create.add_argument(
|
||||
"expire",
|
||||
help="Expiration time as ISO timestamp accepted by datetime.fromisoformat",
|
||||
)
|
||||
parser_create.add_argument("latitude", type=float, help="Latitude of the waypoint")
|
||||
parser_create.add_argument("longitude", type=float, help="Longitude of the waypoint")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# By default will try to find a meshtastic device,
|
||||
# otherwise provide a device path like /dev/ttyUSB0
|
||||
if args.debug:
|
||||
d = sys.stderr
|
||||
else:
|
||||
d = None
|
||||
with meshtastic.serial_interface.SerialInterface(args.port, debugOut=d) as iface:
|
||||
if args.cmd == "create":
|
||||
p = iface.sendWaypoint(
|
||||
waypoint_id=args.id,
|
||||
name=args.name,
|
||||
description=args.description,
|
||||
icon=args.icon,
|
||||
expire=int(datetime.datetime.fromisoformat(args.expire).timestamp()),
|
||||
latitude=args.latitude,
|
||||
longitude=args.longitude,
|
||||
)
|
||||
else:
|
||||
p = iface.deleteWaypoint(args.id)
|
||||
print(p)
|
||||
@@ -2,41 +2,45 @@
|
||||
# A library for the Meshtastic Client API
|
||||
|
||||
Primary interfaces: SerialInterface, TCPInterface, BLEInterface
|
||||
|
||||
Install with pip: "[pip3 install meshtastic](https://pypi.org/project/meshtastic/)"
|
||||
|
||||
Source code on [github](https://github.com/meshtastic/python)
|
||||
|
||||
notable properties of interface classes:
|
||||
|
||||
- nodes - The database of received nodes. Includes always up-to-date location and username information for each
|
||||
- `nodes` - The database of received nodes. Includes always up-to-date location and username information for each
|
||||
node in the mesh. This is a read-only datastructure.
|
||||
- nodesByNum - like "nodes" but keyed by nodeNum instead of nodeId
|
||||
- myInfo & metadata - Contain read-only information about the local radio device (software version, hardware version, etc)
|
||||
- localNode - Pointer to a node object for the local node
|
||||
- `nodesByNum` - like "nodes" but keyed by nodeNum instead of nodeId. As such, includes "unknown" nodes which haven't seen a User packet yet
|
||||
- `myInfo` & `metadata` - Contain read-only information about the local radio device (software version, hardware version, etc)
|
||||
- `localNode` - Pointer to a node object for the local node
|
||||
|
||||
notable properties of nodes:
|
||||
- localConfig - Current radio settings, can be written to the radio with the `writeConfig` method.
|
||||
- moduleConfig - Current module settings, can be written to the radio with the `writeConfig` method.
|
||||
- channels - The node's channels, keyed by index.
|
||||
|
||||
- `localConfig` - Current radio settings, can be written to the radio with the `writeConfig` method.
|
||||
- `moduleConfig` - Current module settings, can be written to the radio with the `writeConfig` method.
|
||||
- `channels` - The node's channels, keyed by index.
|
||||
|
||||
# Published PubSub topics
|
||||
|
||||
We use a [publish-subscribe](https://pypubsub.readthedocs.io/en/v4.0.3/) model to communicate asynchronous events. Available
|
||||
topics:
|
||||
|
||||
- meshtastic.connection.established - published once we've successfully connected to the radio and downloaded the node DB
|
||||
- meshtastic.connection.lost - published once we've lost our link to the radio
|
||||
- meshtastic.receive.text(packet) - delivers a received packet as a dictionary, if you only care about a particular
|
||||
- `meshtastic.connection.established` - published once we've successfully connected to the radio and downloaded the node DB
|
||||
- `meshtastic.connection.lost` - published once we've lost our link to the radio
|
||||
- `meshtastic.receive.text(packet)` - delivers a received packet as a dictionary, if you only care about a particular
|
||||
type of packet, you should subscribe to the full topic name. If you want to see all packets, simply subscribe to "meshtastic.receive".
|
||||
- meshtastic.receive.position(packet)
|
||||
- meshtastic.receive.user(packet)
|
||||
- meshtastic.receive.data.portnum(packet) (where portnum is an integer or well known PortNum enum)
|
||||
- meshtastic.node.updated(node = NodeInfo) - published when a node in the DB changes (appears, location changed, username changed, etc...)
|
||||
- meshtastic.log.line(line) - a raw unparsed log line from the radio
|
||||
- `meshtastic.receive.position(packet)`
|
||||
- `meshtastic.receive.user(packet)`
|
||||
- `meshtastic.receive.data.portnum(packet)` (where portnum is an integer or well known PortNum enum)
|
||||
- `meshtastic.node.updated(node = NodeInfo)` - published when a node in the DB changes (appears, location changed, username changed, etc...)
|
||||
- `meshtastic.log.line(line)` - a raw unparsed log line from the radio
|
||||
- `meshtastic.clientNotification(notification, interface) - a ClientNotification sent from the radio
|
||||
|
||||
We receive position, user, or data packets from the mesh. You probably only care about meshtastic.receive.data. The first argument for
|
||||
that publish will be the packet. Text or binary data packets (from sendData or sendText) will both arrive this way. If you print packet
|
||||
you'll see the fields in the dictionary. decoded.data.payload will contain the raw bytes that were sent. If the packet was sent with
|
||||
sendText, decoded.data.text will **also** be populated with the decoded string. For ASCII these two strings will be the same, but for
|
||||
We receive position, user, or data packets from the mesh. You probably only care about `meshtastic.receive.data`. The first argument for
|
||||
that publish will be the packet. Text or binary data packets (from `sendData` or `sendText`) will both arrive this way. If you print packet
|
||||
you'll see the fields in the dictionary. `decoded.data.payload` will contain the raw bytes that were sent. If the packet was sent with
|
||||
`sendText`, `decoded.data.text` will **also** be populated with the decoded string. For ASCII these two strings will be the same, but for
|
||||
unicode scripts they can be different.
|
||||
|
||||
# Example Usage
|
||||
@@ -77,7 +81,6 @@ from typing import *
|
||||
|
||||
import google.protobuf.json_format
|
||||
import serial # type: ignore[import-untyped]
|
||||
from dotmap import DotMap # type: ignore[import-untyped]
|
||||
from google.protobuf.json_format import MessageToJson
|
||||
from pubsub import pub # type: ignore[import-untyped]
|
||||
from tabulate import tabulate
|
||||
@@ -108,13 +111,13 @@ from . import (
|
||||
LOCAL_ADDR = "^local"
|
||||
"""A special ID that means the local node"""
|
||||
|
||||
BROADCAST_NUM = 0xFFFFFFFF
|
||||
BROADCAST_NUM: int = 0xFFFFFFFF
|
||||
"""if using 8 bit nodenums this will be shortened on the target"""
|
||||
|
||||
BROADCAST_ADDR = "^all"
|
||||
"""A special ID that means broadcast"""
|
||||
|
||||
OUR_APP_VERSION = 20300
|
||||
OUR_APP_VERSION: int = 20300
|
||||
"""The numeric buildnumber (shared with android apps) specifying the
|
||||
level of device code we are guaranteed to understand
|
||||
|
||||
@@ -126,12 +129,15 @@ NODELESS_WANT_CONFIG_ID = 69420
|
||||
|
||||
publishingThread = DeferredExecution("publishing")
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ResponseHandler(NamedTuple):
|
||||
"""A pending response callback, waiting for a response to one of our messages"""
|
||||
|
||||
# requestId: int - used only as a key
|
||||
#: a callable to call when a response is received
|
||||
callback: Callable
|
||||
#: Whether ACKs and NAKs should be passed to this handler
|
||||
ackPermitted: bool = False
|
||||
# FIXME, add timestamp and age out old requests
|
||||
|
||||
@@ -139,11 +145,11 @@ class ResponseHandler(NamedTuple):
|
||||
class KnownProtocol(NamedTuple):
|
||||
"""Used to automatically decode known protocol payloads"""
|
||||
|
||||
#: A descriptive name (e.g. "text", "user", "admin")
|
||||
name: str
|
||||
# portnum: int, now a key
|
||||
# If set, will be called to prase as a protocol buffer
|
||||
#: If set, will be called to parse as a protocol buffer
|
||||
protobufFactory: Optional[Callable] = None
|
||||
# If set, invoked as onReceive(interface, packet)
|
||||
#: If set, invoked as onReceive(interface, packet)
|
||||
onReceive: Optional[Callable] = None
|
||||
|
||||
|
||||
@@ -155,31 +161,53 @@ def _onTextReceive(iface, asDict):
|
||||
#
|
||||
# Usually btw this problem is caused by apps sending binary data but setting the payload type to
|
||||
# text.
|
||||
logging.debug(f"in _onTextReceive() asDict:{asDict}")
|
||||
logger.debug(f"in _onTextReceive() asDict:{asDict}")
|
||||
try:
|
||||
asBytes = asDict["decoded"]["payload"]
|
||||
asDict["decoded"]["text"] = asBytes.decode("utf-8")
|
||||
except Exception as ex:
|
||||
logging.error(f"Malformatted utf8 in text message: {ex}")
|
||||
logger.error(f"Malformatted utf8 in text message: {ex}")
|
||||
_receiveInfoUpdate(iface, asDict)
|
||||
|
||||
|
||||
def _onPositionReceive(iface, asDict):
|
||||
"""Special auto parsing for received messages"""
|
||||
logging.debug(f"in _onPositionReceive() asDict:{asDict}")
|
||||
logger.debug(f"in _onPositionReceive() asDict:{asDict}")
|
||||
if "decoded" in asDict:
|
||||
if "position" in asDict["decoded"] and "from" in asDict:
|
||||
p = asDict["decoded"]["position"]
|
||||
logging.debug(f"p:{p}")
|
||||
logger.debug(f"p:{p}")
|
||||
p = iface._fixupPosition(p)
|
||||
logging.debug(f"after fixup p:{p}")
|
||||
# update node DB as needed
|
||||
iface._getOrCreateByNum(asDict["from"])["position"] = p
|
||||
logger.debug(f"after fixup p:{p}")
|
||||
# For the local node, only accept position updates with equal
|
||||
# or better precision. The local GPS is authoritative, and
|
||||
# low-precision echoes from the mesh (e.g., map reports relayed
|
||||
# by other nodes) should not overwrite it.
|
||||
# For remote nodes, always accept the latest position since
|
||||
# any update from them reflects their current state.
|
||||
node = iface._getOrCreateByNum(asDict["from"])
|
||||
is_local_node = (
|
||||
iface.myInfo is not None
|
||||
and asDict["from"] == iface.myInfo.my_node_num
|
||||
)
|
||||
if is_local_node:
|
||||
existing = node.get("position", {})
|
||||
existing_precision = existing.get("precisionBits", 0) or 0
|
||||
new_precision = p.get("precisionBits", 0) or 0
|
||||
if existing_precision == 0 or new_precision >= existing_precision:
|
||||
node["position"] = p
|
||||
else:
|
||||
logger.debug(
|
||||
f"Ignoring low-precision position echo for local node "
|
||||
f"({new_precision} < {existing_precision})"
|
||||
)
|
||||
else:
|
||||
node["position"] = p
|
||||
|
||||
|
||||
def _onNodeInfoReceive(iface, asDict):
|
||||
"""Special auto parsing for received messages"""
|
||||
logging.debug(f"in _onNodeInfoReceive() asDict:{asDict}")
|
||||
logger.debug(f"in _onNodeInfoReceive() asDict:{asDict}")
|
||||
if "decoded" in asDict:
|
||||
if "user" in asDict["decoded"] and "from" in asDict:
|
||||
p = asDict["decoded"]["user"]
|
||||
@@ -193,14 +221,32 @@ def _onNodeInfoReceive(iface, asDict):
|
||||
|
||||
def _onTelemetryReceive(iface, asDict):
|
||||
"""Automatically update device metrics on received packets"""
|
||||
logging.debug(f"in _onTelemetryReceive() asDict:{asDict}")
|
||||
deviceMetrics = asDict.get("decoded", {}).get("telemetry", {}).get("deviceMetrics")
|
||||
if "from" in asDict and deviceMetrics is not None:
|
||||
node = iface._getOrCreateByNum(asDict["from"])
|
||||
newMetrics = node.get("deviceMetrics", {})
|
||||
newMetrics.update(deviceMetrics)
|
||||
logging.debug(f"updating metrics for {asDict['from']} to {newMetrics}")
|
||||
node["deviceMetrics"] = newMetrics
|
||||
logger.debug(f"in _onTelemetryReceive() asDict:{asDict}")
|
||||
if "from" not in asDict:
|
||||
return
|
||||
|
||||
toUpdate = None
|
||||
|
||||
telemetry = asDict.get("decoded", {}).get("telemetry", {})
|
||||
node = iface._getOrCreateByNum(asDict["from"])
|
||||
if "deviceMetrics" in telemetry:
|
||||
toUpdate = "deviceMetrics"
|
||||
elif "environmentMetrics" in telemetry:
|
||||
toUpdate = "environmentMetrics"
|
||||
elif "airQualityMetrics" in telemetry:
|
||||
toUpdate = "airQualityMetrics"
|
||||
elif "powerMetrics" in telemetry:
|
||||
toUpdate = "powerMetrics"
|
||||
elif "localStats" in telemetry:
|
||||
toUpdate = "localStats"
|
||||
else:
|
||||
return
|
||||
|
||||
updateObj = telemetry.get(toUpdate)
|
||||
newMetrics = node.get(toUpdate, {})
|
||||
newMetrics.update(updateObj)
|
||||
logger.debug(f"updating {toUpdate} metrics for {asDict['from']} to {newMetrics}")
|
||||
node[toUpdate] = newMetrics
|
||||
|
||||
def _receiveInfoUpdate(iface, asDict):
|
||||
if "from" in asDict:
|
||||
@@ -209,6 +255,12 @@ def _receiveInfoUpdate(iface, asDict):
|
||||
iface._getOrCreateByNum(asDict["from"])["snr"] = asDict.get("rxSnr")
|
||||
iface._getOrCreateByNum(asDict["from"])["hopLimit"] = asDict.get("hopLimit")
|
||||
|
||||
def _onAdminReceive(iface, asDict):
|
||||
"""Special auto parsing for received messages"""
|
||||
logger.debug(f"in _onAdminReceive() asDict:{asDict}")
|
||||
if "decoded" in asDict and "from" in asDict and "admin" in asDict["decoded"]:
|
||||
adminMessage = asDict["decoded"]["admin"]["raw"]
|
||||
iface._getOrCreateByNum(asDict["from"])["adminSessionPassKey"] = adminMessage.session_passkey
|
||||
|
||||
"""Well known message payloads can register decoders for automatic protobuf parsing"""
|
||||
protocols = {
|
||||
@@ -228,7 +280,9 @@ protocols = {
|
||||
portnums_pb2.PortNum.NODEINFO_APP: KnownProtocol(
|
||||
"user", mesh_pb2.User, _onNodeInfoReceive
|
||||
),
|
||||
portnums_pb2.PortNum.ADMIN_APP: KnownProtocol("admin", admin_pb2.AdminMessage),
|
||||
portnums_pb2.PortNum.ADMIN_APP: KnownProtocol(
|
||||
"admin", admin_pb2.AdminMessage, _onAdminReceive
|
||||
),
|
||||
portnums_pb2.PortNum.ROUTING_APP: KnownProtocol("routing", mesh_pb2.Routing),
|
||||
portnums_pb2.PortNum.TELEMETRY_APP: KnownProtocol(
|
||||
"telemetry", telemetry_pb2.Telemetry, _onTelemetryReceive
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
from typing import cast, List
|
||||
|
||||
import dash_bootstrap_components as dbc # type: ignore[import-untyped]
|
||||
@@ -143,8 +144,8 @@ def create_dash(slog_path: str) -> Dash:
|
||||
"""
|
||||
app = Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
|
||||
|
||||
dpwr = read_pandas(f"{slog_path}/power.feather")
|
||||
dslog = read_pandas(f"{slog_path}/slog.feather")
|
||||
dpwr = read_pandas(os.path.join(slog_path, "power.feather"))
|
||||
dslog = read_pandas(os.path.join(slog_path, "slog.feather"))
|
||||
|
||||
pmon_raises = get_pmon_raises(dslog)
|
||||
|
||||
@@ -190,7 +191,7 @@ def main():
|
||||
parser = create_argparser()
|
||||
args = parser.parse_args()
|
||||
if not args.slog:
|
||||
args.slog = f"{root_dir()}/latest"
|
||||
args.slog = os.path.join(root_dir(), "latest")
|
||||
|
||||
app = create_dash(slog_path=args.slog)
|
||||
port = 8051
|
||||
|
||||
@@ -4,8 +4,10 @@ import asyncio
|
||||
import atexit
|
||||
import logging
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
from threading import Thread
|
||||
import io
|
||||
from threading import Thread, Event
|
||||
from typing import List, Optional
|
||||
|
||||
import google.protobuf
|
||||
@@ -22,6 +24,7 @@ FROMRADIO_UUID = "2c55e69e-4993-11ed-b878-0242ac120002"
|
||||
FROMNUM_UUID = "ed9da18c-a800-4f66-a670-aa7547e34453"
|
||||
LEGACY_LOGRADIO_UUID = "6c6fd238-78fa-436b-aacf-15c5be1ef2e2"
|
||||
LOGRADIO_UUID = "5a3d6e49-06e6-4423-9944-e9de8cdf9547"
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BLEInterface(MeshInterface):
|
||||
@@ -30,32 +33,43 @@ class BLEInterface(MeshInterface):
|
||||
class BLEError(Exception):
|
||||
"""An exception class for BLE errors."""
|
||||
|
||||
def __init__(
|
||||
DEVICE_NOT_FOUND = "device_not_found"
|
||||
MULTIPLE_DEVICES = "multiple_devices"
|
||||
READ_ERROR = "read_error"
|
||||
WRITE_ERROR = "write_error"
|
||||
UNKNOWN = "unknown"
|
||||
|
||||
def __init__(self, message: str, kind: str = UNKNOWN):
|
||||
super().__init__(message)
|
||||
self.kind = kind
|
||||
|
||||
def __init__( # pylint: disable=R0917
|
||||
self,
|
||||
address: Optional[str],
|
||||
noProto: bool = False,
|
||||
debugOut=None,
|
||||
debugOut: Optional[io.TextIOWrapper]=None,
|
||||
noNodes: bool = False,
|
||||
):
|
||||
timeout: int = 300,
|
||||
) -> None:
|
||||
MeshInterface.__init__(
|
||||
self, debugOut=debugOut, noProto=noProto, noNodes=noNodes
|
||||
self, debugOut=debugOut, noProto=noProto, noNodes=noNodes, timeout=timeout
|
||||
)
|
||||
|
||||
self.should_read = False
|
||||
|
||||
logging.debug("Threads starting")
|
||||
logger.debug("Threads starting")
|
||||
self._want_receive = True
|
||||
self._receiveThread: Optional[Thread] = Thread(
|
||||
target=self._receiveFromRadioImpl, name="BLEReceive", daemon=True
|
||||
)
|
||||
self._receiveThread.start()
|
||||
logging.debug("Threads running")
|
||||
logger.debug("Threads running")
|
||||
|
||||
self.client: Optional[BLEClient] = None
|
||||
try:
|
||||
logging.debug(f"BLE connecting to: {address if address else 'any'}")
|
||||
logger.debug(f"BLE connecting to: {address if address else 'any'}")
|
||||
self.client = self.connect(address)
|
||||
logging.debug("BLE connected")
|
||||
logger.debug("BLE connected")
|
||||
except BLEInterface.BLEError as e:
|
||||
self.close()
|
||||
raise e
|
||||
@@ -68,13 +82,13 @@ class BLEInterface(MeshInterface):
|
||||
if self.client.has_characteristic(LOGRADIO_UUID):
|
||||
self.client.start_notify(LOGRADIO_UUID, self.log_radio_handler)
|
||||
|
||||
logging.debug("Mesh configure starting")
|
||||
logger.debug("Mesh configure starting")
|
||||
self._startConfig()
|
||||
if not self.noProto:
|
||||
self._waitConnected(timeout=60.0)
|
||||
self.waitForConfig()
|
||||
|
||||
logging.debug("Register FROMNUM notify callback")
|
||||
logger.debug("Register FROMNUM notify callback")
|
||||
self.client.start_notify(FROMNUM_UUID, self.from_num_handler)
|
||||
|
||||
# We MUST run atexit (if we can) because otherwise (at least on linux) the BLE device is not disconnected
|
||||
@@ -82,12 +96,23 @@ class BLEInterface(MeshInterface):
|
||||
# Note: the on disconnected callback will call our self.close which will make us nicely wait for threads to exit
|
||||
self._exit_handler = atexit.register(self.client.disconnect)
|
||||
|
||||
def from_num_handler(self, _, b): # pylint: disable=C0116
|
||||
def __repr__(self):
|
||||
rep = f"BLEInterface(address={self.client.address if self.client else None!r}"
|
||||
if self.debugOut is not None:
|
||||
rep += f", debugOut={self.debugOut!r}"
|
||||
if self.noProto:
|
||||
rep += ", noProto=True"
|
||||
if self.noNodes:
|
||||
rep += ", noNodes=True"
|
||||
rep += ")"
|
||||
return rep
|
||||
|
||||
def from_num_handler(self, _, b: bytes) -> None: # pylint: disable=C0116
|
||||
"""Handle callbacks for fromnum notify.
|
||||
Note: this method does not need to be async because it is just setting a bool.
|
||||
"""
|
||||
from_num = struct.unpack("<I", bytes(b))[0]
|
||||
logging.debug(f"FROMNUM notify: {from_num}")
|
||||
logger.debug(f"FROMNUM notify: {from_num}")
|
||||
self.should_read = True
|
||||
|
||||
async def log_radio_handler(self, _, b): # pylint: disable=C0116
|
||||
@@ -102,7 +127,7 @@ class BLEInterface(MeshInterface):
|
||||
)
|
||||
self._handleLogLine(message)
|
||||
except google.protobuf.message.DecodeError:
|
||||
logging.warning("Malformed LogRecord received. Skipping.")
|
||||
logger.warning("Malformed LogRecord received. Skipping.")
|
||||
|
||||
async def legacy_log_radio_handler(self, _, b): # pylint: disable=C0116
|
||||
log_radio = b.decode("utf-8").replace("\n", "")
|
||||
@@ -112,7 +137,7 @@ class BLEInterface(MeshInterface):
|
||||
def scan() -> List[BLEDevice]:
|
||||
"""Scan for available BLE devices."""
|
||||
with BLEClient() as client:
|
||||
logging.info("Scanning for BLE devices (takes 10 seconds)...")
|
||||
logger.info("Scanning for BLE devices (takes 10 seconds)...")
|
||||
response = client.discover(
|
||||
timeout=10, return_adv=True, service_uuids=[SERVICE_UUID]
|
||||
)
|
||||
@@ -142,62 +167,74 @@ class BLEInterface(MeshInterface):
|
||||
|
||||
if len(addressed_devices) == 0:
|
||||
raise BLEInterface.BLEError(
|
||||
f"No Meshtastic BLE peripheral with identifier or address '{address}' found. Try --ble-scan to find it."
|
||||
f"No Meshtastic BLE peripheral with identifier or address '{address}' found. Try --ble-scan to find it.",
|
||||
BLEInterface.BLEError.DEVICE_NOT_FOUND,
|
||||
)
|
||||
if len(addressed_devices) > 1:
|
||||
raise BLEInterface.BLEError(
|
||||
f"More than one Meshtastic BLE peripheral with identifier or address '{address}' found."
|
||||
f"More than one Meshtastic BLE peripheral with identifier or address '{address}' found.",
|
||||
BLEInterface.BLEError.MULTIPLE_DEVICES,
|
||||
)
|
||||
return addressed_devices[0]
|
||||
|
||||
def _sanitize_address(address): # pylint: disable=E0213
|
||||
def _sanitize_address(self, address: Optional[str]) -> Optional[str]: # pylint: disable=E0213
|
||||
"Standardize BLE address by removing extraneous characters and lowercasing."
|
||||
return address.replace("-", "").replace("_", "").replace(":", "").lower()
|
||||
if address is None:
|
||||
return None
|
||||
else:
|
||||
return address.replace("-", "").replace("_", "").replace(":", "").lower()
|
||||
|
||||
def connect(self, address: Optional[str] = None) -> "BLEClient":
|
||||
"Connect to a device by address."
|
||||
|
||||
# Bleak docs recommend always doing a scan before connecting (even if we know addr)
|
||||
device = self.find_device(address)
|
||||
client = BLEClient(device.address, disconnected_callback=lambda _: self.close)
|
||||
client = BLEClient(device.address, disconnected_callback=lambda _: self.close())
|
||||
client.connect()
|
||||
client.discover()
|
||||
return client
|
||||
|
||||
def _receiveFromRadioImpl(self):
|
||||
def _receiveFromRadioImpl(self) -> None:
|
||||
while self._want_receive:
|
||||
if self.should_read:
|
||||
self.should_read = False
|
||||
retries = 0
|
||||
retries: int = 0
|
||||
while self._want_receive:
|
||||
if self.client is None:
|
||||
logger.debug(f"BLE client is None, shutting down")
|
||||
self._want_receive = False
|
||||
continue
|
||||
try:
|
||||
b = bytes(self.client.read_gatt_char(FROMRADIO_UUID))
|
||||
except BleakDBusError as e:
|
||||
# Device disconnected probably, so end our read loop immediately
|
||||
logging.debug(f"Device disconnected, shutting down {e}")
|
||||
logger.debug(f"Device disconnected, shutting down {e}")
|
||||
self._want_receive = False
|
||||
except BleakError as e:
|
||||
# We were definitely disconnected
|
||||
if "Not connected" in str(e):
|
||||
logging.debug(f"Device disconnected, shutting down {e}")
|
||||
logger.debug(f"Device disconnected, shutting down {e}")
|
||||
self._want_receive = False
|
||||
else:
|
||||
raise BLEInterface.BLEError("Error reading BLE") from e
|
||||
raise BLEInterface.BLEError(
|
||||
"Error reading BLE",
|
||||
BLEInterface.BLEError.READ_ERROR,
|
||||
) from e
|
||||
if not b:
|
||||
if retries < 5:
|
||||
time.sleep(0.1)
|
||||
retries += 1
|
||||
continue
|
||||
break
|
||||
logging.debug(f"FROMRADIO read: {b.hex()}")
|
||||
logger.debug(f"FROMRADIO read: {b.hex()}")
|
||||
self._handleFromRadio(b)
|
||||
else:
|
||||
time.sleep(0.01)
|
||||
|
||||
def _sendToRadioImpl(self, toRadio):
|
||||
b = toRadio.SerializeToString()
|
||||
def _sendToRadioImpl(self, toRadio) -> None:
|
||||
b: bytes = toRadio.SerializeToString()
|
||||
if b and self.client: # we silently ignore writes while we are shutting down
|
||||
logging.debug(f"TORADIO write: {b.hex()}")
|
||||
logger.debug(f"TORADIO write: {b.hex()}")
|
||||
try:
|
||||
self.client.write_gatt_char(
|
||||
TORADIO_UUID, b, response=True
|
||||
@@ -205,20 +242,21 @@ class BLEInterface(MeshInterface):
|
||||
# search Bleak src for org.bluez.Error.InProgress
|
||||
except Exception as e:
|
||||
raise BLEInterface.BLEError(
|
||||
"Error writing BLE (are you in the 'bluetooth' user group? did you enter the pairing PIN on your computer?)"
|
||||
"Error writing BLE (are you in the 'bluetooth' user group? did you enter the pairing PIN on your computer?)",
|
||||
BLEInterface.BLEError.WRITE_ERROR,
|
||||
) from e
|
||||
# Allow to propagate and then make sure we read
|
||||
time.sleep(0.01)
|
||||
self.should_read = True
|
||||
|
||||
def close(self):
|
||||
def close(self) -> None:
|
||||
try:
|
||||
MeshInterface.close(self)
|
||||
except Exception as e:
|
||||
logging.error(f"Error closing mesh interface: {e}")
|
||||
logger.error(f"Error closing mesh interface: {e}")
|
||||
|
||||
if self._want_receive:
|
||||
self.want_receive = False # Tell the thread we want it to stop
|
||||
self._want_receive = False # Tell the thread we want it to stop
|
||||
if self._receiveThread:
|
||||
self._receiveThread.join(
|
||||
timeout=2
|
||||
@@ -230,20 +268,23 @@ class BLEInterface(MeshInterface):
|
||||
self.client.disconnect()
|
||||
self.client.close()
|
||||
self.client = None
|
||||
self._disconnected() # send the disconnected indicator up to clients
|
||||
|
||||
|
||||
class BLEClient:
|
||||
"""Client for managing connection to a BLE device"""
|
||||
|
||||
def __init__(self, address=None, **kwargs):
|
||||
def __init__(self, address=None, **kwargs) -> None:
|
||||
self._loop_ready = Event()
|
||||
self._eventLoop = asyncio.new_event_loop()
|
||||
self._eventThread = Thread(
|
||||
target=self._run_event_loop, name="BLEClient", daemon=True
|
||||
)
|
||||
self._eventThread.start()
|
||||
self._loop_ready.wait() # Wait for event loop to be running
|
||||
|
||||
if not address:
|
||||
logging.debug("No address provided - only discover method will work.")
|
||||
logger.debug("No address provided - only discover method will work.")
|
||||
return
|
||||
|
||||
self.bleak_client = BleakClient(address, **kwargs)
|
||||
@@ -284,13 +325,28 @@ class BLEClient:
|
||||
self.close()
|
||||
|
||||
def async_await(self, coro, timeout=None): # pylint: disable=C0116
|
||||
return self.async_run(coro).result(timeout)
|
||||
"""Wait for async operation to complete.
|
||||
|
||||
On macOS, CoreBluetooth requires occasional I/O operations for
|
||||
callbacks to be properly delivered. The debug logging provides this
|
||||
I/O when enabled, allowing the system to process pending callbacks.
|
||||
"""
|
||||
logger.debug(f"async_await: waiting for {coro}")
|
||||
future = self.async_run(coro)
|
||||
# On macOS without debug logging, callbacks may not be delivered
|
||||
# unless we trigger some I/O. This is a known quirk of CoreBluetooth.
|
||||
sys.stdout.flush()
|
||||
result = future.result(timeout)
|
||||
logger.debug("async_await: complete")
|
||||
return result
|
||||
|
||||
def async_run(self, coro): # pylint: disable=C0116
|
||||
return asyncio.run_coroutine_threadsafe(coro, self._eventLoop)
|
||||
|
||||
def _run_event_loop(self):
|
||||
try:
|
||||
# Signal ready from WITHIN the loop to guarantee it's actually running
|
||||
self._eventLoop.call_soon(self._loop_ready.set)
|
||||
self._eventLoop.run_forever()
|
||||
finally:
|
||||
self._eventLoop.close()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,8 @@ with rather more easily once the code is simplified by this change.
|
||||
|
||||
"""
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
def reset():
|
||||
"""
|
||||
Restore the namespace to pristine condition.
|
||||
@@ -33,5 +35,5 @@ args = None
|
||||
parser = None
|
||||
channel_index = None
|
||||
logfile = None
|
||||
tunnelInstance = None
|
||||
tunnelInstance: Optional[Any] = None
|
||||
camel_case = False
|
||||
|
||||
@@ -5,9 +5,9 @@ import base64
|
||||
import logging
|
||||
import time
|
||||
|
||||
from typing import Optional, Union
|
||||
from typing import Optional, Union, List
|
||||
|
||||
from meshtastic.protobuf import admin_pb2, apponly_pb2, channel_pb2, localonly_pb2, mesh_pb2, portnums_pb2
|
||||
from meshtastic.protobuf import admin_pb2, apponly_pb2, channel_pb2, config_pb2, localonly_pb2, mesh_pb2, portnums_pb2
|
||||
from meshtastic.util import (
|
||||
Timeout,
|
||||
camel_to_snake,
|
||||
@@ -16,8 +16,12 @@ from meshtastic.util import (
|
||||
pskToString,
|
||||
stripnl,
|
||||
message_to_json,
|
||||
generate_channel_hash,
|
||||
to_node_num,
|
||||
flags_to_list,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Node:
|
||||
"""A model of a (local or remote) node in the mesh
|
||||
@@ -25,15 +29,15 @@ class Node:
|
||||
Includes methods for localConfig, moduleConfig and channels
|
||||
"""
|
||||
|
||||
def __init__(self, iface, nodeNum, noProto=False):
|
||||
def __init__(self, iface, nodeNum, noProto=False, timeout: int = 300):
|
||||
"""Constructor"""
|
||||
self.iface = iface
|
||||
self.nodeNum = nodeNum
|
||||
self.localConfig = localonly_pb2.LocalConfig()
|
||||
self.moduleConfig = localonly_pb2.LocalModuleConfig()
|
||||
self.channels = None
|
||||
self._timeout = Timeout(maxSecs=300)
|
||||
self.partialChannels = None
|
||||
self._timeout = Timeout(maxSecs=timeout)
|
||||
self.partialChannels: Optional[List] = None
|
||||
self.noProto = noProto
|
||||
self.cannedPluginMessage = None
|
||||
self.cannedPluginMessageMessages = None
|
||||
@@ -42,11 +46,40 @@ class Node:
|
||||
|
||||
self.gotResponse = None
|
||||
|
||||
def __repr__(self):
|
||||
r = f"Node({self.iface!r}, 0x{self.nodeNum:08x}"
|
||||
if self.noProto:
|
||||
r += ", noProto=True"
|
||||
if self._timeout.expireTimeout != 300:
|
||||
r += ", timeout={self._timeout.expireTimeout!r}"
|
||||
r += ")"
|
||||
return r
|
||||
|
||||
@staticmethod
|
||||
def position_flags_list(position_flags: int) -> List[str]:
|
||||
"Return a list of position flags from the given flags integer"
|
||||
return flags_to_list(config_pb2.Config.PositionConfig.PositionFlags, position_flags)
|
||||
|
||||
@staticmethod
|
||||
def excluded_modules_list(excluded_modules: int) -> List[str]:
|
||||
"Return a list of excluded modules from the given flags integer"
|
||||
return flags_to_list(mesh_pb2.ExcludedModules, excluded_modules)
|
||||
|
||||
def module_available(self, excluded_bit: int) -> bool:
|
||||
"""Check DeviceMetadata.excluded_modules to see if a module is available."""
|
||||
meta = getattr(self.iface, "metadata", None)
|
||||
if meta is None:
|
||||
return True
|
||||
try:
|
||||
return (meta.excluded_modules & excluded_bit) == 0
|
||||
except Exception:
|
||||
return True
|
||||
|
||||
def showChannels(self):
|
||||
"""Show human readable description of our channels."""
|
||||
print("Channels:")
|
||||
if self.channels:
|
||||
logging.debug(f"self.channels:{self.channels}")
|
||||
logger.debug(f"self.channels:{self.channels}")
|
||||
for c in self.channels:
|
||||
cStr = message_to_json(c.settings)
|
||||
# don't show disabled channels
|
||||
@@ -77,17 +110,19 @@ class Node:
|
||||
self.channels = channels
|
||||
self._fixupChannels()
|
||||
|
||||
def requestChannels(self):
|
||||
def requestChannels(self, startingIndex: int = 0):
|
||||
"""Send regular MeshPackets to ask channels."""
|
||||
logging.debug(f"requestChannels for nodeNum:{self.nodeNum}")
|
||||
self.channels = None
|
||||
self.partialChannels = [] # We keep our channels in a temp array until finished
|
||||
|
||||
self._requestChannel(0)
|
||||
logger.debug(f"requestChannels for nodeNum:{self.nodeNum}")
|
||||
# only initialize if we're starting out fresh
|
||||
if startingIndex == 0:
|
||||
self.channels = None
|
||||
self.partialChannels = [] # We keep our channels in a temp array until finished
|
||||
self._requestChannel(startingIndex)
|
||||
|
||||
def onResponseRequestSettings(self, p):
|
||||
"""Handle the response packets for requesting settings _requestSettings()"""
|
||||
logging.debug(f"onResponseRequestSetting() p:{p}")
|
||||
logger.debug(f"onResponseRequestSetting() p:{p}")
|
||||
config_values = None
|
||||
if "routing" in p["decoded"]:
|
||||
if p["decoded"]["routing"]["errorReason"] != "NONE":
|
||||
print(f'Error on response: {p["decoded"]["routing"]["errorReason"]}')
|
||||
@@ -97,13 +132,16 @@ class Node:
|
||||
print("")
|
||||
adminMessage = p["decoded"]["admin"]
|
||||
if "getConfigResponse" in adminMessage:
|
||||
oneof = "get_config_response"
|
||||
resp = adminMessage["getConfigResponse"]
|
||||
field = list(resp.keys())[0]
|
||||
config_type = self.localConfig.DESCRIPTOR.fields_by_name.get(
|
||||
camel_to_snake(field)
|
||||
)
|
||||
config_values = getattr(self.localConfig, config_type.name)
|
||||
if config_type is not None:
|
||||
config_values = getattr(self.localConfig, config_type.name)
|
||||
elif "getModuleConfigResponse" in adminMessage:
|
||||
oneof = "get_module_config_response"
|
||||
resp = adminMessage["getModuleConfigResponse"]
|
||||
field = list(resp.keys())[0]
|
||||
config_type = self.moduleConfig.DESCRIPTOR.fields_by_name.get(
|
||||
@@ -115,9 +153,10 @@ class Node:
|
||||
"Did not receive a valid response. Make sure to have a shared channel named 'admin'."
|
||||
)
|
||||
return
|
||||
for key, value in resp[field].items():
|
||||
setattr(config_values, camel_to_snake(key), value)
|
||||
print(f"{str(camel_to_snake(field))}:\n{str(config_values)}")
|
||||
if config_values is not None:
|
||||
raw_config = getattr(getattr(adminMessage['raw'], oneof), camel_to_snake(field))
|
||||
config_values.CopyFrom(raw_config)
|
||||
print(f"{str(camel_to_snake(field))}:\n{str(config_values)}")
|
||||
|
||||
def requestConfig(self, configType):
|
||||
"""Request the config from the node via admin message"""
|
||||
@@ -126,16 +165,17 @@ class Node:
|
||||
else:
|
||||
onResponse = self.onResponseRequestSettings
|
||||
print("Requesting current config from remote node (this can take a while).")
|
||||
p = admin_pb2.AdminMessage()
|
||||
if isinstance(configType, int):
|
||||
p.get_config_request = configType
|
||||
|
||||
msgIndex = configType.index
|
||||
if configType.containing_type.name == "LocalConfig":
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.get_config_request = msgIndex
|
||||
self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
|
||||
else:
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.get_module_config_request = msgIndex
|
||||
self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
|
||||
if configType.containing_type.name == "LocalConfig":
|
||||
p.get_config_request = admin_pb2.AdminMessage.ConfigType.Value(configType.name.upper() + "_CONFIG")
|
||||
else:
|
||||
p.get_module_config_request = configType.index
|
||||
|
||||
self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
|
||||
if onResponse:
|
||||
self.iface.waitForAckNak()
|
||||
|
||||
@@ -170,6 +210,8 @@ class Node:
|
||||
p.set_config.lora.CopyFrom(self.localConfig.lora)
|
||||
elif config_name == "bluetooth":
|
||||
p.set_config.bluetooth.CopyFrom(self.localConfig.bluetooth)
|
||||
elif config_name == "security":
|
||||
p.set_config.security.CopyFrom(self.localConfig.security)
|
||||
elif config_name == "mqtt":
|
||||
p.set_module_config.mqtt.CopyFrom(self.moduleConfig.mqtt)
|
||||
elif config_name == "serial":
|
||||
@@ -202,10 +244,12 @@ class Node:
|
||||
p.set_module_config.ambient_lighting.CopyFrom(self.moduleConfig.ambient_lighting)
|
||||
elif config_name == "paxcounter":
|
||||
p.set_module_config.paxcounter.CopyFrom(self.moduleConfig.paxcounter)
|
||||
elif config_name == "traffic_management":
|
||||
p.set_module_config.traffic_management.CopyFrom(self.moduleConfig.traffic_management)
|
||||
else:
|
||||
our_exit(f"Error: No valid config with name {config_name}")
|
||||
|
||||
logging.debug(f"Wrote: {config_name}")
|
||||
logger.debug(f"Wrote: {config_name}")
|
||||
if self == self.iface.localNode:
|
||||
onResponse = None
|
||||
else:
|
||||
@@ -214,11 +258,11 @@ class Node:
|
||||
|
||||
def writeChannel(self, channelIndex, adminIndex=0):
|
||||
"""Write the current (edited) channel to the device"""
|
||||
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.set_channel.CopyFrom(self.channels[channelIndex])
|
||||
self._sendAdmin(p, adminIndex=adminIndex)
|
||||
logging.debug(f"Wrote channel {channelIndex}")
|
||||
logger.debug(f"Wrote channel {channelIndex}")
|
||||
|
||||
def getChannelByChannelIndex(self, channelIndex):
|
||||
"""Get channel by channelIndex
|
||||
@@ -243,12 +287,22 @@ class Node:
|
||||
# for sending admin channels will also change
|
||||
adminIndex = self.iface.localNode._getAdminChannelIndex()
|
||||
|
||||
# Snapshot serialized channel payloads from channelIndex onward so we
|
||||
# can avoid writing slots whose protobuf content did not change after
|
||||
# the shift. Use bytes (not message objects), because _fixupChannels()
|
||||
# mutates message fields in-place.
|
||||
old_channels = [
|
||||
self.channels[i].SerializeToString()
|
||||
for i in range(channelIndex, len(self.channels))
|
||||
]
|
||||
|
||||
self.channels.pop(channelIndex)
|
||||
self._fixupChannels() # expand back to 8 channels
|
||||
|
||||
index = channelIndex
|
||||
while index < 8:
|
||||
self.writeChannel(index, adminIndex=adminIndex)
|
||||
for old_ch in old_channels:
|
||||
if self.channels[index].SerializeToString() != old_ch:
|
||||
self.writeChannel(index, adminIndex=adminIndex)
|
||||
index += 1
|
||||
|
||||
# if we are updating the local node, we might end up
|
||||
@@ -279,27 +333,37 @@ class Node:
|
||||
return c.index
|
||||
return 0
|
||||
|
||||
def setOwner(self, long_name: Optional[str]=None, short_name: Optional[str]=None, is_licensed: bool=False):
|
||||
def setOwner(self, long_name: Optional[str]=None, short_name: Optional[str]=None, is_licensed: bool=False, is_unmessagable: Optional[bool]=None):
|
||||
"""Set device owner name"""
|
||||
logging.debug(f"in setOwner nodeNum:{self.nodeNum}")
|
||||
logger.debug(f"in setOwner nodeNum:{self.nodeNum}")
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
|
||||
nChars = 4
|
||||
if long_name is not None:
|
||||
long_name = long_name.strip()
|
||||
# Validate that long_name is not empty or whitespace-only
|
||||
if not long_name:
|
||||
our_exit("ERROR: Long Name cannot be empty or contain only whitespace characters")
|
||||
p.set_owner.long_name = long_name
|
||||
p.set_owner.is_licensed = is_licensed
|
||||
if short_name is not None:
|
||||
short_name = short_name.strip()
|
||||
# Validate that short_name is not empty or whitespace-only
|
||||
if not short_name:
|
||||
our_exit("ERROR: Short Name cannot be empty or contain only whitespace characters")
|
||||
if len(short_name) > nChars:
|
||||
short_name = short_name[:nChars]
|
||||
print(f"Maximum is 4 characters, truncated to {short_name}")
|
||||
p.set_owner.short_name = short_name
|
||||
if is_unmessagable is not None:
|
||||
p.set_owner.is_unmessagable = is_unmessagable
|
||||
|
||||
# Note: These debug lines are used in unit tests
|
||||
logging.debug(f"p.set_owner.long_name:{p.set_owner.long_name}:")
|
||||
logging.debug(f"p.set_owner.short_name:{p.set_owner.short_name}:")
|
||||
logging.debug(f"p.set_owner.is_licensed:{p.set_owner.is_licensed}")
|
||||
logger.debug(f"p.set_owner.long_name:{p.set_owner.long_name}:")
|
||||
logger.debug(f"p.set_owner.short_name:{p.set_owner.short_name}:")
|
||||
logger.debug(f"p.set_owner.is_licensed:{p.set_owner.is_licensed}")
|
||||
logger.debug(f"p.set_owner.is_unmessagable:{p.set_owner.is_unmessagable}:")
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
onResponse = None
|
||||
@@ -326,14 +390,59 @@ class Node:
|
||||
s = s.replace("=", "").replace("+", "-").replace("/", "_")
|
||||
return f"https://meshtastic.org/e/#{s}"
|
||||
|
||||
def setURL(self, url):
|
||||
def getContactURL(self, node_id: Union[int, str], should_ignore: bool = False, manually_verified: bool = False):
|
||||
"""Generate a shareable contact URL for the specified node"""
|
||||
nodeNum = to_node_num(node_id)
|
||||
|
||||
node = self.iface.nodesByNum.get(nodeNum)
|
||||
if not node or not node.get("user"):
|
||||
our_exit(f"Warning: Node {node_id} not found in NodeDB")
|
||||
|
||||
contact = admin_pb2.SharedContact()
|
||||
contact.node_num = nodeNum
|
||||
|
||||
u = node["user"]
|
||||
if u.get("id"):
|
||||
contact.user.id = u["id"]
|
||||
if u.get("macaddr"):
|
||||
contact.user.macaddr = base64.b64decode(u["macaddr"])
|
||||
if u.get("longName"):
|
||||
contact.user.long_name = u["longName"]
|
||||
if u.get("shortName"):
|
||||
contact.user.short_name = u["shortName"]
|
||||
if u.get("hwModel") and u["hwModel"] != "UNSET":
|
||||
contact.user.hw_model = mesh_pb2.HardwareModel.Value(u["hwModel"])
|
||||
if u.get("role"):
|
||||
contact.user.role = config_pb2.Config.DeviceConfig.Role.Value(u["role"])
|
||||
if u.get("publicKey"):
|
||||
contact.user.public_key = base64.b64decode(u["publicKey"])
|
||||
if u.get("isLicensed"):
|
||||
contact.user.is_licensed = u["isLicensed"]
|
||||
if u.get("isUnmessagable") is not None:
|
||||
contact.user.is_unmessagable = u["isUnmessagable"]
|
||||
if should_ignore:
|
||||
contact.should_ignore = True
|
||||
if manually_verified:
|
||||
contact.manually_verified = True
|
||||
|
||||
data = contact.SerializeToString()
|
||||
s = base64.urlsafe_b64encode(data).decode("ascii")
|
||||
s = s.replace("=", "").replace("+", "-").replace("/", "_")
|
||||
return f"https://meshtastic.org/v/#{s}"
|
||||
|
||||
def setURL(self, url: str, addOnly: bool = False):
|
||||
"""Set mesh network URL"""
|
||||
if self.localConfig is None:
|
||||
our_exit("Warning: No Config has been read")
|
||||
if self.localConfig is None or self.channels is None:
|
||||
our_exit("Warning: config or channels not loaded")
|
||||
|
||||
# URLs are of the form https://meshtastic.org/d/#{base64_channel_set}
|
||||
# Split on '/#' to find the base64 encoded channel settings
|
||||
splitURL = url.split("/#")
|
||||
if addOnly:
|
||||
splitURL = url.split("/?add=true#")
|
||||
else:
|
||||
splitURL = url.split("/#")
|
||||
if len(splitURL) == 1:
|
||||
our_exit(f"Warning: Invalid URL '{url}'")
|
||||
b64 = splitURL[-1]
|
||||
|
||||
# We normally strip padding to make for a shorter URL, but the python parser doesn't like
|
||||
@@ -350,28 +459,71 @@ class Node:
|
||||
if len(channelSet.settings) == 0:
|
||||
our_exit("Warning: There were no settings.")
|
||||
|
||||
i = 0
|
||||
for chs in channelSet.settings:
|
||||
ch = channel_pb2.Channel()
|
||||
ch.role = (
|
||||
channel_pb2.Channel.Role.PRIMARY
|
||||
if i == 0
|
||||
else channel_pb2.Channel.Role.SECONDARY
|
||||
)
|
||||
ch.index = i
|
||||
ch.settings.CopyFrom(chs)
|
||||
self.channels[ch.index] = ch
|
||||
logging.debug(f"Channel i:{i} ch:{ch}")
|
||||
self.writeChannel(ch.index)
|
||||
i = i + 1
|
||||
if addOnly:
|
||||
# Add new channels with names not already present
|
||||
# Don't change existing channels
|
||||
for chs in channelSet.settings:
|
||||
channelExists = self.getChannelByName(chs.name)
|
||||
if channelExists or chs.name == "":
|
||||
print(f"Ignoring existing or empty channel \"{chs.name}\" from add URL")
|
||||
continue
|
||||
ch = self.getDisabledChannel()
|
||||
if not ch:
|
||||
our_exit("Warning: No free channels were found")
|
||||
ch.settings.CopyFrom(chs)
|
||||
ch.role = channel_pb2.Channel.Role.SECONDARY
|
||||
print(f"Adding new channel '{chs.name}' to device")
|
||||
self.writeChannel(ch.index)
|
||||
else:
|
||||
i = 0
|
||||
for chs in channelSet.settings:
|
||||
ch = channel_pb2.Channel()
|
||||
ch.role = (
|
||||
channel_pb2.Channel.Role.PRIMARY
|
||||
if i == 0
|
||||
else channel_pb2.Channel.Role.SECONDARY
|
||||
)
|
||||
ch.index = i
|
||||
ch.settings.CopyFrom(chs)
|
||||
self.channels[ch.index] = ch
|
||||
logger.debug(f"Channel i:{i} ch:{ch}")
|
||||
self.writeChannel(ch.index)
|
||||
i = i + 1
|
||||
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.set_config.lora.CopyFrom(channelSet.lora_config)
|
||||
self.ensureSessionKey()
|
||||
self._sendAdmin(p)
|
||||
|
||||
def addContactURL(self, url: str):
|
||||
"""Add a contact (User) to the NodeDB from a shareable URL"""
|
||||
self.ensureSessionKey()
|
||||
|
||||
splitURL = url.split("/#")
|
||||
if len(splitURL) == 1:
|
||||
our_exit(f"Warning: Invalid URL '{url}'")
|
||||
b64 = splitURL[-1]
|
||||
|
||||
missing_padding = len(b64) % 4
|
||||
if missing_padding:
|
||||
b64 += "=" * (4 - missing_padding)
|
||||
|
||||
decodedURL = base64.urlsafe_b64decode(b64)
|
||||
contact = admin_pb2.SharedContact()
|
||||
contact.ParseFromString(decodedURL)
|
||||
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.add_contact.CopyFrom(contact)
|
||||
|
||||
if self == self.iface.localNode:
|
||||
onResponse = None
|
||||
else:
|
||||
onResponse = self.onAckNak
|
||||
return self._sendAdmin(p, onResponse=onResponse)
|
||||
|
||||
def onResponseRequestRingtone(self, p):
|
||||
"""Handle the response packet for requesting ringtone part 1"""
|
||||
logging.debug(f"onResponseRequestRingtone() p:{p}")
|
||||
logger.debug(f"onResponseRequestRingtone() p:{p}")
|
||||
errorFound = False
|
||||
if "routing" in p["decoded"]:
|
||||
if p["decoded"]["routing"]["errorReason"] != "NONE":
|
||||
@@ -384,12 +536,16 @@ class Node:
|
||||
self.ringtonePart = p["decoded"]["admin"][
|
||||
"raw"
|
||||
].get_ringtone_response
|
||||
logging.debug(f"self.ringtonePart:{self.ringtonePart}")
|
||||
logger.debug(f"self.ringtonePart:{self.ringtonePart}")
|
||||
self.gotResponse = True
|
||||
|
||||
def get_ringtone(self):
|
||||
"""Get the ringtone. Concatenate all pieces together and return a single string."""
|
||||
logging.debug(f"in get_ringtone()")
|
||||
logger.debug(f"in get_ringtone()")
|
||||
if not self.module_available(mesh_pb2.EXTNOTIF_CONFIG):
|
||||
logging.warning("External Notification module not present (excluded by firmware)")
|
||||
return None
|
||||
|
||||
if not self.ringtone:
|
||||
p1 = admin_pb2.AdminMessage()
|
||||
p1.get_ringtone_request = True
|
||||
@@ -400,22 +556,24 @@ class Node:
|
||||
while self.gotResponse is False:
|
||||
time.sleep(0.1)
|
||||
|
||||
logging.debug(f"self.ringtone:{self.ringtone}")
|
||||
logger.debug(f"self.ringtone:{self.ringtone}")
|
||||
|
||||
self.ringtone = ""
|
||||
if self.ringtonePart:
|
||||
self.ringtone += self.ringtonePart
|
||||
|
||||
print(f"ringtone:{self.ringtone}")
|
||||
logging.debug(f"ringtone:{self.ringtone}")
|
||||
logger.debug(f"ringtone:{self.ringtone}")
|
||||
return self.ringtone
|
||||
|
||||
def set_ringtone(self, ringtone):
|
||||
"""Set the ringtone. The ringtone length must be less than 230 character."""
|
||||
if not self.module_available(mesh_pb2.EXTNOTIF_CONFIG):
|
||||
logging.warning("External Notification module not present (excluded by firmware)")
|
||||
return None
|
||||
|
||||
if len(ringtone) > 230:
|
||||
our_exit("Warning: The ringtone must be less than 230 characters.")
|
||||
|
||||
self.ensureSessionKey()
|
||||
# split into chunks
|
||||
chunks = []
|
||||
chunks_size = 230
|
||||
@@ -431,7 +589,7 @@ class Node:
|
||||
if i == 0:
|
||||
p.set_ringtone_message = chunk
|
||||
|
||||
logging.debug(f"Setting ringtone '{chunk}' part {i+1}")
|
||||
logger.debug(f"Setting ringtone '{chunk}' part {i+1}")
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
onResponse = None
|
||||
@@ -441,7 +599,7 @@ class Node:
|
||||
|
||||
def onResponseRequestCannedMessagePluginMessageMessages(self, p):
|
||||
"""Handle the response packet for requesting canned message plugin message part 1"""
|
||||
logging.debug(f"onResponseRequestCannedMessagePluginMessageMessages() p:{p}")
|
||||
logger.debug(f"onResponseRequestCannedMessagePluginMessageMessages() p:{p}")
|
||||
errorFound = False
|
||||
if "routing" in p["decoded"]:
|
||||
if p["decoded"]["routing"]["errorReason"] != "NONE":
|
||||
@@ -454,14 +612,17 @@ class Node:
|
||||
self.cannedPluginMessageMessages = p["decoded"]["admin"][
|
||||
"raw"
|
||||
].get_canned_message_module_messages_response
|
||||
logging.debug(
|
||||
logger.debug(
|
||||
f"self.cannedPluginMessageMessages:{self.cannedPluginMessageMessages}"
|
||||
)
|
||||
self.gotResponse = True
|
||||
|
||||
def get_canned_message(self):
|
||||
"""Get the canned message string. Concatenate all pieces together and return a single string."""
|
||||
logging.debug(f"in get_canned_message()")
|
||||
logger.debug(f"in get_canned_message()")
|
||||
if not self.module_available(mesh_pb2.CANNEDMSG_CONFIG):
|
||||
logging.warning("Canned Message module not present (excluded by firmware)")
|
||||
return None
|
||||
if not self.cannedPluginMessage:
|
||||
p1 = admin_pb2.AdminMessage()
|
||||
p1.get_canned_message_module_messages_request = True
|
||||
@@ -474,7 +635,7 @@ class Node:
|
||||
while self.gotResponse is False:
|
||||
time.sleep(0.1)
|
||||
|
||||
logging.debug(
|
||||
logger.debug(
|
||||
f"self.cannedPluginMessageMessages:{self.cannedPluginMessageMessages}"
|
||||
)
|
||||
|
||||
@@ -482,16 +643,18 @@ class Node:
|
||||
if self.cannedPluginMessageMessages:
|
||||
self.cannedPluginMessage += self.cannedPluginMessageMessages
|
||||
|
||||
print(f"canned_plugin_message:{self.cannedPluginMessage}")
|
||||
logging.debug(f"canned_plugin_message:{self.cannedPluginMessage}")
|
||||
logger.debug(f"canned_plugin_message:{self.cannedPluginMessage}")
|
||||
return self.cannedPluginMessage
|
||||
|
||||
def set_canned_message(self, message):
|
||||
"""Set the canned message. The canned messages length must be less than 200 character."""
|
||||
if not self.module_available(mesh_pb2.CANNEDMSG_CONFIG):
|
||||
logging.warning("Canned Message module not present (excluded by firmware)")
|
||||
return None
|
||||
|
||||
if len(message) > 200:
|
||||
our_exit("Warning: The canned message must be less than 200 characters.")
|
||||
|
||||
self.ensureSessionKey()
|
||||
# split into chunks
|
||||
chunks = []
|
||||
chunks_size = 200
|
||||
@@ -507,7 +670,7 @@ class Node:
|
||||
if i == 0:
|
||||
p.set_canned_message_module_messages = chunk
|
||||
|
||||
logging.debug(f"Setting canned message '{chunk}' part {i+1}")
|
||||
logger.debug(f"Setting canned message '{chunk}' part {i+1}")
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
onResponse = None
|
||||
@@ -518,17 +681,19 @@ class Node:
|
||||
def exitSimulator(self):
|
||||
"""Tell a simulator node to exit (this message
|
||||
is ignored for other nodes)"""
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.exit_simulator = True
|
||||
logging.debug("in exitSimulator()")
|
||||
logger.debug("in exitSimulator()")
|
||||
|
||||
return self._sendAdmin(p)
|
||||
|
||||
def reboot(self, secs: int = 10):
|
||||
"""Tell the node to reboot."""
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.reboot_seconds = secs
|
||||
logging.info(f"Telling node to reboot in {secs} seconds")
|
||||
logger.info(f"Telling node to reboot in {secs} seconds")
|
||||
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
@@ -539,9 +704,10 @@ class Node:
|
||||
|
||||
def beginSettingsTransaction(self):
|
||||
"""Tell the node to open a transaction to edit settings."""
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.begin_edit_settings = True
|
||||
logging.info(f"Telling open a transaction to edit settings")
|
||||
logger.info(f"Telling open a transaction to edit settings")
|
||||
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
@@ -552,9 +718,10 @@ class Node:
|
||||
|
||||
def commitSettingsTransaction(self):
|
||||
"""Tell the node to commit the open transaction for editing settings."""
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.commit_edit_settings = True
|
||||
logging.info(f"Telling node to commit open transaction for editing settings")
|
||||
logger.info(f"Telling node to commit open transaction for editing settings")
|
||||
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
@@ -564,10 +731,11 @@ class Node:
|
||||
return self._sendAdmin(p, onResponse=onResponse)
|
||||
|
||||
def rebootOTA(self, secs: int = 10):
|
||||
"""Tell the node to reboot into factory firmware."""
|
||||
"""Tell the node to reboot into factory firmware (firmware < 2.7.18)."""
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.reboot_ota_seconds = secs
|
||||
logging.info(f"Telling node to reboot to OTA in {secs} seconds")
|
||||
logger.info(f"Telling node to reboot to OTA in {secs} seconds")
|
||||
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
@@ -576,11 +744,28 @@ class Node:
|
||||
onResponse = self.onAckNak
|
||||
return self._sendAdmin(p, onResponse=onResponse)
|
||||
|
||||
def startOTA(
|
||||
self,
|
||||
ota_mode: admin_pb2.OTAMode.ValueType,
|
||||
ota_file_hash: bytes,
|
||||
):
|
||||
"""Tell the node to start OTA mode (firmware >= 2.7.18)."""
|
||||
if self != self.iface.localNode:
|
||||
raise ValueError("startOTA only possible in local node")
|
||||
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.ota_request.reboot_ota_mode = ota_mode
|
||||
p.ota_request.ota_hash = ota_file_hash
|
||||
|
||||
return self._sendAdmin(p)
|
||||
|
||||
def enterDFUMode(self):
|
||||
"""Tell the node to enter DFU mode (NRF52)."""
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.enter_dfu_mode_request = True
|
||||
logging.info(f"Telling node to enable DFU mode")
|
||||
logger.info(f"Telling node to enable DFU mode")
|
||||
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
@@ -591,9 +776,10 @@ class Node:
|
||||
|
||||
def shutdown(self, secs: int = 10):
|
||||
"""Tell the node to shutdown."""
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.shutdown_seconds = secs
|
||||
logging.info(f"Telling node to shutdown in {secs} seconds")
|
||||
logger.info(f"Telling node to shutdown in {secs} seconds")
|
||||
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
@@ -606,18 +792,23 @@ class Node:
|
||||
"""Get the node's metadata."""
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.get_device_metadata_request = True
|
||||
logging.info(f"Requesting device metadata")
|
||||
logger.info(f"Requesting device metadata")
|
||||
|
||||
self._sendAdmin(
|
||||
p, wantResponse=True, onResponse=self.onRequestGetMetadata
|
||||
)
|
||||
self.iface.waitForAckNak()
|
||||
|
||||
def factoryReset(self):
|
||||
def factoryReset(self, full: bool = False):
|
||||
"""Tell the node to factory reset."""
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.factory_reset = True
|
||||
logging.info(f"Telling node to factory reset")
|
||||
if full:
|
||||
p.factory_reset_device = 1
|
||||
logger.info(f"Telling node to factory reset (full device reset)")
|
||||
else:
|
||||
p.factory_reset_config = 1
|
||||
logger.info(f"Telling node to factory reset (config reset)")
|
||||
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
@@ -628,11 +819,8 @@ class Node:
|
||||
|
||||
def removeNode(self, nodeId: Union[int, str]):
|
||||
"""Tell the node to remove a specific node by ID"""
|
||||
if isinstance(nodeId, str):
|
||||
if nodeId.startswith("!"):
|
||||
nodeId = int(nodeId[1:], 16)
|
||||
else:
|
||||
nodeId = int(nodeId)
|
||||
self.ensureSessionKey()
|
||||
nodeId = to_node_num(nodeId)
|
||||
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.remove_by_nodenum = nodeId
|
||||
@@ -643,11 +831,68 @@ class Node:
|
||||
onResponse = self.onAckNak
|
||||
return self._sendAdmin(p, onResponse=onResponse)
|
||||
|
||||
def setFavorite(self, nodeId: Union[int, str]):
|
||||
"""Tell the node to set the specified node ID to be favorited on the NodeDB on the device"""
|
||||
self.ensureSessionKey()
|
||||
nodeId = to_node_num(nodeId)
|
||||
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.set_favorite_node = nodeId
|
||||
|
||||
if self == self.iface.localNode:
|
||||
onResponse = None
|
||||
else:
|
||||
onResponse = self.onAckNak
|
||||
return self._sendAdmin(p, onResponse=onResponse)
|
||||
|
||||
def removeFavorite(self, nodeId: Union[int, str]):
|
||||
"""Tell the node to set the specified node ID to be un-favorited on the NodeDB on the device"""
|
||||
self.ensureSessionKey()
|
||||
nodeId = to_node_num(nodeId)
|
||||
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.remove_favorite_node = nodeId
|
||||
|
||||
if self == self.iface.localNode:
|
||||
onResponse = None
|
||||
else:
|
||||
onResponse = self.onAckNak
|
||||
return self._sendAdmin(p, onResponse=onResponse)
|
||||
|
||||
def setIgnored(self, nodeId: Union[int, str]):
|
||||
"""Tell the node to set the specified node ID to be ignored on the NodeDB on the device"""
|
||||
self.ensureSessionKey()
|
||||
nodeId = to_node_num(nodeId)
|
||||
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.set_ignored_node = nodeId
|
||||
|
||||
if self == self.iface.localNode:
|
||||
onResponse = None
|
||||
else:
|
||||
onResponse = self.onAckNak
|
||||
return self._sendAdmin(p, onResponse=onResponse)
|
||||
|
||||
def removeIgnored(self, nodeId: Union[int, str]):
|
||||
"""Tell the node to set the specified node ID to be un-ignored on the NodeDB on the device"""
|
||||
self.ensureSessionKey()
|
||||
nodeId = to_node_num(nodeId)
|
||||
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.remove_ignored_node = nodeId
|
||||
|
||||
if self == self.iface.localNode:
|
||||
onResponse = None
|
||||
else:
|
||||
onResponse = self.onAckNak
|
||||
return self._sendAdmin(p, onResponse=onResponse)
|
||||
|
||||
def resetNodeDb(self):
|
||||
"""Tell the node to reset its list of nodes."""
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.nodedb_reset = True
|
||||
logging.info(f"Telling node to reset the NodeDB")
|
||||
logger.info(f"Telling node to reset the NodeDB")
|
||||
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
@@ -658,9 +903,7 @@ class Node:
|
||||
|
||||
def setFixedPosition(self, lat: Union[int, float], lon: Union[int, float], alt: int):
|
||||
"""Tell the node to set fixed position to the provided value and enable the fixed position setting"""
|
||||
if self != self.iface.localNode:
|
||||
logging.error("Setting position of remote nodes is not supported.")
|
||||
return None
|
||||
self.ensureSessionKey()
|
||||
|
||||
p = mesh_pb2.Position()
|
||||
if isinstance(lat, float) and lat != 0.0:
|
||||
@@ -678,15 +921,40 @@ class Node:
|
||||
|
||||
a = admin_pb2.AdminMessage()
|
||||
a.set_fixed_position.CopyFrom(p)
|
||||
return self._sendAdmin(a)
|
||||
|
||||
if self == self.iface.localNode:
|
||||
onResponse = None
|
||||
else:
|
||||
onResponse = self.onAckNak
|
||||
return self._sendAdmin(a, onResponse=onResponse)
|
||||
|
||||
def removeFixedPosition(self):
|
||||
"""Tell the node to remove the fixed position and set the fixed position setting to false"""
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.remove_fixed_position = True
|
||||
logging.info(f"Telling node to remove fixed position")
|
||||
logger.info(f"Telling node to remove fixed position")
|
||||
|
||||
return self._sendAdmin(p)
|
||||
if self == self.iface.localNode:
|
||||
onResponse = None
|
||||
else:
|
||||
onResponse = self.onAckNak
|
||||
return self._sendAdmin(p, onResponse=onResponse)
|
||||
|
||||
def setTime(self, timeSec: int = 0):
|
||||
"""Tell the node to set its time to the provided timestamp, or the system's current time if not provided or 0."""
|
||||
self.ensureSessionKey()
|
||||
if timeSec == 0:
|
||||
timeSec = int(time.time())
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.set_time_only = timeSec
|
||||
logger.info(f"Setting node time to {timeSec}")
|
||||
|
||||
if self == self.iface.localNode:
|
||||
onResponse = None
|
||||
else:
|
||||
onResponse = self.onAckNak
|
||||
return self._sendAdmin(p, onResponse=onResponse)
|
||||
|
||||
def _fixupChannels(self):
|
||||
"""Fixup indexes and add disabled channels as needed"""
|
||||
@@ -712,7 +980,7 @@ class Node:
|
||||
|
||||
def onRequestGetMetadata(self, p):
|
||||
"""Handle the response packet for requesting device metadata getMetadata()"""
|
||||
logging.debug(f"onRequestGetMetadata() p:{p}")
|
||||
logger.debug(f"onRequestGetMetadata() p:{p}")
|
||||
|
||||
if "routing" in p["decoded"]:
|
||||
if p["decoded"]["routing"]["errorReason"] != "NONE":
|
||||
@@ -724,30 +992,42 @@ class Node:
|
||||
portnums_pb2.PortNum.ROUTING_APP
|
||||
):
|
||||
if p["decoded"]["routing"]["errorReason"] != "NONE":
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f'Metadata request failed, error reason: {p["decoded"]["routing"]["errorReason"]}'
|
||||
)
|
||||
self._timeout.expireTime = time.time() # Do not wait any longer
|
||||
return # Don't try to parse this routing message
|
||||
logging.debug(f"Retrying metadata request.")
|
||||
logger.debug(f"Retrying metadata request.")
|
||||
self.getMetadata()
|
||||
return
|
||||
|
||||
c = p["decoded"]["admin"]["raw"].get_device_metadata_response
|
||||
self._timeout.reset() # We made forward progress
|
||||
logging.debug(f"Received metadata {stripnl(c)}")
|
||||
logger.debug(f"Received metadata {stripnl(c)}")
|
||||
print(f"\nfirmware_version: {c.firmware_version}")
|
||||
print(f"device_state_version: {c.device_state_version}")
|
||||
if c.role in config_pb2.Config.DeviceConfig.Role.values():
|
||||
print(f"role: {config_pb2.Config.DeviceConfig.Role.Name(c.role)}")
|
||||
else:
|
||||
print(f"role: {c.role}")
|
||||
print(f"position_flags: {self.position_flags_list(c.position_flags)}")
|
||||
if c.hw_model in mesh_pb2.HardwareModel.values():
|
||||
print(f"hw_model: {mesh_pb2.HardwareModel.Name(c.hw_model)}")
|
||||
else:
|
||||
print(f"hw_model: {c.hw_model}")
|
||||
print(f"hasPKC: {c.hasPKC}")
|
||||
if c.excluded_modules > 0:
|
||||
print(f"excluded_modules: {self.excluded_modules_list(c.excluded_modules)}")
|
||||
|
||||
def onResponseRequestChannel(self, p):
|
||||
"""Handle the response packet for requesting a channel _requestChannel()"""
|
||||
logging.debug(f"onResponseRequestChannel() p:{p}")
|
||||
logger.debug(f"onResponseRequestChannel() p:{p}")
|
||||
|
||||
if p["decoded"]["portnum"] == portnums_pb2.PortNum.Name(
|
||||
portnums_pb2.PortNum.ROUTING_APP
|
||||
):
|
||||
if p["decoded"]["routing"]["errorReason"] != "NONE":
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f'Channel request failed, error reason: {p["decoded"]["routing"]["errorReason"]}'
|
||||
)
|
||||
self._timeout.expireTime = time.time() # Do not wait any longer
|
||||
@@ -755,18 +1035,18 @@ class Node:
|
||||
lastTried = 0
|
||||
if len(self.partialChannels) > 0:
|
||||
lastTried = self.partialChannels[-1].index
|
||||
logging.debug(f"Retrying previous channel request.")
|
||||
logger.debug(f"Retrying previous channel request.")
|
||||
self._requestChannel(lastTried)
|
||||
return
|
||||
|
||||
c = p["decoded"]["admin"]["raw"].get_channel_response
|
||||
self.partialChannels.append(c)
|
||||
self._timeout.reset() # We made forward progress
|
||||
logging.debug(f"Received channel {stripnl(c)}")
|
||||
logger.debug(f"Received channel {stripnl(c)}")
|
||||
index = c.index
|
||||
|
||||
if index >= 8 - 1:
|
||||
logging.debug("Finished downloading channels")
|
||||
logger.debug("Finished downloading channels")
|
||||
|
||||
self.channels = self.partialChannels
|
||||
self._fixupChannels()
|
||||
@@ -801,11 +1081,11 @@ class Node:
|
||||
print(
|
||||
f"Requesting channel {channelNum} info from remote node (this could take a while)"
|
||||
)
|
||||
logging.debug(
|
||||
logger.debug(
|
||||
f"Requesting channel {channelNum} info from remote node (this could take a while)"
|
||||
)
|
||||
else:
|
||||
logging.debug(f"Requesting channel {channelNum}")
|
||||
logger.debug(f"Requesting channel {channelNum}")
|
||||
|
||||
return self._sendAdmin(
|
||||
p, wantResponse=True, onResponse=self.onResponseRequestChannel
|
||||
@@ -822,7 +1102,7 @@ class Node:
|
||||
"""Send an admin message to the specified node (or the local node if destNodeNum is zero)"""
|
||||
|
||||
if self.noProto:
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f"Not sending packet because protocol use is disabled by noProto"
|
||||
)
|
||||
else:
|
||||
@@ -830,14 +1110,45 @@ class Node:
|
||||
adminIndex == 0
|
||||
): # unless a special channel index was used, we want to use the admin index
|
||||
adminIndex = self.iface.localNode._getAdminChannelIndex()
|
||||
logging.debug(f"adminIndex:{adminIndex}")
|
||||
|
||||
logger.debug(f"adminIndex:{adminIndex}")
|
||||
nodeid = to_node_num(self.nodeNum)
|
||||
if "adminSessionPassKey" in self.iface._getOrCreateByNum(nodeid):
|
||||
p.session_passkey = self.iface._getOrCreateByNum(nodeid).get("adminSessionPassKey")
|
||||
return self.iface.sendData(
|
||||
p,
|
||||
self.nodeNum,
|
||||
portNum=portnums_pb2.PortNum.ADMIN_APP,
|
||||
wantAck=False,
|
||||
wantAck=True,
|
||||
wantResponse=wantResponse,
|
||||
onResponse=onResponse,
|
||||
channelIndex=adminIndex,
|
||||
pkiEncrypted=True,
|
||||
)
|
||||
|
||||
def ensureSessionKey(self):
|
||||
"""If our entry in iface.nodesByNum doesn't already have an adminSessionPassKey, make a request to get one"""
|
||||
if self.noProto:
|
||||
logger.warning(
|
||||
f"Not ensuring session key, because protocol use is disabled by noProto"
|
||||
)
|
||||
else:
|
||||
nodeid = to_node_num(self.nodeNum)
|
||||
if self.iface._getOrCreateByNum(nodeid).get("adminSessionPassKey") is None:
|
||||
self.requestConfig(admin_pb2.AdminMessage.SESSIONKEY_CONFIG)
|
||||
|
||||
def get_channels_with_hash(self):
|
||||
"""Return a list of dicts with channel info and hash."""
|
||||
result = []
|
||||
if self.channels:
|
||||
for c in self.channels:
|
||||
if c.settings and hasattr(c.settings, "name") and hasattr(c.settings, "psk"):
|
||||
hash_val = generate_channel_hash(c.settings.name, c.settings.psk)
|
||||
else:
|
||||
hash_val = None
|
||||
result.append({
|
||||
"index": c.index,
|
||||
"role": channel_pb2.Channel.Role.Name(c.role),
|
||||
"name": c.settings.name if c.settings and hasattr(c.settings, "name") else "",
|
||||
"hash": hash_val,
|
||||
})
|
||||
return result
|
||||
|
||||
128
meshtastic/ota.py
Normal file
128
meshtastic/ota.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""Meshtastic ESP32 Unified OTA
|
||||
"""
|
||||
import os
|
||||
import hashlib
|
||||
import socket
|
||||
import logging
|
||||
from typing import Optional, Callable
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _file_sha256(filename: str):
|
||||
"""Calculate SHA256 hash of a file."""
|
||||
sha256_hash = hashlib.sha256()
|
||||
|
||||
with open(filename, "rb") as f:
|
||||
for byte_block in iter(lambda: f.read(4096), b""):
|
||||
sha256_hash.update(byte_block)
|
||||
|
||||
return sha256_hash
|
||||
|
||||
|
||||
class OTAError(Exception):
|
||||
"""Exception for OTA errors."""
|
||||
|
||||
|
||||
class ESP32WiFiOTA:
|
||||
"""ESP32 WiFi Unified OTA updates."""
|
||||
|
||||
def __init__(self, filename: str, hostname: str, port: int = 3232):
|
||||
self._filename = filename
|
||||
self._hostname = hostname
|
||||
self._port = port
|
||||
self._socket: Optional[socket.socket] = None
|
||||
|
||||
if not os.path.exists(self._filename):
|
||||
raise FileNotFoundError(f"File {self._filename} does not exist")
|
||||
|
||||
self._file_hash = _file_sha256(self._filename)
|
||||
|
||||
def _read_line(self) -> str:
|
||||
"""Read a line from the socket."""
|
||||
if not self._socket:
|
||||
raise ConnectionError("Socket not connected")
|
||||
|
||||
line = b""
|
||||
while not line.endswith(b"\n"):
|
||||
char = self._socket.recv(1)
|
||||
|
||||
if not char:
|
||||
raise ConnectionError("Connection closed while waiting for response")
|
||||
|
||||
line += char
|
||||
|
||||
return line.decode("utf-8").strip()
|
||||
|
||||
def hash_bytes(self) -> bytes:
|
||||
"""Return the hash as bytes."""
|
||||
return self._file_hash.digest()
|
||||
|
||||
def hash_hex(self) -> str:
|
||||
"""Return the hash as a hex string."""
|
||||
return self._file_hash.hexdigest()
|
||||
|
||||
def update(self, progress_callback: Optional[Callable[[int, int], None]] = None):
|
||||
"""Perform the OTA update."""
|
||||
with open(self._filename, "rb") as f:
|
||||
data = f.read()
|
||||
size = len(data)
|
||||
|
||||
logger.info(f"Starting OTA update with {self._filename} ({size} bytes, hash {self.hash_hex()})")
|
||||
|
||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self._socket.settimeout(15)
|
||||
try:
|
||||
self._socket.connect((self._hostname, self._port))
|
||||
logger.debug(f"Connected to {self._hostname}:{self._port}")
|
||||
|
||||
# Send start command
|
||||
self._socket.sendall(f"OTA {size} {self.hash_hex()}\n".encode("utf-8"))
|
||||
|
||||
# Wait for OK from the device
|
||||
while True:
|
||||
response = self._read_line()
|
||||
if response == "OK":
|
||||
break
|
||||
|
||||
if response == "ERASING":
|
||||
logger.info("Device is erasing flash...")
|
||||
elif response.startswith("ERR "):
|
||||
raise OTAError(f"Device reported error: {response}")
|
||||
else:
|
||||
logger.warning(f"Unexpected response: {response}")
|
||||
|
||||
# Stream firmware
|
||||
sent_bytes = 0
|
||||
chunk_size = 1024
|
||||
while sent_bytes < size:
|
||||
chunk = data[sent_bytes : sent_bytes + chunk_size]
|
||||
self._socket.sendall(chunk)
|
||||
sent_bytes += len(chunk)
|
||||
|
||||
if progress_callback:
|
||||
progress_callback(sent_bytes, size)
|
||||
else:
|
||||
print(f"[{sent_bytes / size * 100:5.1f}%] Sent {sent_bytes} of {size} bytes...", end="\r")
|
||||
|
||||
if not progress_callback:
|
||||
print()
|
||||
|
||||
# Wait for OK from device
|
||||
logger.info("Firmware sent, waiting for verification...")
|
||||
while True:
|
||||
response = self._read_line()
|
||||
if response == "OK":
|
||||
logger.info("OTA update completed successfully!")
|
||||
break
|
||||
|
||||
if response.startswith("ERR "):
|
||||
raise OTAError(f"OTA update failed: {response}")
|
||||
elif response != "ACK":
|
||||
logger.warning(f"Unexpected final response: {response}")
|
||||
|
||||
finally:
|
||||
if self._socket:
|
||||
self._socket.close()
|
||||
self._socket = None
|
||||
84
meshtastic/protobuf/admin_pb2.py
generated
84
meshtastic/protobuf/admin_pb2.py
generated
File diff suppressed because one or more lines are too long
730
meshtastic/protobuf/admin_pb2.pyi
generated
730
meshtastic/protobuf/admin_pb2.pyi
generated
@@ -12,6 +12,7 @@ import google.protobuf.message
|
||||
import meshtastic.protobuf.channel_pb2
|
||||
import meshtastic.protobuf.config_pb2
|
||||
import meshtastic.protobuf.connection_status_pb2
|
||||
import meshtastic.protobuf.device_ui_pb2
|
||||
import meshtastic.protobuf.mesh_pb2
|
||||
import meshtastic.protobuf.module_config_pb2
|
||||
import sys
|
||||
@@ -24,6 +25,44 @@ else:
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
|
||||
|
||||
class _OTAMode:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _OTAModeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_OTAMode.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
NO_REBOOT_OTA: _OTAMode.ValueType # 0
|
||||
"""
|
||||
Do not reboot into OTA mode
|
||||
"""
|
||||
OTA_BLE: _OTAMode.ValueType # 1
|
||||
"""
|
||||
Reboot into OTA mode for BLE firmware update
|
||||
"""
|
||||
OTA_WIFI: _OTAMode.ValueType # 2
|
||||
"""
|
||||
Reboot into OTA mode for WiFi firmware update
|
||||
"""
|
||||
|
||||
class OTAMode(_OTAMode, metaclass=_OTAModeEnumTypeWrapper):
|
||||
"""
|
||||
Firmware update mode for OTA updates
|
||||
"""
|
||||
|
||||
NO_REBOOT_OTA: OTAMode.ValueType # 0
|
||||
"""
|
||||
Do not reboot into OTA mode
|
||||
"""
|
||||
OTA_BLE: OTAMode.ValueType # 1
|
||||
"""
|
||||
Reboot into OTA mode for BLE firmware update
|
||||
"""
|
||||
OTA_WIFI: OTAMode.ValueType # 2
|
||||
"""
|
||||
Reboot into OTA mode for WiFi firmware update
|
||||
"""
|
||||
global___OTAMode = OTAMode
|
||||
|
||||
@typing.final
|
||||
class AdminMessage(google.protobuf.message.Message):
|
||||
"""
|
||||
@@ -68,6 +107,18 @@ class AdminMessage(google.protobuf.message.Message):
|
||||
"""
|
||||
TODO: REPLACE
|
||||
"""
|
||||
SECURITY_CONFIG: AdminMessage._ConfigType.ValueType # 7
|
||||
"""
|
||||
TODO: REPLACE
|
||||
"""
|
||||
SESSIONKEY_CONFIG: AdminMessage._ConfigType.ValueType # 8
|
||||
"""
|
||||
Session key config
|
||||
"""
|
||||
DEVICEUI_CONFIG: AdminMessage._ConfigType.ValueType # 9
|
||||
"""
|
||||
device-ui config
|
||||
"""
|
||||
|
||||
class ConfigType(_ConfigType, metaclass=_ConfigTypeEnumTypeWrapper):
|
||||
"""
|
||||
@@ -102,6 +153,18 @@ class AdminMessage(google.protobuf.message.Message):
|
||||
"""
|
||||
TODO: REPLACE
|
||||
"""
|
||||
SECURITY_CONFIG: AdminMessage.ConfigType.ValueType # 7
|
||||
"""
|
||||
TODO: REPLACE
|
||||
"""
|
||||
SESSIONKEY_CONFIG: AdminMessage.ConfigType.ValueType # 8
|
||||
"""
|
||||
Session key config
|
||||
"""
|
||||
DEVICEUI_CONFIG: AdminMessage.ConfigType.ValueType # 9
|
||||
"""
|
||||
device-ui config
|
||||
"""
|
||||
|
||||
class _ModuleConfigType:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
@@ -161,6 +224,18 @@ class AdminMessage(google.protobuf.message.Message):
|
||||
"""
|
||||
TODO: REPLACE
|
||||
"""
|
||||
STATUSMESSAGE_CONFIG: AdminMessage._ModuleConfigType.ValueType # 13
|
||||
"""
|
||||
TODO: REPLACE
|
||||
"""
|
||||
TRAFFICMANAGEMENT_CONFIG: AdminMessage._ModuleConfigType.ValueType # 14
|
||||
"""
|
||||
Traffic management module config
|
||||
"""
|
||||
TAK_CONFIG: AdminMessage._ModuleConfigType.ValueType # 15
|
||||
"""
|
||||
TAK module config
|
||||
"""
|
||||
|
||||
class ModuleConfigType(_ModuleConfigType, metaclass=_ModuleConfigTypeEnumTypeWrapper):
|
||||
"""
|
||||
@@ -219,7 +294,111 @@ class AdminMessage(google.protobuf.message.Message):
|
||||
"""
|
||||
TODO: REPLACE
|
||||
"""
|
||||
STATUSMESSAGE_CONFIG: AdminMessage.ModuleConfigType.ValueType # 13
|
||||
"""
|
||||
TODO: REPLACE
|
||||
"""
|
||||
TRAFFICMANAGEMENT_CONFIG: AdminMessage.ModuleConfigType.ValueType # 14
|
||||
"""
|
||||
Traffic management module config
|
||||
"""
|
||||
TAK_CONFIG: AdminMessage.ModuleConfigType.ValueType # 15
|
||||
"""
|
||||
TAK module config
|
||||
"""
|
||||
|
||||
class _BackupLocation:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _BackupLocationEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[AdminMessage._BackupLocation.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
FLASH: AdminMessage._BackupLocation.ValueType # 0
|
||||
"""
|
||||
Backup to the internal flash
|
||||
"""
|
||||
SD: AdminMessage._BackupLocation.ValueType # 1
|
||||
"""
|
||||
Backup to the SD card
|
||||
"""
|
||||
|
||||
class BackupLocation(_BackupLocation, metaclass=_BackupLocationEnumTypeWrapper): ...
|
||||
FLASH: AdminMessage.BackupLocation.ValueType # 0
|
||||
"""
|
||||
Backup to the internal flash
|
||||
"""
|
||||
SD: AdminMessage.BackupLocation.ValueType # 1
|
||||
"""
|
||||
Backup to the SD card
|
||||
"""
|
||||
|
||||
@typing.final
|
||||
class InputEvent(google.protobuf.message.Message):
|
||||
"""
|
||||
Input event message to be sent to the node.
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
EVENT_CODE_FIELD_NUMBER: builtins.int
|
||||
KB_CHAR_FIELD_NUMBER: builtins.int
|
||||
TOUCH_X_FIELD_NUMBER: builtins.int
|
||||
TOUCH_Y_FIELD_NUMBER: builtins.int
|
||||
event_code: builtins.int
|
||||
"""
|
||||
The input event code
|
||||
"""
|
||||
kb_char: builtins.int
|
||||
"""
|
||||
Keyboard character code
|
||||
"""
|
||||
touch_x: builtins.int
|
||||
"""
|
||||
The touch X coordinate
|
||||
"""
|
||||
touch_y: builtins.int
|
||||
"""
|
||||
The touch Y coordinate
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
event_code: builtins.int = ...,
|
||||
kb_char: builtins.int = ...,
|
||||
touch_x: builtins.int = ...,
|
||||
touch_y: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["event_code", b"event_code", "kb_char", b"kb_char", "touch_x", b"touch_x", "touch_y", b"touch_y"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class OTAEvent(google.protobuf.message.Message):
|
||||
"""
|
||||
User is requesting an over the air update.
|
||||
Node will reboot into the OTA loader
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
REBOOT_OTA_MODE_FIELD_NUMBER: builtins.int
|
||||
OTA_HASH_FIELD_NUMBER: builtins.int
|
||||
reboot_ota_mode: global___OTAMode.ValueType
|
||||
"""
|
||||
Tell the node to reboot into OTA mode for firmware update via BLE or WiFi (ESP32 only for now)
|
||||
"""
|
||||
ota_hash: builtins.bytes
|
||||
"""
|
||||
A 32 byte hash of the OTA firmware.
|
||||
Used to verify the integrity of the firmware before applying an update.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
reboot_ota_mode: global___OTAMode.ValueType = ...,
|
||||
ota_hash: builtins.bytes = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["ota_hash", b"ota_hash", "reboot_ota_mode", b"reboot_ota_mode"]) -> None: ...
|
||||
|
||||
SESSION_PASSKEY_FIELD_NUMBER: builtins.int
|
||||
GET_CHANNEL_REQUEST_FIELD_NUMBER: builtins.int
|
||||
GET_CHANNEL_RESPONSE_FIELD_NUMBER: builtins.int
|
||||
GET_OWNER_REQUEST_FIELD_NUMBER: builtins.int
|
||||
@@ -242,6 +421,10 @@ class AdminMessage(google.protobuf.message.Message):
|
||||
ENTER_DFU_MODE_REQUEST_FIELD_NUMBER: builtins.int
|
||||
DELETE_FILE_REQUEST_FIELD_NUMBER: builtins.int
|
||||
SET_SCALE_FIELD_NUMBER: builtins.int
|
||||
BACKUP_PREFERENCES_FIELD_NUMBER: builtins.int
|
||||
RESTORE_PREFERENCES_FIELD_NUMBER: builtins.int
|
||||
REMOVE_BACKUP_PREFERENCES_FIELD_NUMBER: builtins.int
|
||||
SEND_INPUT_EVENT_FIELD_NUMBER: builtins.int
|
||||
SET_OWNER_FIELD_NUMBER: builtins.int
|
||||
SET_CHANNEL_FIELD_NUMBER: builtins.int
|
||||
SET_CONFIG_FIELD_NUMBER: builtins.int
|
||||
@@ -253,8 +436,17 @@ class AdminMessage(google.protobuf.message.Message):
|
||||
REMOVE_FAVORITE_NODE_FIELD_NUMBER: builtins.int
|
||||
SET_FIXED_POSITION_FIELD_NUMBER: builtins.int
|
||||
REMOVE_FIXED_POSITION_FIELD_NUMBER: builtins.int
|
||||
SET_TIME_ONLY_FIELD_NUMBER: builtins.int
|
||||
GET_UI_CONFIG_REQUEST_FIELD_NUMBER: builtins.int
|
||||
GET_UI_CONFIG_RESPONSE_FIELD_NUMBER: builtins.int
|
||||
STORE_UI_CONFIG_FIELD_NUMBER: builtins.int
|
||||
SET_IGNORED_NODE_FIELD_NUMBER: builtins.int
|
||||
REMOVE_IGNORED_NODE_FIELD_NUMBER: builtins.int
|
||||
TOGGLE_MUTED_NODE_FIELD_NUMBER: builtins.int
|
||||
BEGIN_EDIT_SETTINGS_FIELD_NUMBER: builtins.int
|
||||
COMMIT_EDIT_SETTINGS_FIELD_NUMBER: builtins.int
|
||||
ADD_CONTACT_FIELD_NUMBER: builtins.int
|
||||
KEY_VERIFICATION_FIELD_NUMBER: builtins.int
|
||||
FACTORY_RESET_DEVICE_FIELD_NUMBER: builtins.int
|
||||
REBOOT_OTA_SECONDS_FIELD_NUMBER: builtins.int
|
||||
EXIT_SIMULATOR_FIELD_NUMBER: builtins.int
|
||||
@@ -262,6 +454,15 @@ class AdminMessage(google.protobuf.message.Message):
|
||||
SHUTDOWN_SECONDS_FIELD_NUMBER: builtins.int
|
||||
FACTORY_RESET_CONFIG_FIELD_NUMBER: builtins.int
|
||||
NODEDB_RESET_FIELD_NUMBER: builtins.int
|
||||
OTA_REQUEST_FIELD_NUMBER: builtins.int
|
||||
SENSOR_CONFIG_FIELD_NUMBER: builtins.int
|
||||
LOCKDOWN_AUTH_FIELD_NUMBER: builtins.int
|
||||
session_passkey: builtins.bytes
|
||||
"""
|
||||
The node generates this key and sends it with any get_x_response packets.
|
||||
The client MUST include the same key with any set_x commands. Key expires after 300 seconds.
|
||||
Prevents replay attacks for admin messages.
|
||||
"""
|
||||
get_channel_request: builtins.int
|
||||
"""
|
||||
Send the specified channel in the response to this message
|
||||
@@ -320,6 +521,18 @@ class AdminMessage(google.protobuf.message.Message):
|
||||
"""
|
||||
Set zero and offset for scale chips
|
||||
"""
|
||||
backup_preferences: global___AdminMessage.BackupLocation.ValueType
|
||||
"""
|
||||
Backup the node's preferences
|
||||
"""
|
||||
restore_preferences: global___AdminMessage.BackupLocation.ValueType
|
||||
"""
|
||||
Restore the node's preferences
|
||||
"""
|
||||
remove_backup_preferences: global___AdminMessage.BackupLocation.ValueType
|
||||
"""
|
||||
Remove backups of the node's preferences
|
||||
"""
|
||||
set_canned_message_module_messages: builtins.str
|
||||
"""
|
||||
Set the Canned Message Module messages text.
|
||||
@@ -344,6 +557,27 @@ class AdminMessage(google.protobuf.message.Message):
|
||||
"""
|
||||
Clear fixed position coordinates and then set position.fixed_position = false
|
||||
"""
|
||||
set_time_only: builtins.int
|
||||
"""
|
||||
Set time only on the node
|
||||
Convenience method to set the time on the node (as Net quality) without any other position data
|
||||
"""
|
||||
get_ui_config_request: builtins.bool
|
||||
"""
|
||||
Tell the node to send the stored ui data.
|
||||
"""
|
||||
set_ignored_node: builtins.int
|
||||
"""
|
||||
Set specified node-num to be ignored on the NodeDB on the device
|
||||
"""
|
||||
remove_ignored_node: builtins.int
|
||||
"""
|
||||
Set specified node-num to be un-ignored on the NodeDB on the device
|
||||
"""
|
||||
toggle_muted_node: builtins.int
|
||||
"""
|
||||
Set specified node-num to be muted
|
||||
"""
|
||||
begin_edit_settings: builtins.bool
|
||||
"""
|
||||
Begins an edit transaction for config, module config, owner, and channel settings changes
|
||||
@@ -361,6 +595,7 @@ class AdminMessage(google.protobuf.message.Message):
|
||||
"""
|
||||
Tell the node to reboot into the OTA Firmware in this many seconds (or <0 to cancel reboot)
|
||||
Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth.
|
||||
Deprecated in favor of reboot_ota_mode in 2.7.17
|
||||
"""
|
||||
exit_simulator: builtins.bool
|
||||
"""
|
||||
@@ -379,9 +614,10 @@ class AdminMessage(google.protobuf.message.Message):
|
||||
"""
|
||||
Tell the node to factory reset config; all device state and configuration will be returned to factory defaults; BLE bonds will be preserved.
|
||||
"""
|
||||
nodedb_reset: builtins.int
|
||||
nodedb_reset: builtins.bool
|
||||
"""
|
||||
Tell the node to reset the nodedb.
|
||||
When true, favorites are preserved through reset.
|
||||
"""
|
||||
@property
|
||||
def get_channel_response(self) -> meshtastic.protobuf.channel_pb2.Channel:
|
||||
@@ -431,6 +667,13 @@ class AdminMessage(google.protobuf.message.Message):
|
||||
Respond with the mesh's nodes with their available gpio pins for RemoteHardware module use
|
||||
"""
|
||||
|
||||
@property
|
||||
def send_input_event(self) -> global___AdminMessage.InputEvent:
|
||||
"""
|
||||
Send an input event to the node.
|
||||
This is used to trigger physical input events like button presses, touch events, etc.
|
||||
"""
|
||||
|
||||
@property
|
||||
def set_owner(self) -> meshtastic.protobuf.mesh_pb2.User:
|
||||
"""
|
||||
@@ -465,9 +708,59 @@ class AdminMessage(google.protobuf.message.Message):
|
||||
Set fixed position data on the node and then set the position.fixed_position = true
|
||||
"""
|
||||
|
||||
@property
|
||||
def get_ui_config_response(self) -> meshtastic.protobuf.device_ui_pb2.DeviceUIConfig:
|
||||
"""
|
||||
Reply stored device ui data.
|
||||
"""
|
||||
|
||||
@property
|
||||
def store_ui_config(self) -> meshtastic.protobuf.device_ui_pb2.DeviceUIConfig:
|
||||
"""
|
||||
Tell the node to store UI data persistently.
|
||||
"""
|
||||
|
||||
@property
|
||||
def add_contact(self) -> global___SharedContact:
|
||||
"""
|
||||
Add a contact (User) to the nodedb
|
||||
"""
|
||||
|
||||
@property
|
||||
def key_verification(self) -> global___KeyVerificationAdmin:
|
||||
"""
|
||||
Initiate or respond to a key verification request
|
||||
"""
|
||||
|
||||
@property
|
||||
def ota_request(self) -> global___AdminMessage.OTAEvent:
|
||||
"""
|
||||
Tell the node to reset into the OTA Loader
|
||||
"""
|
||||
|
||||
@property
|
||||
def sensor_config(self) -> global___SensorConfig:
|
||||
"""
|
||||
Parameters and sensor configuration
|
||||
"""
|
||||
|
||||
@property
|
||||
def lockdown_auth(self) -> global___LockdownAuth:
|
||||
"""
|
||||
Lockdown passphrase delivery / unlock / lock-now command for hardened
|
||||
firmware builds (see MESHTASTIC_LOCKDOWN). Used to provision the
|
||||
passphrase on first boot, unlock encrypted storage on subsequent
|
||||
reboots, re-verify on already-unlocked devices to authorize a new
|
||||
client connection, or immediately re-lock the device.
|
||||
|
||||
Replaces the earlier scheme that repurposed SecurityConfig.private_key
|
||||
to carry passphrase bytes; that hack is retired.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
session_passkey: builtins.bytes = ...,
|
||||
get_channel_request: builtins.int = ...,
|
||||
get_channel_response: meshtastic.protobuf.channel_pb2.Channel | None = ...,
|
||||
get_owner_request: builtins.bool = ...,
|
||||
@@ -490,6 +783,10 @@ class AdminMessage(google.protobuf.message.Message):
|
||||
enter_dfu_mode_request: builtins.bool = ...,
|
||||
delete_file_request: builtins.str = ...,
|
||||
set_scale: builtins.int = ...,
|
||||
backup_preferences: global___AdminMessage.BackupLocation.ValueType = ...,
|
||||
restore_preferences: global___AdminMessage.BackupLocation.ValueType = ...,
|
||||
remove_backup_preferences: global___AdminMessage.BackupLocation.ValueType = ...,
|
||||
send_input_event: global___AdminMessage.InputEvent | None = ...,
|
||||
set_owner: meshtastic.protobuf.mesh_pb2.User | None = ...,
|
||||
set_channel: meshtastic.protobuf.channel_pb2.Channel | None = ...,
|
||||
set_config: meshtastic.protobuf.config_pb2.Config | None = ...,
|
||||
@@ -501,22 +798,96 @@ class AdminMessage(google.protobuf.message.Message):
|
||||
remove_favorite_node: builtins.int = ...,
|
||||
set_fixed_position: meshtastic.protobuf.mesh_pb2.Position | None = ...,
|
||||
remove_fixed_position: builtins.bool = ...,
|
||||
set_time_only: builtins.int = ...,
|
||||
get_ui_config_request: builtins.bool = ...,
|
||||
get_ui_config_response: meshtastic.protobuf.device_ui_pb2.DeviceUIConfig | None = ...,
|
||||
store_ui_config: meshtastic.protobuf.device_ui_pb2.DeviceUIConfig | None = ...,
|
||||
set_ignored_node: builtins.int = ...,
|
||||
remove_ignored_node: builtins.int = ...,
|
||||
toggle_muted_node: builtins.int = ...,
|
||||
begin_edit_settings: builtins.bool = ...,
|
||||
commit_edit_settings: builtins.bool = ...,
|
||||
add_contact: global___SharedContact | None = ...,
|
||||
key_verification: global___KeyVerificationAdmin | None = ...,
|
||||
factory_reset_device: builtins.int = ...,
|
||||
reboot_ota_seconds: builtins.int = ...,
|
||||
exit_simulator: builtins.bool = ...,
|
||||
reboot_seconds: builtins.int = ...,
|
||||
shutdown_seconds: builtins.int = ...,
|
||||
factory_reset_config: builtins.int = ...,
|
||||
nodedb_reset: builtins.int = ...,
|
||||
nodedb_reset: builtins.bool = ...,
|
||||
ota_request: global___AdminMessage.OTAEvent | None = ...,
|
||||
sensor_config: global___SensorConfig | None = ...,
|
||||
lockdown_auth: global___LockdownAuth | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset_config", b"factory_reset_config", "factory_reset_device", b"factory_reset_device", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "nodedb_reset", b"nodedb_reset", "payload_variant", b"payload_variant", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "shutdown_seconds", b"shutdown_seconds"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset_config", b"factory_reset_config", "factory_reset_device", b"factory_reset_device", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "nodedb_reset", b"nodedb_reset", "payload_variant", b"payload_variant", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "shutdown_seconds", b"shutdown_seconds"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["get_channel_request", "get_channel_response", "get_owner_request", "get_owner_response", "get_config_request", "get_config_response", "get_module_config_request", "get_module_config_response", "get_canned_message_module_messages_request", "get_canned_message_module_messages_response", "get_device_metadata_request", "get_device_metadata_response", "get_ringtone_request", "get_ringtone_response", "get_device_connection_status_request", "get_device_connection_status_response", "set_ham_mode", "get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", "enter_dfu_mode_request", "delete_file_request", "set_scale", "set_owner", "set_channel", "set_config", "set_module_config", "set_canned_message_module_messages", "set_ringtone_message", "remove_by_nodenum", "set_favorite_node", "remove_favorite_node", "set_fixed_position", "remove_fixed_position", "begin_edit_settings", "commit_edit_settings", "factory_reset_device", "reboot_ota_seconds", "exit_simulator", "reboot_seconds", "shutdown_seconds", "factory_reset_config", "nodedb_reset"] | None: ...
|
||||
def HasField(self, field_name: typing.Literal["add_contact", b"add_contact", "backup_preferences", b"backup_preferences", "begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset_config", b"factory_reset_config", "factory_reset_device", b"factory_reset_device", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "get_ui_config_request", b"get_ui_config_request", "get_ui_config_response", b"get_ui_config_response", "key_verification", b"key_verification", "lockdown_auth", b"lockdown_auth", "nodedb_reset", b"nodedb_reset", "ota_request", b"ota_request", "payload_variant", b"payload_variant", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_backup_preferences", b"remove_backup_preferences", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "remove_ignored_node", b"remove_ignored_node", "restore_preferences", b"restore_preferences", "send_input_event", b"send_input_event", "sensor_config", b"sensor_config", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_ignored_node", b"set_ignored_node", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "set_time_only", b"set_time_only", "shutdown_seconds", b"shutdown_seconds", "store_ui_config", b"store_ui_config", "toggle_muted_node", b"toggle_muted_node"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["add_contact", b"add_contact", "backup_preferences", b"backup_preferences", "begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset_config", b"factory_reset_config", "factory_reset_device", b"factory_reset_device", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "get_ui_config_request", b"get_ui_config_request", "get_ui_config_response", b"get_ui_config_response", "key_verification", b"key_verification", "lockdown_auth", b"lockdown_auth", "nodedb_reset", b"nodedb_reset", "ota_request", b"ota_request", "payload_variant", b"payload_variant", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_backup_preferences", b"remove_backup_preferences", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "remove_ignored_node", b"remove_ignored_node", "restore_preferences", b"restore_preferences", "send_input_event", b"send_input_event", "sensor_config", b"sensor_config", "session_passkey", b"session_passkey", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_ignored_node", b"set_ignored_node", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "set_time_only", b"set_time_only", "shutdown_seconds", b"shutdown_seconds", "store_ui_config", b"store_ui_config", "toggle_muted_node", b"toggle_muted_node"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["get_channel_request", "get_channel_response", "get_owner_request", "get_owner_response", "get_config_request", "get_config_response", "get_module_config_request", "get_module_config_response", "get_canned_message_module_messages_request", "get_canned_message_module_messages_response", "get_device_metadata_request", "get_device_metadata_response", "get_ringtone_request", "get_ringtone_response", "get_device_connection_status_request", "get_device_connection_status_response", "set_ham_mode", "get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", "enter_dfu_mode_request", "delete_file_request", "set_scale", "backup_preferences", "restore_preferences", "remove_backup_preferences", "send_input_event", "set_owner", "set_channel", "set_config", "set_module_config", "set_canned_message_module_messages", "set_ringtone_message", "remove_by_nodenum", "set_favorite_node", "remove_favorite_node", "set_fixed_position", "remove_fixed_position", "set_time_only", "get_ui_config_request", "get_ui_config_response", "store_ui_config", "set_ignored_node", "remove_ignored_node", "toggle_muted_node", "begin_edit_settings", "commit_edit_settings", "add_contact", "key_verification", "factory_reset_device", "reboot_ota_seconds", "exit_simulator", "reboot_seconds", "shutdown_seconds", "factory_reset_config", "nodedb_reset", "ota_request", "sensor_config", "lockdown_auth"] | None: ...
|
||||
|
||||
global___AdminMessage = AdminMessage
|
||||
|
||||
@typing.final
|
||||
class LockdownAuth(google.protobuf.message.Message):
|
||||
"""
|
||||
Lockdown passphrase delivery payload.
|
||||
|
||||
One message handles three operations distinguished by content:
|
||||
- Provision (first-time): passphrase set, lock_now=false. Firmware
|
||||
generates DEK, wraps with passphrase-derived KEK, persists.
|
||||
- Unlock: passphrase set, lock_now=false. Firmware verifies
|
||||
passphrase against stored DEK, unlocks storage, authorizes the
|
||||
connection that delivered this packet.
|
||||
- Lock now: lock_now=true, passphrase ignored. Firmware revokes
|
||||
all client auth and reboots into the locked state.
|
||||
|
||||
Firmware decides between provision and unlock based on its own state
|
||||
(whether a DEK file already exists). Clients do not need to track
|
||||
which case applies.
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
PASSPHRASE_FIELD_NUMBER: builtins.int
|
||||
BOOTS_REMAINING_FIELD_NUMBER: builtins.int
|
||||
VALID_UNTIL_EPOCH_FIELD_NUMBER: builtins.int
|
||||
LOCK_NOW_FIELD_NUMBER: builtins.int
|
||||
passphrase: builtins.bytes
|
||||
"""
|
||||
Passphrase bytes (1-32). Empty when lock_now is true.
|
||||
Capped to 32 to match the proto cap on related security fields.
|
||||
"""
|
||||
boots_remaining: builtins.int
|
||||
"""
|
||||
Optional override of the boot-count token TTL granted on success.
|
||||
0 = use firmware default (TOKEN_DEFAULT_BOOTS).
|
||||
On reboot the firmware decrements this; when it reaches 0 the
|
||||
device boots fully locked and requires a fresh passphrase.
|
||||
"""
|
||||
valid_until_epoch: builtins.int
|
||||
"""
|
||||
Optional wall-clock expiry for the unlock token, as absolute
|
||||
Unix-epoch seconds. 0 = no time limit (only the boot-count TTL
|
||||
applies). On boot, if the device RTC is set and now > this value,
|
||||
the token is treated as expired.
|
||||
"""
|
||||
lock_now: builtins.bool
|
||||
"""
|
||||
If true, ignore passphrase fields, immediately revoke all
|
||||
connection-level admin authorization, and reboot the device into
|
||||
the locked state. Always honoured regardless of current lock state.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
passphrase: builtins.bytes = ...,
|
||||
boots_remaining: builtins.int = ...,
|
||||
valid_until_epoch: builtins.int = ...,
|
||||
lock_now: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["boots_remaining", b"boots_remaining", "lock_now", b"lock_now", "passphrase", b"passphrase", "valid_until_epoch", b"valid_until_epoch"]) -> None: ...
|
||||
|
||||
global___LockdownAuth = LockdownAuth
|
||||
|
||||
@typing.final
|
||||
class HamParameters(google.protobuf.message.Message):
|
||||
"""
|
||||
@@ -582,3 +953,352 @@ class NodeRemoteHardwarePinsResponse(google.protobuf.message.Message):
|
||||
def ClearField(self, field_name: typing.Literal["node_remote_hardware_pins", b"node_remote_hardware_pins"]) -> None: ...
|
||||
|
||||
global___NodeRemoteHardwarePinsResponse = NodeRemoteHardwarePinsResponse
|
||||
|
||||
@typing.final
|
||||
class SharedContact(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
NODE_NUM_FIELD_NUMBER: builtins.int
|
||||
USER_FIELD_NUMBER: builtins.int
|
||||
SHOULD_IGNORE_FIELD_NUMBER: builtins.int
|
||||
MANUALLY_VERIFIED_FIELD_NUMBER: builtins.int
|
||||
node_num: builtins.int
|
||||
"""
|
||||
The node number of the contact
|
||||
"""
|
||||
should_ignore: builtins.bool
|
||||
"""
|
||||
Add this contact to the blocked / ignored list
|
||||
"""
|
||||
manually_verified: builtins.bool
|
||||
"""
|
||||
Set the IS_KEY_MANUALLY_VERIFIED bit
|
||||
"""
|
||||
@property
|
||||
def user(self) -> meshtastic.protobuf.mesh_pb2.User:
|
||||
"""
|
||||
The User of the contact
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
node_num: builtins.int = ...,
|
||||
user: meshtastic.protobuf.mesh_pb2.User | None = ...,
|
||||
should_ignore: builtins.bool = ...,
|
||||
manually_verified: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["user", b"user"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["manually_verified", b"manually_verified", "node_num", b"node_num", "should_ignore", b"should_ignore", "user", b"user"]) -> None: ...
|
||||
|
||||
global___SharedContact = SharedContact
|
||||
|
||||
@typing.final
|
||||
class KeyVerificationAdmin(google.protobuf.message.Message):
|
||||
"""
|
||||
This message is used by a client to initiate or complete a key verification
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
class _MessageType:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _MessageTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[KeyVerificationAdmin._MessageType.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
INITIATE_VERIFICATION: KeyVerificationAdmin._MessageType.ValueType # 0
|
||||
"""
|
||||
This is the first stage, where a client initiates
|
||||
"""
|
||||
PROVIDE_SECURITY_NUMBER: KeyVerificationAdmin._MessageType.ValueType # 1
|
||||
"""
|
||||
After the nonce has been returned over the mesh, the client prompts for the security number
|
||||
And uses this message to provide it to the node.
|
||||
"""
|
||||
DO_VERIFY: KeyVerificationAdmin._MessageType.ValueType # 2
|
||||
"""
|
||||
Once the user has compared the verification message, this message notifies the node.
|
||||
"""
|
||||
DO_NOT_VERIFY: KeyVerificationAdmin._MessageType.ValueType # 3
|
||||
"""
|
||||
This is the cancel path, can be taken at any point
|
||||
"""
|
||||
|
||||
class MessageType(_MessageType, metaclass=_MessageTypeEnumTypeWrapper):
|
||||
"""
|
||||
Three stages of this request.
|
||||
"""
|
||||
|
||||
INITIATE_VERIFICATION: KeyVerificationAdmin.MessageType.ValueType # 0
|
||||
"""
|
||||
This is the first stage, where a client initiates
|
||||
"""
|
||||
PROVIDE_SECURITY_NUMBER: KeyVerificationAdmin.MessageType.ValueType # 1
|
||||
"""
|
||||
After the nonce has been returned over the mesh, the client prompts for the security number
|
||||
And uses this message to provide it to the node.
|
||||
"""
|
||||
DO_VERIFY: KeyVerificationAdmin.MessageType.ValueType # 2
|
||||
"""
|
||||
Once the user has compared the verification message, this message notifies the node.
|
||||
"""
|
||||
DO_NOT_VERIFY: KeyVerificationAdmin.MessageType.ValueType # 3
|
||||
"""
|
||||
This is the cancel path, can be taken at any point
|
||||
"""
|
||||
|
||||
MESSAGE_TYPE_FIELD_NUMBER: builtins.int
|
||||
REMOTE_NODENUM_FIELD_NUMBER: builtins.int
|
||||
NONCE_FIELD_NUMBER: builtins.int
|
||||
SECURITY_NUMBER_FIELD_NUMBER: builtins.int
|
||||
message_type: global___KeyVerificationAdmin.MessageType.ValueType
|
||||
remote_nodenum: builtins.int
|
||||
"""
|
||||
The nodenum we're requesting
|
||||
"""
|
||||
nonce: builtins.int
|
||||
"""
|
||||
The nonce is used to track the connection
|
||||
"""
|
||||
security_number: builtins.int
|
||||
"""
|
||||
The 4 digit code generated by the remote node, and communicated outside the mesh
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
message_type: global___KeyVerificationAdmin.MessageType.ValueType = ...,
|
||||
remote_nodenum: builtins.int = ...,
|
||||
nonce: builtins.int = ...,
|
||||
security_number: builtins.int | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_security_number", b"_security_number", "security_number", b"security_number"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_security_number", b"_security_number", "message_type", b"message_type", "nonce", b"nonce", "remote_nodenum", b"remote_nodenum", "security_number", b"security_number"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_security_number", b"_security_number"]) -> typing.Literal["security_number"] | None: ...
|
||||
|
||||
global___KeyVerificationAdmin = KeyVerificationAdmin
|
||||
|
||||
@typing.final
|
||||
class SensorConfig(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
SCD4X_CONFIG_FIELD_NUMBER: builtins.int
|
||||
SEN5X_CONFIG_FIELD_NUMBER: builtins.int
|
||||
SCD30_CONFIG_FIELD_NUMBER: builtins.int
|
||||
SHTXX_CONFIG_FIELD_NUMBER: builtins.int
|
||||
@property
|
||||
def scd4x_config(self) -> global___SCD4X_config:
|
||||
"""
|
||||
SCD4X CO2 Sensor configuration
|
||||
"""
|
||||
|
||||
@property
|
||||
def sen5x_config(self) -> global___SEN5X_config:
|
||||
"""
|
||||
SEN5X PM Sensor configuration
|
||||
"""
|
||||
|
||||
@property
|
||||
def scd30_config(self) -> global___SCD30_config:
|
||||
"""
|
||||
SCD30 CO2 Sensor configuration
|
||||
"""
|
||||
|
||||
@property
|
||||
def shtxx_config(self) -> global___SHTXX_config:
|
||||
"""
|
||||
SHTXX temperature and relative humidity sensor configuration
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
scd4x_config: global___SCD4X_config | None = ...,
|
||||
sen5x_config: global___SEN5X_config | None = ...,
|
||||
scd30_config: global___SCD30_config | None = ...,
|
||||
shtxx_config: global___SHTXX_config | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["scd30_config", b"scd30_config", "scd4x_config", b"scd4x_config", "sen5x_config", b"sen5x_config", "shtxx_config", b"shtxx_config"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["scd30_config", b"scd30_config", "scd4x_config", b"scd4x_config", "sen5x_config", b"sen5x_config", "shtxx_config", b"shtxx_config"]) -> None: ...
|
||||
|
||||
global___SensorConfig = SensorConfig
|
||||
|
||||
@typing.final
|
||||
class SCD4X_config(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
SET_ASC_FIELD_NUMBER: builtins.int
|
||||
SET_TARGET_CO2_CONC_FIELD_NUMBER: builtins.int
|
||||
SET_TEMPERATURE_FIELD_NUMBER: builtins.int
|
||||
SET_ALTITUDE_FIELD_NUMBER: builtins.int
|
||||
SET_AMBIENT_PRESSURE_FIELD_NUMBER: builtins.int
|
||||
FACTORY_RESET_FIELD_NUMBER: builtins.int
|
||||
SET_POWER_MODE_FIELD_NUMBER: builtins.int
|
||||
set_asc: builtins.bool
|
||||
"""
|
||||
Set Automatic self-calibration enabled
|
||||
"""
|
||||
set_target_co2_conc: builtins.int
|
||||
"""
|
||||
Recalibration target CO2 concentration in ppm (FRC or ASC)
|
||||
"""
|
||||
set_temperature: builtins.float
|
||||
"""
|
||||
Reference temperature in degC
|
||||
"""
|
||||
set_altitude: builtins.int
|
||||
"""
|
||||
Altitude of sensor in meters above sea level. 0 - 3000m (overrides ambient pressure)
|
||||
"""
|
||||
set_ambient_pressure: builtins.int
|
||||
"""
|
||||
Sensor ambient pressure in Pa. 70000 - 120000 Pa (overrides altitude)
|
||||
"""
|
||||
factory_reset: builtins.bool
|
||||
"""
|
||||
Perform a factory reset of the sensor
|
||||
"""
|
||||
set_power_mode: builtins.bool
|
||||
"""
|
||||
Power mode for sensor (true for low power, false for normal)
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
set_asc: builtins.bool | None = ...,
|
||||
set_target_co2_conc: builtins.int | None = ...,
|
||||
set_temperature: builtins.float | None = ...,
|
||||
set_altitude: builtins.int | None = ...,
|
||||
set_ambient_pressure: builtins.int | None = ...,
|
||||
factory_reset: builtins.bool | None = ...,
|
||||
set_power_mode: builtins.bool | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_factory_reset", b"_factory_reset", "_set_altitude", b"_set_altitude", "_set_ambient_pressure", b"_set_ambient_pressure", "_set_asc", b"_set_asc", "_set_power_mode", b"_set_power_mode", "_set_target_co2_conc", b"_set_target_co2_conc", "_set_temperature", b"_set_temperature", "factory_reset", b"factory_reset", "set_altitude", b"set_altitude", "set_ambient_pressure", b"set_ambient_pressure", "set_asc", b"set_asc", "set_power_mode", b"set_power_mode", "set_target_co2_conc", b"set_target_co2_conc", "set_temperature", b"set_temperature"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_factory_reset", b"_factory_reset", "_set_altitude", b"_set_altitude", "_set_ambient_pressure", b"_set_ambient_pressure", "_set_asc", b"_set_asc", "_set_power_mode", b"_set_power_mode", "_set_target_co2_conc", b"_set_target_co2_conc", "_set_temperature", b"_set_temperature", "factory_reset", b"factory_reset", "set_altitude", b"set_altitude", "set_ambient_pressure", b"set_ambient_pressure", "set_asc", b"set_asc", "set_power_mode", b"set_power_mode", "set_target_co2_conc", b"set_target_co2_conc", "set_temperature", b"set_temperature"]) -> None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_factory_reset", b"_factory_reset"]) -> typing.Literal["factory_reset"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_altitude", b"_set_altitude"]) -> typing.Literal["set_altitude"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_ambient_pressure", b"_set_ambient_pressure"]) -> typing.Literal["set_ambient_pressure"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_asc", b"_set_asc"]) -> typing.Literal["set_asc"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_power_mode", b"_set_power_mode"]) -> typing.Literal["set_power_mode"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_target_co2_conc", b"_set_target_co2_conc"]) -> typing.Literal["set_target_co2_conc"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_temperature", b"_set_temperature"]) -> typing.Literal["set_temperature"] | None: ...
|
||||
|
||||
global___SCD4X_config = SCD4X_config
|
||||
|
||||
@typing.final
|
||||
class SEN5X_config(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
SET_TEMPERATURE_FIELD_NUMBER: builtins.int
|
||||
SET_ONE_SHOT_MODE_FIELD_NUMBER: builtins.int
|
||||
set_temperature: builtins.float
|
||||
"""
|
||||
Reference temperature in degC
|
||||
"""
|
||||
set_one_shot_mode: builtins.bool
|
||||
"""
|
||||
One-shot mode (true for low power - one-shot mode, false for normal - continuous mode)
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
set_temperature: builtins.float | None = ...,
|
||||
set_one_shot_mode: builtins.bool | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_set_one_shot_mode", b"_set_one_shot_mode", "_set_temperature", b"_set_temperature", "set_one_shot_mode", b"set_one_shot_mode", "set_temperature", b"set_temperature"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_set_one_shot_mode", b"_set_one_shot_mode", "_set_temperature", b"_set_temperature", "set_one_shot_mode", b"set_one_shot_mode", "set_temperature", b"set_temperature"]) -> None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_one_shot_mode", b"_set_one_shot_mode"]) -> typing.Literal["set_one_shot_mode"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_temperature", b"_set_temperature"]) -> typing.Literal["set_temperature"] | None: ...
|
||||
|
||||
global___SEN5X_config = SEN5X_config
|
||||
|
||||
@typing.final
|
||||
class SCD30_config(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
SET_ASC_FIELD_NUMBER: builtins.int
|
||||
SET_TARGET_CO2_CONC_FIELD_NUMBER: builtins.int
|
||||
SET_TEMPERATURE_FIELD_NUMBER: builtins.int
|
||||
SET_ALTITUDE_FIELD_NUMBER: builtins.int
|
||||
SET_MEASUREMENT_INTERVAL_FIELD_NUMBER: builtins.int
|
||||
SOFT_RESET_FIELD_NUMBER: builtins.int
|
||||
set_asc: builtins.bool
|
||||
"""
|
||||
Set Automatic self-calibration enabled
|
||||
"""
|
||||
set_target_co2_conc: builtins.int
|
||||
"""
|
||||
Recalibration target CO2 concentration in ppm (FRC or ASC)
|
||||
"""
|
||||
set_temperature: builtins.float
|
||||
"""
|
||||
Reference temperature in degC
|
||||
"""
|
||||
set_altitude: builtins.int
|
||||
"""
|
||||
Altitude of sensor in meters above sea level. 0 - 3000m (overrides ambient pressure)
|
||||
"""
|
||||
set_measurement_interval: builtins.int
|
||||
"""
|
||||
Power mode for sensor (true for low power, false for normal)
|
||||
"""
|
||||
soft_reset: builtins.bool
|
||||
"""
|
||||
Perform a factory reset of the sensor
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
set_asc: builtins.bool | None = ...,
|
||||
set_target_co2_conc: builtins.int | None = ...,
|
||||
set_temperature: builtins.float | None = ...,
|
||||
set_altitude: builtins.int | None = ...,
|
||||
set_measurement_interval: builtins.int | None = ...,
|
||||
soft_reset: builtins.bool | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_set_altitude", b"_set_altitude", "_set_asc", b"_set_asc", "_set_measurement_interval", b"_set_measurement_interval", "_set_target_co2_conc", b"_set_target_co2_conc", "_set_temperature", b"_set_temperature", "_soft_reset", b"_soft_reset", "set_altitude", b"set_altitude", "set_asc", b"set_asc", "set_measurement_interval", b"set_measurement_interval", "set_target_co2_conc", b"set_target_co2_conc", "set_temperature", b"set_temperature", "soft_reset", b"soft_reset"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_set_altitude", b"_set_altitude", "_set_asc", b"_set_asc", "_set_measurement_interval", b"_set_measurement_interval", "_set_target_co2_conc", b"_set_target_co2_conc", "_set_temperature", b"_set_temperature", "_soft_reset", b"_soft_reset", "set_altitude", b"set_altitude", "set_asc", b"set_asc", "set_measurement_interval", b"set_measurement_interval", "set_target_co2_conc", b"set_target_co2_conc", "set_temperature", b"set_temperature", "soft_reset", b"soft_reset"]) -> None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_altitude", b"_set_altitude"]) -> typing.Literal["set_altitude"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_asc", b"_set_asc"]) -> typing.Literal["set_asc"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_measurement_interval", b"_set_measurement_interval"]) -> typing.Literal["set_measurement_interval"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_target_co2_conc", b"_set_target_co2_conc"]) -> typing.Literal["set_target_co2_conc"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_temperature", b"_set_temperature"]) -> typing.Literal["set_temperature"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_soft_reset", b"_soft_reset"]) -> typing.Literal["soft_reset"] | None: ...
|
||||
|
||||
global___SCD30_config = SCD30_config
|
||||
|
||||
@typing.final
|
||||
class SHTXX_config(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
SET_ACCURACY_FIELD_NUMBER: builtins.int
|
||||
set_accuracy: builtins.int
|
||||
"""
|
||||
Accuracy mode (0 = low, 1 = medium, 2 = high)
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
set_accuracy: builtins.int | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_set_accuracy", b"_set_accuracy", "set_accuracy", b"set_accuracy"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_set_accuracy", b"_set_accuracy", "set_accuracy", b"set_accuracy"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_accuracy", b"_set_accuracy"]) -> typing.Literal["set_accuracy"] | None: ...
|
||||
|
||||
global___SHTXX_config = SHTXX_config
|
||||
|
||||
11
meshtastic/protobuf/apponly_pb2.py
generated
11
meshtastic/protobuf/apponly_pb2.py
generated
@@ -13,16 +13,19 @@ _sym_db = _symbol_database.Default()
|
||||
|
||||
from meshtastic.protobuf import channel_pb2 as meshtastic_dot_protobuf_dot_channel__pb2
|
||||
from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config__pb2
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!meshtastic/protobuf/apponly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a meshtastic/protobuf/config.proto\"\x81\x01\n\nChannelSet\x12\x36\n\x08settings\x18\x01 \x03(\x0b\x32$.meshtastic.protobuf.ChannelSettings\x12;\n\x0blora_config\x18\x02 \x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfigBb\n\x13\x63om.geeksville.meshB\rAppOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!meshtastic/protobuf/apponly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a meshtastic/protobuf/config.proto\x1a meshtastic/protobuf/nanopb.proto\"\x88\x01\n\nChannelSet\x12=\n\x08settings\x18\x01 \x03(\x0b\x32$.meshtastic.protobuf.ChannelSettingsB\x05\x92?\x02\x10\x08\x12;\n\x0blora_config\x18\x02 \x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfigBc\n\x14org.meshtastic.protoB\rAppOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.apponly_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\rAppOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_globals['_CHANNELSET']._serialized_start=128
|
||||
_globals['_CHANNELSET']._serialized_end=257
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\rAppOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_CHANNELSET.fields_by_name['settings']._options = None
|
||||
_CHANNELSET.fields_by_name['settings']._serialized_options = b'\222?\002\020\010'
|
||||
_globals['_CHANNELSET']._serialized_start=162
|
||||
_globals['_CHANNELSET']._serialized_end=298
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
249
meshtastic/protobuf/atak_pb2.py
generated
249
meshtastic/protobuf/atak_pb2.py
generated
File diff suppressed because one or more lines are too long
3395
meshtastic/protobuf/atak_pb2.pyi
generated
3395
meshtastic/protobuf/atak_pb2.pyi
generated
File diff suppressed because it is too large
Load Diff
11
meshtastic/protobuf/cannedmessages_pb2.py
generated
11
meshtastic/protobuf/cannedmessages_pb2.py
generated
@@ -11,16 +11,19 @@ from google.protobuf.internal import builder as _builder
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n(meshtastic/protobuf/cannedmessages.proto\x12\x13meshtastic.protobuf\"-\n\x19\x43\x61nnedMessageModuleConfig\x12\x10\n\x08messages\x18\x01 \x01(\tBn\n\x13\x63om.geeksville.meshB\x19\x43\x61nnedMessageConfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n(meshtastic/protobuf/cannedmessages.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/nanopb.proto\"5\n\x19\x43\x61nnedMessageModuleConfig\x12\x18\n\x08messages\x18\x01 \x01(\tB\x06\x92?\x03\x08\xc9\x01\x42o\n\x14org.meshtastic.protoB\x19\x43\x61nnedMessageConfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.cannedmessages_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\031CannedMessageConfigProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_globals['_CANNEDMESSAGEMODULECONFIG']._serialized_start=65
|
||||
_globals['_CANNEDMESSAGEMODULECONFIG']._serialized_end=110
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\031CannedMessageConfigProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_CANNEDMESSAGEMODULECONFIG.fields_by_name['messages']._options = None
|
||||
_CANNEDMESSAGEMODULECONFIG.fields_by_name['messages']._serialized_options = b'\222?\003\010\311\001'
|
||||
_globals['_CANNEDMESSAGEMODULECONFIG']._serialized_start=99
|
||||
_globals['_CANNEDMESSAGEMODULECONFIG']._serialized_end=152
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
27
meshtastic/protobuf/channel_pb2.py
generated
27
meshtastic/protobuf/channel_pb2.py
generated
@@ -11,24 +11,31 @@ from google.protobuf.internal import builder as _builder
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!meshtastic/protobuf/channel.proto\x12\x13meshtastic.protobuf\"\xc1\x01\n\x0f\x43hannelSettings\x12\x17\n\x0b\x63hannel_num\x18\x01 \x01(\rB\x02\x18\x01\x12\x0b\n\x03psk\x18\x02 \x01(\x0c\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\n\n\x02id\x18\x04 \x01(\x07\x12\x16\n\x0euplink_enabled\x18\x05 \x01(\x08\x12\x18\n\x10\x64ownlink_enabled\x18\x06 \x01(\x08\x12<\n\x0fmodule_settings\x18\x07 \x01(\x0b\x32#.meshtastic.protobuf.ModuleSettings\"E\n\x0eModuleSettings\x12\x1a\n\x12position_precision\x18\x01 \x01(\r\x12\x17\n\x0fis_client_muted\x18\x02 \x01(\x08\"\xb3\x01\n\x07\x43hannel\x12\r\n\x05index\x18\x01 \x01(\x05\x12\x36\n\x08settings\x18\x02 \x01(\x0b\x32$.meshtastic.protobuf.ChannelSettings\x12/\n\x04role\x18\x03 \x01(\x0e\x32!.meshtastic.protobuf.Channel.Role\"0\n\x04Role\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07PRIMARY\x10\x01\x12\r\n\tSECONDARY\x10\x02\x42\x62\n\x13\x63om.geeksville.meshB\rChannelProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!meshtastic/protobuf/channel.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/nanopb.proto\"\xcf\x01\n\x0f\x43hannelSettings\x12\x17\n\x0b\x63hannel_num\x18\x01 \x01(\rB\x02\x18\x01\x12\x12\n\x03psk\x18\x02 \x01(\x0c\x42\x05\x92?\x02\x08 \x12\x13\n\x04name\x18\x03 \x01(\tB\x05\x92?\x02\x08\x0c\x12\n\n\x02id\x18\x04 \x01(\x07\x12\x16\n\x0euplink_enabled\x18\x05 \x01(\x08\x12\x18\n\x10\x64ownlink_enabled\x18\x06 \x01(\x08\x12<\n\x0fmodule_settings\x18\x07 \x01(\x0b\x32#.meshtastic.protobuf.ModuleSettings\">\n\x0eModuleSettings\x12\x1a\n\x12position_precision\x18\x01 \x01(\r\x12\x10\n\x08is_muted\x18\x02 \x01(\x08\"\xba\x01\n\x07\x43hannel\x12\x14\n\x05index\x18\x01 \x01(\x05\x42\x05\x92?\x02\x38\x08\x12\x36\n\x08settings\x18\x02 \x01(\x0b\x32$.meshtastic.protobuf.ChannelSettings\x12/\n\x04role\x18\x03 \x01(\x0e\x32!.meshtastic.protobuf.Channel.Role\"0\n\x04Role\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07PRIMARY\x10\x01\x12\r\n\tSECONDARY\x10\x02\x42\x63\n\x14org.meshtastic.protoB\rChannelProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.channel_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\rChannelProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\rChannelProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_CHANNELSETTINGS.fields_by_name['channel_num']._options = None
|
||||
_CHANNELSETTINGS.fields_by_name['channel_num']._serialized_options = b'\030\001'
|
||||
_globals['_CHANNELSETTINGS']._serialized_start=59
|
||||
_globals['_CHANNELSETTINGS']._serialized_end=252
|
||||
_globals['_MODULESETTINGS']._serialized_start=254
|
||||
_globals['_MODULESETTINGS']._serialized_end=323
|
||||
_globals['_CHANNEL']._serialized_start=326
|
||||
_globals['_CHANNEL']._serialized_end=505
|
||||
_globals['_CHANNEL_ROLE']._serialized_start=457
|
||||
_globals['_CHANNEL_ROLE']._serialized_end=505
|
||||
_CHANNELSETTINGS.fields_by_name['psk']._options = None
|
||||
_CHANNELSETTINGS.fields_by_name['psk']._serialized_options = b'\222?\002\010 '
|
||||
_CHANNELSETTINGS.fields_by_name['name']._options = None
|
||||
_CHANNELSETTINGS.fields_by_name['name']._serialized_options = b'\222?\002\010\014'
|
||||
_CHANNEL.fields_by_name['index']._options = None
|
||||
_CHANNEL.fields_by_name['index']._serialized_options = b'\222?\0028\010'
|
||||
_globals['_CHANNELSETTINGS']._serialized_start=93
|
||||
_globals['_CHANNELSETTINGS']._serialized_end=300
|
||||
_globals['_MODULESETTINGS']._serialized_start=302
|
||||
_globals['_MODULESETTINGS']._serialized_end=364
|
||||
_globals['_CHANNEL']._serialized_start=367
|
||||
_globals['_CHANNEL']._serialized_end=553
|
||||
_globals['_CHANNEL_ROLE']._serialized_start=505
|
||||
_globals['_CHANNEL_ROLE']._serialized_end=553
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
12
meshtastic/protobuf/channel_pb2.pyi
generated
12
meshtastic/protobuf/channel_pb2.pyi
generated
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
@generated by mypy-protobuf. Do not edit manually!
|
||||
isort:skip_file
|
||||
"""
|
||||
trunk-ignore(buf-lint/PACKAGE_DIRECTORY_MATCH)"""
|
||||
|
||||
import builtins
|
||||
import google.protobuf.descriptor
|
||||
@@ -127,23 +127,23 @@ class ModuleSettings(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
POSITION_PRECISION_FIELD_NUMBER: builtins.int
|
||||
IS_CLIENT_MUTED_FIELD_NUMBER: builtins.int
|
||||
IS_MUTED_FIELD_NUMBER: builtins.int
|
||||
position_precision: builtins.int
|
||||
"""
|
||||
Bits of precision for the location sent in position packets.
|
||||
"""
|
||||
is_client_muted: builtins.bool
|
||||
is_muted: builtins.bool
|
||||
"""
|
||||
Controls whether or not the phone / clients should mute the current channel
|
||||
Controls whether or not the client / device should mute the current channel
|
||||
Useful for noisy public channels you don't necessarily want to disable
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
position_precision: builtins.int = ...,
|
||||
is_client_muted: builtins.bool = ...,
|
||||
is_muted: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["is_client_muted", b"is_client_muted", "position_precision", b"position_precision"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["is_muted", b"is_muted", "position_precision", b"position_precision"]) -> None: ...
|
||||
|
||||
global___ModuleSettings = ModuleSettings
|
||||
|
||||
|
||||
18
meshtastic/protobuf/clientonly_pb2.py
generated
18
meshtastic/protobuf/clientonly_pb2.py
generated
@@ -12,16 +12,26 @@ _sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from meshtastic.protobuf import localonly_pb2 as meshtastic_dot_protobuf_dot_localonly__pb2
|
||||
from meshtastic.protobuf import mesh_pb2 as meshtastic_dot_protobuf_dot_mesh__pb2
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/clientonly.proto\x12\x13meshtastic.protobuf\x1a#meshtastic/protobuf/localonly.proto\"\x9f\x02\n\rDeviceProfile\x12\x16\n\tlong_name\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x17\n\nshort_name\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0b\x63hannel_url\x18\x03 \x01(\tH\x02\x88\x01\x01\x12\x35\n\x06\x63onfig\x18\x04 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfigH\x03\x88\x01\x01\x12\x42\n\rmodule_config\x18\x05 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfigH\x04\x88\x01\x01\x42\x0c\n\n_long_nameB\r\n\x0b_short_nameB\x0e\n\x0c_channel_urlB\t\n\x07_configB\x10\n\x0e_module_configBe\n\x13\x63om.geeksville.meshB\x10\x43lientOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/clientonly.proto\x12\x13meshtastic.protobuf\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a meshtastic/protobuf/nanopb.proto\"\xe2\x03\n\rDeviceProfile\x12\x1d\n\tlong_name\x18\x01 \x01(\tB\x05\x92?\x02\x08(H\x00\x88\x01\x01\x12\x1e\n\nshort_name\x18\x02 \x01(\tB\x05\x92?\x02\x08\x05H\x01\x88\x01\x01\x12\x18\n\x0b\x63hannel_url\x18\x03 \x01(\tH\x02\x88\x01\x01\x12\x35\n\x06\x63onfig\x18\x04 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfigH\x03\x88\x01\x01\x12\x42\n\rmodule_config\x18\x05 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfigH\x04\x88\x01\x01\x12:\n\x0e\x66ixed_position\x18\x06 \x01(\x0b\x32\x1d.meshtastic.protobuf.PositionH\x05\x88\x01\x01\x12\x1d\n\x08ringtone\x18\x07 \x01(\tB\x06\x92?\x03\x08\xe7\x01H\x06\x88\x01\x01\x12$\n\x0f\x63\x61nned_messages\x18\x08 \x01(\tB\x06\x92?\x03\x08\xc9\x01H\x07\x88\x01\x01\x42\x0c\n\n_long_nameB\r\n\x0b_short_nameB\x0e\n\x0c_channel_urlB\t\n\x07_configB\x10\n\x0e_module_configB\x11\n\x0f_fixed_positionB\x0b\n\t_ringtoneB\x12\n\x10_canned_messagesBf\n\x14org.meshtastic.protoB\x10\x43lientOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.clientonly_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\020ClientOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_globals['_DEVICEPROFILE']._serialized_start=99
|
||||
_globals['_DEVICEPROFILE']._serialized_end=386
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\020ClientOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_DEVICEPROFILE.fields_by_name['long_name']._options = None
|
||||
_DEVICEPROFILE.fields_by_name['long_name']._serialized_options = b'\222?\002\010('
|
||||
_DEVICEPROFILE.fields_by_name['short_name']._options = None
|
||||
_DEVICEPROFILE.fields_by_name['short_name']._serialized_options = b'\222?\002\010\005'
|
||||
_DEVICEPROFILE.fields_by_name['ringtone']._options = None
|
||||
_DEVICEPROFILE.fields_by_name['ringtone']._serialized_options = b'\222?\003\010\347\001'
|
||||
_DEVICEPROFILE.fields_by_name['canned_messages']._options = None
|
||||
_DEVICEPROFILE.fields_by_name['canned_messages']._serialized_options = b'\222?\003\010\311\001'
|
||||
_globals['_DEVICEPROFILE']._serialized_start=165
|
||||
_globals['_DEVICEPROFILE']._serialized_end=647
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
31
meshtastic/protobuf/clientonly_pb2.pyi
generated
31
meshtastic/protobuf/clientonly_pb2.pyi
generated
@@ -7,6 +7,7 @@ import builtins
|
||||
import google.protobuf.descriptor
|
||||
import google.protobuf.message
|
||||
import meshtastic.protobuf.localonly_pb2
|
||||
import meshtastic.protobuf.mesh_pb2
|
||||
import typing
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
|
||||
@@ -25,6 +26,9 @@ class DeviceProfile(google.protobuf.message.Message):
|
||||
CHANNEL_URL_FIELD_NUMBER: builtins.int
|
||||
CONFIG_FIELD_NUMBER: builtins.int
|
||||
MODULE_CONFIG_FIELD_NUMBER: builtins.int
|
||||
FIXED_POSITION_FIELD_NUMBER: builtins.int
|
||||
RINGTONE_FIELD_NUMBER: builtins.int
|
||||
CANNED_MESSAGES_FIELD_NUMBER: builtins.int
|
||||
long_name: builtins.str
|
||||
"""
|
||||
Long name for the node
|
||||
@@ -37,6 +41,14 @@ class DeviceProfile(google.protobuf.message.Message):
|
||||
"""
|
||||
The url of the channels from our node
|
||||
"""
|
||||
ringtone: builtins.str
|
||||
"""
|
||||
Ringtone for ExternalNotification
|
||||
"""
|
||||
canned_messages: builtins.str
|
||||
"""
|
||||
Predefined messages for CannedMessage
|
||||
"""
|
||||
@property
|
||||
def config(self) -> meshtastic.protobuf.localonly_pb2.LocalConfig:
|
||||
"""
|
||||
@@ -49,6 +61,12 @@ class DeviceProfile(google.protobuf.message.Message):
|
||||
The ModuleConfig of the node
|
||||
"""
|
||||
|
||||
@property
|
||||
def fixed_position(self) -> meshtastic.protobuf.mesh_pb2.Position:
|
||||
"""
|
||||
Fixed position data
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -57,18 +75,27 @@ class DeviceProfile(google.protobuf.message.Message):
|
||||
channel_url: builtins.str | None = ...,
|
||||
config: meshtastic.protobuf.localonly_pb2.LocalConfig | None = ...,
|
||||
module_config: meshtastic.protobuf.localonly_pb2.LocalModuleConfig | None = ...,
|
||||
fixed_position: meshtastic.protobuf.mesh_pb2.Position | None = ...,
|
||||
ringtone: builtins.str | None = ...,
|
||||
canned_messages: builtins.str | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_channel_url", b"_channel_url", "_config", b"_config", "_long_name", b"_long_name", "_module_config", b"_module_config", "_short_name", b"_short_name", "channel_url", b"channel_url", "config", b"config", "long_name", b"long_name", "module_config", b"module_config", "short_name", b"short_name"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_channel_url", b"_channel_url", "_config", b"_config", "_long_name", b"_long_name", "_module_config", b"_module_config", "_short_name", b"_short_name", "channel_url", b"channel_url", "config", b"config", "long_name", b"long_name", "module_config", b"module_config", "short_name", b"short_name"]) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_canned_messages", b"_canned_messages", "_channel_url", b"_channel_url", "_config", b"_config", "_fixed_position", b"_fixed_position", "_long_name", b"_long_name", "_module_config", b"_module_config", "_ringtone", b"_ringtone", "_short_name", b"_short_name", "canned_messages", b"canned_messages", "channel_url", b"channel_url", "config", b"config", "fixed_position", b"fixed_position", "long_name", b"long_name", "module_config", b"module_config", "ringtone", b"ringtone", "short_name", b"short_name"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_canned_messages", b"_canned_messages", "_channel_url", b"_channel_url", "_config", b"_config", "_fixed_position", b"_fixed_position", "_long_name", b"_long_name", "_module_config", b"_module_config", "_ringtone", b"_ringtone", "_short_name", b"_short_name", "canned_messages", b"canned_messages", "channel_url", b"channel_url", "config", b"config", "fixed_position", b"fixed_position", "long_name", b"long_name", "module_config", b"module_config", "ringtone", b"ringtone", "short_name", b"short_name"]) -> None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_canned_messages", b"_canned_messages"]) -> typing.Literal["canned_messages"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_channel_url", b"_channel_url"]) -> typing.Literal["channel_url"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_config", b"_config"]) -> typing.Literal["config"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_fixed_position", b"_fixed_position"]) -> typing.Literal["fixed_position"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_long_name", b"_long_name"]) -> typing.Literal["long_name"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_module_config", b"_module_config"]) -> typing.Literal["module_config"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_ringtone", b"_ringtone"]) -> typing.Literal["ringtone"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_short_name", b"_short_name"]) -> typing.Literal["short_name"] | None: ...
|
||||
|
||||
global___DeviceProfile = DeviceProfile
|
||||
|
||||
148
meshtastic/protobuf/config_pb2.py
generated
148
meshtastic/protobuf/config_pb2.py
generated
File diff suppressed because one or more lines are too long
651
meshtastic/protobuf/config_pb2.pyi
generated
651
meshtastic/protobuf/config_pb2.pyi
generated
@@ -9,6 +9,7 @@ import google.protobuf.descriptor
|
||||
import google.protobuf.internal.containers
|
||||
import google.protobuf.internal.enum_type_wrapper
|
||||
import google.protobuf.message
|
||||
import meshtastic.protobuf.device_ui_pb2
|
||||
import sys
|
||||
import typing
|
||||
|
||||
@@ -63,6 +64,7 @@ class Config(google.protobuf.message.Message):
|
||||
Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list.
|
||||
Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry
|
||||
or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate.
|
||||
Deprecated in v2.7.11 because it creates "holes" in the mesh rebroadcast chain.
|
||||
"""
|
||||
TRACKER: Config.DeviceConfig._Role.ValueType # 5
|
||||
"""
|
||||
@@ -107,6 +109,21 @@ class Config(google.protobuf.message.Message):
|
||||
and automatic TAK PLI (position location information) broadcasts.
|
||||
Uses position module configuration to determine TAK PLI broadcast interval.
|
||||
"""
|
||||
ROUTER_LATE: Config.DeviceConfig._Role.ValueType # 11
|
||||
"""
|
||||
Description: Will always rebroadcast packets, but will do so after all other modes.
|
||||
Technical Details: Used for router nodes that are intended to provide additional coverage
|
||||
in areas not already covered by other routers, or to bridge around problematic terrain,
|
||||
but should not be given priority over other routers in order to avoid unnecessaraily
|
||||
consuming hops.
|
||||
"""
|
||||
CLIENT_BASE: Config.DeviceConfig._Role.ValueType # 12
|
||||
"""
|
||||
Description: Treats packets from or to favorited nodes as ROUTER_LATE, and all other packets as CLIENT.
|
||||
Technical Details: Used for stronger attic/roof nodes to distribute messages more widely
|
||||
from weaker, indoor, or less-well-positioned nodes. Recommended for users with multiple nodes
|
||||
where one CLIENT_BASE acts as a more powerful base station, such as an attic/roof node.
|
||||
"""
|
||||
|
||||
class Role(_Role, metaclass=_RoleEnumTypeWrapper):
|
||||
"""
|
||||
@@ -139,6 +156,7 @@ class Config(google.protobuf.message.Message):
|
||||
Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list.
|
||||
Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry
|
||||
or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate.
|
||||
Deprecated in v2.7.11 because it creates "holes" in the mesh rebroadcast chain.
|
||||
"""
|
||||
TRACKER: Config.DeviceConfig.Role.ValueType # 5
|
||||
"""
|
||||
@@ -183,6 +201,21 @@ class Config(google.protobuf.message.Message):
|
||||
and automatic TAK PLI (position location information) broadcasts.
|
||||
Uses position module configuration to determine TAK PLI broadcast interval.
|
||||
"""
|
||||
ROUTER_LATE: Config.DeviceConfig.Role.ValueType # 11
|
||||
"""
|
||||
Description: Will always rebroadcast packets, but will do so after all other modes.
|
||||
Technical Details: Used for router nodes that are intended to provide additional coverage
|
||||
in areas not already covered by other routers, or to bridge around problematic terrain,
|
||||
but should not be given priority over other routers in order to avoid unnecessaraily
|
||||
consuming hops.
|
||||
"""
|
||||
CLIENT_BASE: Config.DeviceConfig.Role.ValueType # 12
|
||||
"""
|
||||
Description: Treats packets from or to favorited nodes as ROUTER_LATE, and all other packets as CLIENT.
|
||||
Technical Details: Used for stronger attic/roof nodes to distribute messages more widely
|
||||
from weaker, indoor, or less-well-positioned nodes. Recommended for users with multiple nodes
|
||||
where one CLIENT_BASE acts as a more powerful base station, such as an attic/roof node.
|
||||
"""
|
||||
|
||||
class _RebroadcastMode:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
@@ -210,6 +243,15 @@ class Config(google.protobuf.message.Message):
|
||||
Ignores observed messages from foreign meshes like LOCAL_ONLY,
|
||||
but takes it step further by also ignoring messages from nodenums not in the node's known list (NodeDB)
|
||||
"""
|
||||
NONE: Config.DeviceConfig._RebroadcastMode.ValueType # 4
|
||||
"""
|
||||
Only permitted for SENSOR, TRACKER and TAK_TRACKER roles, this will inhibit all rebroadcasts, not unlike CLIENT_MUTE role.
|
||||
"""
|
||||
CORE_PORTNUMS_ONLY: Config.DeviceConfig._RebroadcastMode.ValueType # 5
|
||||
"""
|
||||
Ignores packets from non-standard portnums such as: TAK, RangeTest, PaxCounter, etc.
|
||||
Only rebroadcasts packets with standard portnums: NodeInfo, Text, Position, Telemetry, and Routing.
|
||||
"""
|
||||
|
||||
class RebroadcastMode(_RebroadcastMode, metaclass=_RebroadcastModeEnumTypeWrapper):
|
||||
"""
|
||||
@@ -236,10 +278,85 @@ class Config(google.protobuf.message.Message):
|
||||
Ignores observed messages from foreign meshes like LOCAL_ONLY,
|
||||
but takes it step further by also ignoring messages from nodenums not in the node's known list (NodeDB)
|
||||
"""
|
||||
NONE: Config.DeviceConfig.RebroadcastMode.ValueType # 4
|
||||
"""
|
||||
Only permitted for SENSOR, TRACKER and TAK_TRACKER roles, this will inhibit all rebroadcasts, not unlike CLIENT_MUTE role.
|
||||
"""
|
||||
CORE_PORTNUMS_ONLY: Config.DeviceConfig.RebroadcastMode.ValueType # 5
|
||||
"""
|
||||
Ignores packets from non-standard portnums such as: TAK, RangeTest, PaxCounter, etc.
|
||||
Only rebroadcasts packets with standard portnums: NodeInfo, Text, Position, Telemetry, and Routing.
|
||||
"""
|
||||
|
||||
class _BuzzerMode:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _BuzzerModeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Config.DeviceConfig._BuzzerMode.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
ALL_ENABLED: Config.DeviceConfig._BuzzerMode.ValueType # 0
|
||||
"""
|
||||
Default behavior.
|
||||
Buzzer is enabled for all audio feedback including button presses and alerts.
|
||||
"""
|
||||
DISABLED: Config.DeviceConfig._BuzzerMode.ValueType # 1
|
||||
"""
|
||||
Disabled.
|
||||
All buzzer audio feedback is disabled.
|
||||
"""
|
||||
NOTIFICATIONS_ONLY: Config.DeviceConfig._BuzzerMode.ValueType # 2
|
||||
"""
|
||||
Notifications Only.
|
||||
Buzzer is enabled only for notifications and alerts, but not for button presses.
|
||||
External notification config determines the specifics of the notification behavior.
|
||||
"""
|
||||
SYSTEM_ONLY: Config.DeviceConfig._BuzzerMode.ValueType # 3
|
||||
"""
|
||||
Non-notification system buzzer tones only.
|
||||
Buzzer is enabled only for non-notification tones such as button presses, startup, shutdown, but not for alerts.
|
||||
"""
|
||||
DIRECT_MSG_ONLY: Config.DeviceConfig._BuzzerMode.ValueType # 4
|
||||
"""
|
||||
Direct Message notifications only.
|
||||
Buzzer is enabled only for direct messages and alerts, but not for button presses.
|
||||
External notification config determines the specifics of the notification behavior.
|
||||
"""
|
||||
|
||||
class BuzzerMode(_BuzzerMode, metaclass=_BuzzerModeEnumTypeWrapper):
|
||||
"""
|
||||
Defines buzzer behavior for audio feedback
|
||||
"""
|
||||
|
||||
ALL_ENABLED: Config.DeviceConfig.BuzzerMode.ValueType # 0
|
||||
"""
|
||||
Default behavior.
|
||||
Buzzer is enabled for all audio feedback including button presses and alerts.
|
||||
"""
|
||||
DISABLED: Config.DeviceConfig.BuzzerMode.ValueType # 1
|
||||
"""
|
||||
Disabled.
|
||||
All buzzer audio feedback is disabled.
|
||||
"""
|
||||
NOTIFICATIONS_ONLY: Config.DeviceConfig.BuzzerMode.ValueType # 2
|
||||
"""
|
||||
Notifications Only.
|
||||
Buzzer is enabled only for notifications and alerts, but not for button presses.
|
||||
External notification config determines the specifics of the notification behavior.
|
||||
"""
|
||||
SYSTEM_ONLY: Config.DeviceConfig.BuzzerMode.ValueType # 3
|
||||
"""
|
||||
Non-notification system buzzer tones only.
|
||||
Buzzer is enabled only for non-notification tones such as button presses, startup, shutdown, but not for alerts.
|
||||
"""
|
||||
DIRECT_MSG_ONLY: Config.DeviceConfig.BuzzerMode.ValueType # 4
|
||||
"""
|
||||
Direct Message notifications only.
|
||||
Buzzer is enabled only for direct messages and alerts, but not for button presses.
|
||||
External notification config determines the specifics of the notification behavior.
|
||||
"""
|
||||
|
||||
ROLE_FIELD_NUMBER: builtins.int
|
||||
SERIAL_ENABLED_FIELD_NUMBER: builtins.int
|
||||
DEBUG_LOG_ENABLED_FIELD_NUMBER: builtins.int
|
||||
BUTTON_GPIO_FIELD_NUMBER: builtins.int
|
||||
BUZZER_GPIO_FIELD_NUMBER: builtins.int
|
||||
REBROADCAST_MODE_FIELD_NUMBER: builtins.int
|
||||
@@ -249,6 +366,7 @@ class Config(google.protobuf.message.Message):
|
||||
DISABLE_TRIPLE_CLICK_FIELD_NUMBER: builtins.int
|
||||
TZDEF_FIELD_NUMBER: builtins.int
|
||||
LED_HEARTBEAT_DISABLED_FIELD_NUMBER: builtins.int
|
||||
BUZZER_MODE_FIELD_NUMBER: builtins.int
|
||||
role: global___Config.DeviceConfig.Role.ValueType
|
||||
"""
|
||||
Sets the role of node
|
||||
@@ -256,11 +374,7 @@ class Config(google.protobuf.message.Message):
|
||||
serial_enabled: builtins.bool
|
||||
"""
|
||||
Disabling this will disable the SerialConsole by not initilizing the StreamAPI
|
||||
"""
|
||||
debug_log_enabled: builtins.bool
|
||||
"""
|
||||
By default we turn off logging as soon as an API client connects (to keep shared serial link quiet).
|
||||
Set this to true to leave the debug log outputting even when API is active.
|
||||
Moved to SecurityConfig
|
||||
"""
|
||||
button_gpio: builtins.int
|
||||
"""
|
||||
@@ -289,6 +403,7 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
If true, device is considered to be "managed" by a mesh administrator
|
||||
Clients should then limit available configuration and administrative options inside the user interface
|
||||
Moved to SecurityConfig
|
||||
"""
|
||||
disable_triple_click: builtins.bool
|
||||
"""
|
||||
@@ -302,12 +417,16 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
If true, disable the default blinking LED (LED_PIN) behavior on the device
|
||||
"""
|
||||
buzzer_mode: global___Config.DeviceConfig.BuzzerMode.ValueType
|
||||
"""
|
||||
Controls buzzer behavior for audio feedback
|
||||
Defaults to ENABLED
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
role: global___Config.DeviceConfig.Role.ValueType = ...,
|
||||
serial_enabled: builtins.bool = ...,
|
||||
debug_log_enabled: builtins.bool = ...,
|
||||
button_gpio: builtins.int = ...,
|
||||
buzzer_gpio: builtins.int = ...,
|
||||
rebroadcast_mode: global___Config.DeviceConfig.RebroadcastMode.ValueType = ...,
|
||||
@@ -317,8 +436,9 @@ class Config(google.protobuf.message.Message):
|
||||
disable_triple_click: builtins.bool = ...,
|
||||
tzdef: builtins.str = ...,
|
||||
led_heartbeat_disabled: builtins.bool = ...,
|
||||
buzzer_mode: global___Config.DeviceConfig.BuzzerMode.ValueType = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["button_gpio", b"button_gpio", "buzzer_gpio", b"buzzer_gpio", "debug_log_enabled", b"debug_log_enabled", "disable_triple_click", b"disable_triple_click", "double_tap_as_button_press", b"double_tap_as_button_press", "is_managed", b"is_managed", "led_heartbeat_disabled", b"led_heartbeat_disabled", "node_info_broadcast_secs", b"node_info_broadcast_secs", "rebroadcast_mode", b"rebroadcast_mode", "role", b"role", "serial_enabled", b"serial_enabled", "tzdef", b"tzdef"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["button_gpio", b"button_gpio", "buzzer_gpio", b"buzzer_gpio", "buzzer_mode", b"buzzer_mode", "disable_triple_click", b"disable_triple_click", "double_tap_as_button_press", b"double_tap_as_button_press", "is_managed", b"is_managed", "led_heartbeat_disabled", b"led_heartbeat_disabled", "node_info_broadcast_secs", b"node_info_broadcast_secs", "rebroadcast_mode", b"rebroadcast_mode", "role", b"role", "serial_enabled", b"serial_enabled", "tzdef", b"tzdef"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class PositionConfig(google.protobuf.message.Message):
|
||||
@@ -585,7 +705,7 @@ class Config(google.protobuf.message.Message):
|
||||
POWERMON_ENABLES_FIELD_NUMBER: builtins.int
|
||||
is_power_saving: builtins.bool
|
||||
"""
|
||||
Description: Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio.
|
||||
Description: Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio.
|
||||
Don't use this setting if you want to use your device with the phone apps or are using a device without a user button.
|
||||
Technical Details: Works for ESP32 devices and NRF52 devices in the Sensor or Tracker roles
|
||||
"""
|
||||
@@ -679,6 +799,35 @@ class Config(google.protobuf.message.Message):
|
||||
use static ip address
|
||||
"""
|
||||
|
||||
class _ProtocolFlags:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _ProtocolFlagsEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Config.NetworkConfig._ProtocolFlags.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
NO_BROADCAST: Config.NetworkConfig._ProtocolFlags.ValueType # 0
|
||||
"""
|
||||
Do not broadcast packets over any network protocol
|
||||
"""
|
||||
UDP_BROADCAST: Config.NetworkConfig._ProtocolFlags.ValueType # 1
|
||||
"""
|
||||
Enable broadcasting packets via UDP over the local network
|
||||
"""
|
||||
|
||||
class ProtocolFlags(_ProtocolFlags, metaclass=_ProtocolFlagsEnumTypeWrapper):
|
||||
"""
|
||||
Available flags auxiliary network protocols
|
||||
"""
|
||||
|
||||
NO_BROADCAST: Config.NetworkConfig.ProtocolFlags.ValueType # 0
|
||||
"""
|
||||
Do not broadcast packets over any network protocol
|
||||
"""
|
||||
UDP_BROADCAST: Config.NetworkConfig.ProtocolFlags.ValueType # 1
|
||||
"""
|
||||
Enable broadcasting packets via UDP over the local network
|
||||
"""
|
||||
|
||||
@typing.final
|
||||
class IpV4Config(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
@@ -721,6 +870,8 @@ class Config(google.protobuf.message.Message):
|
||||
ADDRESS_MODE_FIELD_NUMBER: builtins.int
|
||||
IPV4_CONFIG_FIELD_NUMBER: builtins.int
|
||||
RSYSLOG_SERVER_FIELD_NUMBER: builtins.int
|
||||
ENABLED_PROTOCOLS_FIELD_NUMBER: builtins.int
|
||||
IPV6_ENABLED_FIELD_NUMBER: builtins.int
|
||||
wifi_enabled: builtins.bool
|
||||
"""
|
||||
Enable WiFi (disables Bluetooth)
|
||||
@@ -736,7 +887,7 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
ntp_server: builtins.str
|
||||
"""
|
||||
NTP server to use if WiFi is conneced, defaults to `0.pool.ntp.org`
|
||||
NTP server to use if WiFi is conneced, defaults to `meshtastic.pool.ntp.org`
|
||||
"""
|
||||
eth_enabled: builtins.bool
|
||||
"""
|
||||
@@ -750,6 +901,14 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
rsyslog Server and Port
|
||||
"""
|
||||
enabled_protocols: builtins.int
|
||||
"""
|
||||
Flags for enabling/disabling network protocols
|
||||
"""
|
||||
ipv6_enabled: builtins.bool
|
||||
"""
|
||||
Enable/Disable ipv6 support
|
||||
"""
|
||||
@property
|
||||
def ipv4_config(self) -> global___Config.NetworkConfig.IpV4Config:
|
||||
"""
|
||||
@@ -767,9 +926,11 @@ class Config(google.protobuf.message.Message):
|
||||
address_mode: global___Config.NetworkConfig.AddressMode.ValueType = ...,
|
||||
ipv4_config: global___Config.NetworkConfig.IpV4Config | None = ...,
|
||||
rsyslog_server: builtins.str = ...,
|
||||
enabled_protocols: builtins.int = ...,
|
||||
ipv6_enabled: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["ipv4_config", b"ipv4_config"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["address_mode", b"address_mode", "eth_enabled", b"eth_enabled", "ipv4_config", b"ipv4_config", "ntp_server", b"ntp_server", "rsyslog_server", b"rsyslog_server", "wifi_enabled", b"wifi_enabled", "wifi_psk", b"wifi_psk", "wifi_ssid", b"wifi_ssid"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["address_mode", b"address_mode", "enabled_protocols", b"enabled_protocols", "eth_enabled", b"eth_enabled", "ipv4_config", b"ipv4_config", "ipv6_enabled", b"ipv6_enabled", "ntp_server", b"ntp_server", "rsyslog_server", b"rsyslog_server", "wifi_enabled", b"wifi_enabled", "wifi_psk", b"wifi_psk", "wifi_ssid", b"wifi_ssid"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class DisplayConfig(google.protobuf.message.Message):
|
||||
@@ -779,80 +940,20 @@ class Config(google.protobuf.message.Message):
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
class _GpsCoordinateFormat:
|
||||
class _DeprecatedGpsCoordinateFormat:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _GpsCoordinateFormatEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Config.DisplayConfig._GpsCoordinateFormat.ValueType], builtins.type):
|
||||
class _DeprecatedGpsCoordinateFormatEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Config.DisplayConfig._DeprecatedGpsCoordinateFormat.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
DEC: Config.DisplayConfig._GpsCoordinateFormat.ValueType # 0
|
||||
UNUSED: Config.DisplayConfig._DeprecatedGpsCoordinateFormat.ValueType # 0
|
||||
|
||||
class DeprecatedGpsCoordinateFormat(_DeprecatedGpsCoordinateFormat, metaclass=_DeprecatedGpsCoordinateFormatEnumTypeWrapper):
|
||||
"""
|
||||
GPS coordinates are displayed in the normal decimal degrees format:
|
||||
DD.DDDDDD DDD.DDDDDD
|
||||
"""
|
||||
DMS: Config.DisplayConfig._GpsCoordinateFormat.ValueType # 1
|
||||
"""
|
||||
GPS coordinates are displayed in the degrees minutes seconds format:
|
||||
DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant
|
||||
"""
|
||||
UTM: Config.DisplayConfig._GpsCoordinateFormat.ValueType # 2
|
||||
"""
|
||||
Universal Transverse Mercator format:
|
||||
ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing
|
||||
"""
|
||||
MGRS: Config.DisplayConfig._GpsCoordinateFormat.ValueType # 3
|
||||
"""
|
||||
Military Grid Reference System format:
|
||||
ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
|
||||
E is easting, N is northing
|
||||
"""
|
||||
OLC: Config.DisplayConfig._GpsCoordinateFormat.ValueType # 4
|
||||
"""
|
||||
Open Location Code (aka Plus Codes).
|
||||
"""
|
||||
OSGR: Config.DisplayConfig._GpsCoordinateFormat.ValueType # 5
|
||||
"""
|
||||
Ordnance Survey Grid Reference (the National Grid System of the UK).
|
||||
Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
|
||||
E is the easting, N is the northing
|
||||
Deprecated in 2.7.4: Unused
|
||||
"""
|
||||
|
||||
class GpsCoordinateFormat(_GpsCoordinateFormat, metaclass=_GpsCoordinateFormatEnumTypeWrapper):
|
||||
"""
|
||||
How the GPS coordinates are displayed on the OLED screen.
|
||||
"""
|
||||
|
||||
DEC: Config.DisplayConfig.GpsCoordinateFormat.ValueType # 0
|
||||
"""
|
||||
GPS coordinates are displayed in the normal decimal degrees format:
|
||||
DD.DDDDDD DDD.DDDDDD
|
||||
"""
|
||||
DMS: Config.DisplayConfig.GpsCoordinateFormat.ValueType # 1
|
||||
"""
|
||||
GPS coordinates are displayed in the degrees minutes seconds format:
|
||||
DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant
|
||||
"""
|
||||
UTM: Config.DisplayConfig.GpsCoordinateFormat.ValueType # 2
|
||||
"""
|
||||
Universal Transverse Mercator format:
|
||||
ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing
|
||||
"""
|
||||
MGRS: Config.DisplayConfig.GpsCoordinateFormat.ValueType # 3
|
||||
"""
|
||||
Military Grid Reference System format:
|
||||
ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
|
||||
E is easting, N is northing
|
||||
"""
|
||||
OLC: Config.DisplayConfig.GpsCoordinateFormat.ValueType # 4
|
||||
"""
|
||||
Open Location Code (aka Plus Codes).
|
||||
"""
|
||||
OSGR: Config.DisplayConfig.GpsCoordinateFormat.ValueType # 5
|
||||
"""
|
||||
Ordnance Survey Grid Reference (the National Grid System of the UK).
|
||||
Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
|
||||
E is the easting, N is the northing
|
||||
"""
|
||||
UNUSED: Config.DisplayConfig.DeprecatedGpsCoordinateFormat.ValueType # 0
|
||||
|
||||
class _DisplayUnits:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
@@ -891,20 +992,28 @@ class Config(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
OLED_AUTO: Config.DisplayConfig._OledType.ValueType # 0
|
||||
"""
|
||||
Default / Auto
|
||||
Default / Autodetect
|
||||
"""
|
||||
OLED_SSD1306: Config.DisplayConfig._OledType.ValueType # 1
|
||||
"""
|
||||
Default / Auto
|
||||
Default / Autodetect
|
||||
"""
|
||||
OLED_SH1106: Config.DisplayConfig._OledType.ValueType # 2
|
||||
"""
|
||||
Default / Auto
|
||||
Default / Autodetect
|
||||
"""
|
||||
OLED_SH1107: Config.DisplayConfig._OledType.ValueType # 3
|
||||
"""
|
||||
Can not be auto detected but set by proto. Used for 128x64 screens
|
||||
"""
|
||||
OLED_SH1107_128_128: Config.DisplayConfig._OledType.ValueType # 4
|
||||
"""
|
||||
Can not be auto detected but set by proto. Used for 128x128 screens
|
||||
"""
|
||||
OLED_SH1107_ROTATED: Config.DisplayConfig._OledType.ValueType # 5
|
||||
"""
|
||||
Can not be auto detected but set by proto. Used for 64x128 rotated screens
|
||||
"""
|
||||
|
||||
class OledType(_OledType, metaclass=_OledTypeEnumTypeWrapper):
|
||||
"""
|
||||
@@ -913,20 +1022,28 @@ class Config(google.protobuf.message.Message):
|
||||
|
||||
OLED_AUTO: Config.DisplayConfig.OledType.ValueType # 0
|
||||
"""
|
||||
Default / Auto
|
||||
Default / Autodetect
|
||||
"""
|
||||
OLED_SSD1306: Config.DisplayConfig.OledType.ValueType # 1
|
||||
"""
|
||||
Default / Auto
|
||||
Default / Autodetect
|
||||
"""
|
||||
OLED_SH1106: Config.DisplayConfig.OledType.ValueType # 2
|
||||
"""
|
||||
Default / Auto
|
||||
Default / Autodetect
|
||||
"""
|
||||
OLED_SH1107: Config.DisplayConfig.OledType.ValueType # 3
|
||||
"""
|
||||
Can not be auto detected but set by proto. Used for 128x64 screens
|
||||
"""
|
||||
OLED_SH1107_128_128: Config.DisplayConfig.OledType.ValueType # 4
|
||||
"""
|
||||
Can not be auto detected but set by proto. Used for 128x128 screens
|
||||
"""
|
||||
OLED_SH1107_ROTATED: Config.DisplayConfig.OledType.ValueType # 5
|
||||
"""
|
||||
Can not be auto detected but set by proto. Used for 64x128 rotated screens
|
||||
"""
|
||||
|
||||
class _DisplayMode:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
@@ -1053,13 +1170,17 @@ class Config(google.protobuf.message.Message):
|
||||
HEADING_BOLD_FIELD_NUMBER: builtins.int
|
||||
WAKE_ON_TAP_OR_MOTION_FIELD_NUMBER: builtins.int
|
||||
COMPASS_ORIENTATION_FIELD_NUMBER: builtins.int
|
||||
USE_12H_CLOCK_FIELD_NUMBER: builtins.int
|
||||
USE_LONG_NODE_NAME_FIELD_NUMBER: builtins.int
|
||||
ENABLE_MESSAGE_BUBBLES_FIELD_NUMBER: builtins.int
|
||||
screen_on_secs: builtins.int
|
||||
"""
|
||||
Number of seconds the screen stays on after pressing the user button or receiving a message
|
||||
0 for default of one minute MAXUINT for always on
|
||||
"""
|
||||
gps_format: global___Config.DisplayConfig.GpsCoordinateFormat.ValueType
|
||||
gps_format: global___Config.DisplayConfig.DeprecatedGpsCoordinateFormat.ValueType
|
||||
"""
|
||||
Deprecated in 2.7.4: Unused
|
||||
How the GPS coordinates are formatted on the OLED screen.
|
||||
"""
|
||||
auto_screen_carousel_secs: builtins.int
|
||||
@@ -1100,11 +1221,25 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
Indicates how to rotate or invert the compass output to accurate display on the display.
|
||||
"""
|
||||
use_12h_clock: builtins.bool
|
||||
"""
|
||||
If false (default), the device will display the time in 24-hour format on screen.
|
||||
If true, the device will display the time in 12-hour format on screen.
|
||||
"""
|
||||
use_long_node_name: builtins.bool
|
||||
"""
|
||||
If false (default), the device will use short names for various display screens.
|
||||
If true, node names will show in long format
|
||||
"""
|
||||
enable_message_bubbles: builtins.bool
|
||||
"""
|
||||
If true, the device will display message bubbles on screen.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
screen_on_secs: builtins.int = ...,
|
||||
gps_format: global___Config.DisplayConfig.GpsCoordinateFormat.ValueType = ...,
|
||||
gps_format: global___Config.DisplayConfig.DeprecatedGpsCoordinateFormat.ValueType = ...,
|
||||
auto_screen_carousel_secs: builtins.int = ...,
|
||||
compass_north_top: builtins.bool = ...,
|
||||
flip_screen: builtins.bool = ...,
|
||||
@@ -1114,8 +1249,11 @@ class Config(google.protobuf.message.Message):
|
||||
heading_bold: builtins.bool = ...,
|
||||
wake_on_tap_or_motion: builtins.bool = ...,
|
||||
compass_orientation: global___Config.DisplayConfig.CompassOrientation.ValueType = ...,
|
||||
use_12h_clock: builtins.bool = ...,
|
||||
use_long_node_name: builtins.bool = ...,
|
||||
enable_message_bubbles: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["auto_screen_carousel_secs", b"auto_screen_carousel_secs", "compass_north_top", b"compass_north_top", "compass_orientation", b"compass_orientation", "displaymode", b"displaymode", "flip_screen", b"flip_screen", "gps_format", b"gps_format", "heading_bold", b"heading_bold", "oled", b"oled", "screen_on_secs", b"screen_on_secs", "units", b"units", "wake_on_tap_or_motion", b"wake_on_tap_or_motion"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["auto_screen_carousel_secs", b"auto_screen_carousel_secs", "compass_north_top", b"compass_north_top", "compass_orientation", b"compass_orientation", "displaymode", b"displaymode", "enable_message_bubbles", b"enable_message_bubbles", "flip_screen", b"flip_screen", "gps_format", b"gps_format", "heading_bold", b"heading_bold", "oled", b"oled", "screen_on_secs", b"screen_on_secs", "units", b"units", "use_12h_clock", b"use_12h_clock", "use_long_node_name", b"use_long_node_name", "wake_on_tap_or_motion", b"wake_on_tap_or_motion"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class LoRaConfig(google.protobuf.message.Message):
|
||||
@@ -1207,6 +1345,63 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
Singapore 923mhz
|
||||
"""
|
||||
PH_433: Config.LoRaConfig._RegionCode.ValueType # 19
|
||||
"""
|
||||
Philippines 433mhz
|
||||
"""
|
||||
PH_868: Config.LoRaConfig._RegionCode.ValueType # 20
|
||||
"""
|
||||
Philippines 868mhz
|
||||
"""
|
||||
PH_915: Config.LoRaConfig._RegionCode.ValueType # 21
|
||||
"""
|
||||
Philippines 915mhz
|
||||
"""
|
||||
ANZ_433: Config.LoRaConfig._RegionCode.ValueType # 22
|
||||
"""
|
||||
Australia / New Zealand 433MHz
|
||||
"""
|
||||
KZ_433: Config.LoRaConfig._RegionCode.ValueType # 23
|
||||
"""
|
||||
Kazakhstan 433MHz
|
||||
"""
|
||||
KZ_863: Config.LoRaConfig._RegionCode.ValueType # 24
|
||||
"""
|
||||
Kazakhstan 863MHz
|
||||
"""
|
||||
NP_865: Config.LoRaConfig._RegionCode.ValueType # 25
|
||||
"""
|
||||
Nepal 865MHz
|
||||
"""
|
||||
BR_902: Config.LoRaConfig._RegionCode.ValueType # 26
|
||||
"""
|
||||
Brazil 902MHz
|
||||
"""
|
||||
ITU1_2M: Config.LoRaConfig._RegionCode.ValueType # 27
|
||||
"""
|
||||
ITU Region 1 Amateur Radio 2m band (144-146 MHz)
|
||||
"""
|
||||
ITU2_2M: Config.LoRaConfig._RegionCode.ValueType # 28
|
||||
"""
|
||||
ITU Region 2 Amateur Radio 2m band (144-148 MHz)
|
||||
"""
|
||||
EU_866: Config.LoRaConfig._RegionCode.ValueType # 29
|
||||
"""
|
||||
EU 866MHz band (Band no. 47b of 2006/771/EC and subsequent amendments) for Non-specific short-range devices (SRD)
|
||||
"""
|
||||
EU_874: Config.LoRaConfig._RegionCode.ValueType # 30
|
||||
"""
|
||||
EU 874MHz and 917MHz bands (Band no. 1 and 4 of 2022/172/EC and subsequent amendments) for Non-specific short-range devices (SRD)
|
||||
"""
|
||||
EU_917: Config.LoRaConfig._RegionCode.ValueType # 31
|
||||
EU_N_868: Config.LoRaConfig._RegionCode.ValueType # 32
|
||||
"""
|
||||
EU 868MHz band, with narrow presets
|
||||
"""
|
||||
ITU3_2M: Config.LoRaConfig._RegionCode.ValueType # 33
|
||||
"""
|
||||
ITU Region 3 Amateur Radio 2m band (144-148 MHz)
|
||||
"""
|
||||
|
||||
class RegionCode(_RegionCode, metaclass=_RegionCodeEnumTypeWrapper): ...
|
||||
UNSET: Config.LoRaConfig.RegionCode.ValueType # 0
|
||||
@@ -1285,6 +1480,63 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
Singapore 923mhz
|
||||
"""
|
||||
PH_433: Config.LoRaConfig.RegionCode.ValueType # 19
|
||||
"""
|
||||
Philippines 433mhz
|
||||
"""
|
||||
PH_868: Config.LoRaConfig.RegionCode.ValueType # 20
|
||||
"""
|
||||
Philippines 868mhz
|
||||
"""
|
||||
PH_915: Config.LoRaConfig.RegionCode.ValueType # 21
|
||||
"""
|
||||
Philippines 915mhz
|
||||
"""
|
||||
ANZ_433: Config.LoRaConfig.RegionCode.ValueType # 22
|
||||
"""
|
||||
Australia / New Zealand 433MHz
|
||||
"""
|
||||
KZ_433: Config.LoRaConfig.RegionCode.ValueType # 23
|
||||
"""
|
||||
Kazakhstan 433MHz
|
||||
"""
|
||||
KZ_863: Config.LoRaConfig.RegionCode.ValueType # 24
|
||||
"""
|
||||
Kazakhstan 863MHz
|
||||
"""
|
||||
NP_865: Config.LoRaConfig.RegionCode.ValueType # 25
|
||||
"""
|
||||
Nepal 865MHz
|
||||
"""
|
||||
BR_902: Config.LoRaConfig.RegionCode.ValueType # 26
|
||||
"""
|
||||
Brazil 902MHz
|
||||
"""
|
||||
ITU1_2M: Config.LoRaConfig.RegionCode.ValueType # 27
|
||||
"""
|
||||
ITU Region 1 Amateur Radio 2m band (144-146 MHz)
|
||||
"""
|
||||
ITU2_2M: Config.LoRaConfig.RegionCode.ValueType # 28
|
||||
"""
|
||||
ITU Region 2 Amateur Radio 2m band (144-148 MHz)
|
||||
"""
|
||||
EU_866: Config.LoRaConfig.RegionCode.ValueType # 29
|
||||
"""
|
||||
EU 866MHz band (Band no. 47b of 2006/771/EC and subsequent amendments) for Non-specific short-range devices (SRD)
|
||||
"""
|
||||
EU_874: Config.LoRaConfig.RegionCode.ValueType # 30
|
||||
"""
|
||||
EU 874MHz and 917MHz bands (Band no. 1 and 4 of 2022/172/EC and subsequent amendments) for Non-specific short-range devices (SRD)
|
||||
"""
|
||||
EU_917: Config.LoRaConfig.RegionCode.ValueType # 31
|
||||
EU_N_868: Config.LoRaConfig.RegionCode.ValueType # 32
|
||||
"""
|
||||
EU 868MHz band, with narrow presets
|
||||
"""
|
||||
ITU3_2M: Config.LoRaConfig.RegionCode.ValueType # 33
|
||||
"""
|
||||
ITU Region 3 Amateur Radio 2m band (144-148 MHz)
|
||||
"""
|
||||
|
||||
class _ModemPreset:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
@@ -1299,10 +1551,12 @@ class Config(google.protobuf.message.Message):
|
||||
LONG_SLOW: Config.LoRaConfig._ModemPreset.ValueType # 1
|
||||
"""
|
||||
Long Range - Slow
|
||||
Deprecated in 2.7: Unpopular slow preset.
|
||||
"""
|
||||
VERY_LONG_SLOW: Config.LoRaConfig._ModemPreset.ValueType # 2
|
||||
"""
|
||||
Very Long Range - Slow
|
||||
Deprecated in 2.5: Works only with txco and is unusably slow
|
||||
"""
|
||||
MEDIUM_SLOW: Config.LoRaConfig._ModemPreset.ValueType # 3
|
||||
"""
|
||||
@@ -1324,6 +1578,42 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
Long Range - Moderately Fast
|
||||
"""
|
||||
SHORT_TURBO: Config.LoRaConfig._ModemPreset.ValueType # 8
|
||||
"""
|
||||
Short Range - Turbo
|
||||
This is the fastest preset and the only one with 500kHz bandwidth.
|
||||
It is not legal to use in all regions due to this wider bandwidth.
|
||||
"""
|
||||
LONG_TURBO: Config.LoRaConfig._ModemPreset.ValueType # 9
|
||||
"""
|
||||
Long Range - Turbo
|
||||
This preset performs similarly to LongFast, but with 500Khz bandwidth.
|
||||
"""
|
||||
LITE_FAST: Config.LoRaConfig._ModemPreset.ValueType # 10
|
||||
"""
|
||||
Lite Fast
|
||||
Medium range preset optimized for EU 866MHz SRD band with 125kHz bandwidth.
|
||||
Comparable link budget to MEDIUM_FAST but compliant with Band no. 47b of 2006/771/EC.
|
||||
"""
|
||||
LITE_SLOW: Config.LoRaConfig._ModemPreset.ValueType # 11
|
||||
"""
|
||||
Lite Slow
|
||||
Medium-to-moderate range preset optimized for EU 866MHz SRD band with 125kHz bandwidth.
|
||||
Comparable link budget to LONG_FAST but compliant with Band no. 47b of 2006/771/EC.
|
||||
"""
|
||||
NARROW_FAST: Config.LoRaConfig._ModemPreset.ValueType # 12
|
||||
"""
|
||||
Narrow Fast
|
||||
Medium-to-moderate range preset optimized for EU 868MHz band with 62.5kHz bandwidth.
|
||||
Comparable link budget to SHORT_SLOW, but with half the data rate.
|
||||
Intended to avoid interference with other devices.
|
||||
"""
|
||||
NARROW_SLOW: Config.LoRaConfig._ModemPreset.ValueType # 13
|
||||
"""
|
||||
Narrow Slow
|
||||
Moderate range preset optimized for EU 868MHz band with 62.5kHz bandwidth.
|
||||
Comparable link budget and data rate to LONG_FAST.
|
||||
"""
|
||||
|
||||
class ModemPreset(_ModemPreset, metaclass=_ModemPresetEnumTypeWrapper):
|
||||
"""
|
||||
@@ -1338,10 +1628,12 @@ class Config(google.protobuf.message.Message):
|
||||
LONG_SLOW: Config.LoRaConfig.ModemPreset.ValueType # 1
|
||||
"""
|
||||
Long Range - Slow
|
||||
Deprecated in 2.7: Unpopular slow preset.
|
||||
"""
|
||||
VERY_LONG_SLOW: Config.LoRaConfig.ModemPreset.ValueType # 2
|
||||
"""
|
||||
Very Long Range - Slow
|
||||
Deprecated in 2.5: Works only with txco and is unusably slow
|
||||
"""
|
||||
MEDIUM_SLOW: Config.LoRaConfig.ModemPreset.ValueType # 3
|
||||
"""
|
||||
@@ -1363,6 +1655,75 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
Long Range - Moderately Fast
|
||||
"""
|
||||
SHORT_TURBO: Config.LoRaConfig.ModemPreset.ValueType # 8
|
||||
"""
|
||||
Short Range - Turbo
|
||||
This is the fastest preset and the only one with 500kHz bandwidth.
|
||||
It is not legal to use in all regions due to this wider bandwidth.
|
||||
"""
|
||||
LONG_TURBO: Config.LoRaConfig.ModemPreset.ValueType # 9
|
||||
"""
|
||||
Long Range - Turbo
|
||||
This preset performs similarly to LongFast, but with 500Khz bandwidth.
|
||||
"""
|
||||
LITE_FAST: Config.LoRaConfig.ModemPreset.ValueType # 10
|
||||
"""
|
||||
Lite Fast
|
||||
Medium range preset optimized for EU 866MHz SRD band with 125kHz bandwidth.
|
||||
Comparable link budget to MEDIUM_FAST but compliant with Band no. 47b of 2006/771/EC.
|
||||
"""
|
||||
LITE_SLOW: Config.LoRaConfig.ModemPreset.ValueType # 11
|
||||
"""
|
||||
Lite Slow
|
||||
Medium-to-moderate range preset optimized for EU 866MHz SRD band with 125kHz bandwidth.
|
||||
Comparable link budget to LONG_FAST but compliant with Band no. 47b of 2006/771/EC.
|
||||
"""
|
||||
NARROW_FAST: Config.LoRaConfig.ModemPreset.ValueType # 12
|
||||
"""
|
||||
Narrow Fast
|
||||
Medium-to-moderate range preset optimized for EU 868MHz band with 62.5kHz bandwidth.
|
||||
Comparable link budget to SHORT_SLOW, but with half the data rate.
|
||||
Intended to avoid interference with other devices.
|
||||
"""
|
||||
NARROW_SLOW: Config.LoRaConfig.ModemPreset.ValueType # 13
|
||||
"""
|
||||
Narrow Slow
|
||||
Moderate range preset optimized for EU 868MHz band with 62.5kHz bandwidth.
|
||||
Comparable link budget and data rate to LONG_FAST.
|
||||
"""
|
||||
|
||||
class _FEM_LNA_Mode:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _FEM_LNA_ModeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Config.LoRaConfig._FEM_LNA_Mode.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
DISABLED: Config.LoRaConfig._FEM_LNA_Mode.ValueType # 0
|
||||
"""
|
||||
FEM_LNA is present but disabled
|
||||
"""
|
||||
ENABLED: Config.LoRaConfig._FEM_LNA_Mode.ValueType # 1
|
||||
"""
|
||||
FEM_LNA is present and enabled
|
||||
"""
|
||||
NOT_PRESENT: Config.LoRaConfig._FEM_LNA_Mode.ValueType # 2
|
||||
"""
|
||||
FEM_LNA is not present on the device
|
||||
"""
|
||||
|
||||
class FEM_LNA_Mode(_FEM_LNA_Mode, metaclass=_FEM_LNA_ModeEnumTypeWrapper): ...
|
||||
DISABLED: Config.LoRaConfig.FEM_LNA_Mode.ValueType # 0
|
||||
"""
|
||||
FEM_LNA is present but disabled
|
||||
"""
|
||||
ENABLED: Config.LoRaConfig.FEM_LNA_Mode.ValueType # 1
|
||||
"""
|
||||
FEM_LNA is present and enabled
|
||||
"""
|
||||
NOT_PRESENT: Config.LoRaConfig.FEM_LNA_Mode.ValueType # 2
|
||||
"""
|
||||
FEM_LNA is not present on the device
|
||||
"""
|
||||
|
||||
USE_PRESET_FIELD_NUMBER: builtins.int
|
||||
MODEM_PRESET_FIELD_NUMBER: builtins.int
|
||||
@@ -1381,6 +1742,9 @@ class Config(google.protobuf.message.Message):
|
||||
PA_FAN_DISABLED_FIELD_NUMBER: builtins.int
|
||||
IGNORE_INCOMING_FIELD_NUMBER: builtins.int
|
||||
IGNORE_MQTT_FIELD_NUMBER: builtins.int
|
||||
CONFIG_OK_TO_MQTT_FIELD_NUMBER: builtins.int
|
||||
FEM_LNA_MODE_FIELD_NUMBER: builtins.int
|
||||
SERIAL_HAL_ONLY_FIELD_NUMBER: builtins.int
|
||||
use_preset: builtins.bool
|
||||
"""
|
||||
When enabled, the `modem_preset` fields will be adhered to, else the `bandwidth`/`spread_factor`/`coding_rate`
|
||||
@@ -1474,6 +1838,18 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
If true, the device will not process any packets received via LoRa that passed via MQTT anywhere on the path towards it.
|
||||
"""
|
||||
config_ok_to_mqtt: builtins.bool
|
||||
"""
|
||||
Sets the ok_to_mqtt bit on outgoing packets
|
||||
"""
|
||||
fem_lna_mode: global___Config.LoRaConfig.FEM_LNA_Mode.ValueType
|
||||
"""
|
||||
Set where LORA FEM is enabled, disabled, or not present
|
||||
"""
|
||||
serial_hal_only: builtins.bool
|
||||
"""
|
||||
Don't use radiolib to initialize the radio, instead listen for a serialHal connection
|
||||
"""
|
||||
@property
|
||||
def ignore_incoming(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]:
|
||||
"""
|
||||
@@ -1502,8 +1878,11 @@ class Config(google.protobuf.message.Message):
|
||||
pa_fan_disabled: builtins.bool = ...,
|
||||
ignore_incoming: collections.abc.Iterable[builtins.int] | None = ...,
|
||||
ignore_mqtt: builtins.bool = ...,
|
||||
config_ok_to_mqtt: builtins.bool = ...,
|
||||
fem_lna_mode: global___Config.LoRaConfig.FEM_LNA_Mode.ValueType = ...,
|
||||
serial_hal_only: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["bandwidth", b"bandwidth", "channel_num", b"channel_num", "coding_rate", b"coding_rate", "frequency_offset", b"frequency_offset", "hop_limit", b"hop_limit", "ignore_incoming", b"ignore_incoming", "ignore_mqtt", b"ignore_mqtt", "modem_preset", b"modem_preset", "override_duty_cycle", b"override_duty_cycle", "override_frequency", b"override_frequency", "pa_fan_disabled", b"pa_fan_disabled", "region", b"region", "spread_factor", b"spread_factor", "sx126x_rx_boosted_gain", b"sx126x_rx_boosted_gain", "tx_enabled", b"tx_enabled", "tx_power", b"tx_power", "use_preset", b"use_preset"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["bandwidth", b"bandwidth", "channel_num", b"channel_num", "coding_rate", b"coding_rate", "config_ok_to_mqtt", b"config_ok_to_mqtt", "fem_lna_mode", b"fem_lna_mode", "frequency_offset", b"frequency_offset", "hop_limit", b"hop_limit", "ignore_incoming", b"ignore_incoming", "ignore_mqtt", b"ignore_mqtt", "modem_preset", b"modem_preset", "override_duty_cycle", b"override_duty_cycle", "override_frequency", b"override_frequency", "pa_fan_disabled", b"pa_fan_disabled", "region", b"region", "serial_hal_only", b"serial_hal_only", "spread_factor", b"spread_factor", "sx126x_rx_boosted_gain", b"sx126x_rx_boosted_gain", "tx_enabled", b"tx_enabled", "tx_power", b"tx_power", "use_preset", b"use_preset"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class BluetoothConfig(google.protobuf.message.Message):
|
||||
@@ -1545,7 +1924,6 @@ class Config(google.protobuf.message.Message):
|
||||
ENABLED_FIELD_NUMBER: builtins.int
|
||||
MODE_FIELD_NUMBER: builtins.int
|
||||
FIXED_PIN_FIELD_NUMBER: builtins.int
|
||||
DEVICE_LOGGING_ENABLED_FIELD_NUMBER: builtins.int
|
||||
enabled: builtins.bool
|
||||
"""
|
||||
Enable Bluetooth on the device
|
||||
@@ -1558,19 +1936,84 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
Specified PIN for PairingMode.FixedPin
|
||||
"""
|
||||
device_logging_enabled: builtins.bool
|
||||
"""
|
||||
Enables device (serial style logs) over Bluetooth
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
enabled: builtins.bool = ...,
|
||||
mode: global___Config.BluetoothConfig.PairingMode.ValueType = ...,
|
||||
fixed_pin: builtins.int = ...,
|
||||
device_logging_enabled: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["device_logging_enabled", b"device_logging_enabled", "enabled", b"enabled", "fixed_pin", b"fixed_pin", "mode", b"mode"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["enabled", b"enabled", "fixed_pin", b"fixed_pin", "mode", b"mode"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class SecurityConfig(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
PUBLIC_KEY_FIELD_NUMBER: builtins.int
|
||||
PRIVATE_KEY_FIELD_NUMBER: builtins.int
|
||||
ADMIN_KEY_FIELD_NUMBER: builtins.int
|
||||
IS_MANAGED_FIELD_NUMBER: builtins.int
|
||||
SERIAL_ENABLED_FIELD_NUMBER: builtins.int
|
||||
DEBUG_LOG_API_ENABLED_FIELD_NUMBER: builtins.int
|
||||
ADMIN_CHANNEL_ENABLED_FIELD_NUMBER: builtins.int
|
||||
public_key: builtins.bytes
|
||||
"""
|
||||
The public key of the user's device.
|
||||
Sent out to other nodes on the mesh to allow them to compute a shared secret key.
|
||||
"""
|
||||
private_key: builtins.bytes
|
||||
"""
|
||||
The private key of the device.
|
||||
Used to create a shared key with a remote device.
|
||||
"""
|
||||
is_managed: builtins.bool
|
||||
"""
|
||||
If true, device is considered to be "managed" by a mesh administrator via admin messages
|
||||
Device is managed by a mesh administrator.
|
||||
"""
|
||||
serial_enabled: builtins.bool
|
||||
"""
|
||||
Serial Console over the Stream API."
|
||||
"""
|
||||
debug_log_api_enabled: builtins.bool
|
||||
"""
|
||||
By default we turn off logging as soon as an API client connects (to keep shared serial link quiet).
|
||||
Output live debug logging over serial or bluetooth is set to true.
|
||||
"""
|
||||
admin_channel_enabled: builtins.bool
|
||||
"""
|
||||
Allow incoming device control over the insecure legacy admin channel.
|
||||
"""
|
||||
@property
|
||||
def admin_key(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.bytes]:
|
||||
"""
|
||||
The public key authorized to send admin messages to this node.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
public_key: builtins.bytes = ...,
|
||||
private_key: builtins.bytes = ...,
|
||||
admin_key: collections.abc.Iterable[builtins.bytes] | None = ...,
|
||||
is_managed: builtins.bool = ...,
|
||||
serial_enabled: builtins.bool = ...,
|
||||
debug_log_api_enabled: builtins.bool = ...,
|
||||
admin_channel_enabled: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["admin_channel_enabled", b"admin_channel_enabled", "admin_key", b"admin_key", "debug_log_api_enabled", b"debug_log_api_enabled", "is_managed", b"is_managed", "private_key", b"private_key", "public_key", b"public_key", "serial_enabled", b"serial_enabled"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class SessionkeyConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
Blank config request, strictly for getting the session key
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
) -> None: ...
|
||||
|
||||
DEVICE_FIELD_NUMBER: builtins.int
|
||||
POSITION_FIELD_NUMBER: builtins.int
|
||||
@@ -1579,6 +2022,9 @@ class Config(google.protobuf.message.Message):
|
||||
DISPLAY_FIELD_NUMBER: builtins.int
|
||||
LORA_FIELD_NUMBER: builtins.int
|
||||
BLUETOOTH_FIELD_NUMBER: builtins.int
|
||||
SECURITY_FIELD_NUMBER: builtins.int
|
||||
SESSIONKEY_FIELD_NUMBER: builtins.int
|
||||
DEVICE_UI_FIELD_NUMBER: builtins.int
|
||||
@property
|
||||
def device(self) -> global___Config.DeviceConfig: ...
|
||||
@property
|
||||
@@ -1593,6 +2039,12 @@ class Config(google.protobuf.message.Message):
|
||||
def lora(self) -> global___Config.LoRaConfig: ...
|
||||
@property
|
||||
def bluetooth(self) -> global___Config.BluetoothConfig: ...
|
||||
@property
|
||||
def security(self) -> global___Config.SecurityConfig: ...
|
||||
@property
|
||||
def sessionkey(self) -> global___Config.SessionkeyConfig: ...
|
||||
@property
|
||||
def device_ui(self) -> meshtastic.protobuf.device_ui_pb2.DeviceUIConfig: ...
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -1603,9 +2055,12 @@ class Config(google.protobuf.message.Message):
|
||||
display: global___Config.DisplayConfig | None = ...,
|
||||
lora: global___Config.LoRaConfig | None = ...,
|
||||
bluetooth: global___Config.BluetoothConfig | None = ...,
|
||||
security: global___Config.SecurityConfig | None = ...,
|
||||
sessionkey: global___Config.SessionkeyConfig | None = ...,
|
||||
device_ui: meshtastic.protobuf.device_ui_pb2.DeviceUIConfig | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "display", b"display", "lora", b"lora", "network", b"network", "payload_variant", b"payload_variant", "position", b"position", "power", b"power"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "display", b"display", "lora", b"lora", "network", b"network", "payload_variant", b"payload_variant", "position", b"position", "power", b"power"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["device", "position", "power", "network", "display", "lora", "bluetooth"] | None: ...
|
||||
def HasField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "device_ui", b"device_ui", "display", b"display", "lora", b"lora", "network", b"network", "payload_variant", b"payload_variant", "position", b"position", "power", b"power", "security", b"security", "sessionkey", b"sessionkey"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "device_ui", b"device_ui", "display", b"display", "lora", b"lora", "network", b"network", "payload_variant", b"payload_variant", "position", b"position", "power", b"power", "security", b"security", "sessionkey", b"sessionkey"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["device", "position", "power", "network", "display", "lora", "bluetooth", "security", "sessionkey", "device_ui"] | None: ...
|
||||
|
||||
global___Config = Config
|
||||
|
||||
31
meshtastic/protobuf/connection_status_pb2.py
generated
31
meshtastic/protobuf/connection_status_pb2.py
generated
@@ -11,26 +11,29 @@ from google.protobuf.internal import builder as _builder
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n+meshtastic/protobuf/connection_status.proto\x12\x13meshtastic.protobuf\"\xd5\x02\n\x16\x44\x65viceConnectionStatus\x12<\n\x04wifi\x18\x01 \x01(\x0b\x32).meshtastic.protobuf.WifiConnectionStatusH\x00\x88\x01\x01\x12\x44\n\x08\x65thernet\x18\x02 \x01(\x0b\x32-.meshtastic.protobuf.EthernetConnectionStatusH\x01\x88\x01\x01\x12\x46\n\tbluetooth\x18\x03 \x01(\x0b\x32..meshtastic.protobuf.BluetoothConnectionStatusH\x02\x88\x01\x01\x12@\n\x06serial\x18\x04 \x01(\x0b\x32+.meshtastic.protobuf.SerialConnectionStatusH\x03\x88\x01\x01\x42\x07\n\x05_wifiB\x0b\n\t_ethernetB\x0c\n\n_bluetoothB\t\n\x07_serial\"p\n\x14WifiConnectionStatus\x12<\n\x06status\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.NetworkConnectionStatus\x12\x0c\n\x04ssid\x18\x02 \x01(\t\x12\x0c\n\x04rssi\x18\x03 \x01(\x05\"X\n\x18\x45thernetConnectionStatus\x12<\n\x06status\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.NetworkConnectionStatus\"{\n\x17NetworkConnectionStatus\x12\x12\n\nip_address\x18\x01 \x01(\x07\x12\x14\n\x0cis_connected\x18\x02 \x01(\x08\x12\x19\n\x11is_mqtt_connected\x18\x03 \x01(\x08\x12\x1b\n\x13is_syslog_connected\x18\x04 \x01(\x08\"L\n\x19\x42luetoothConnectionStatus\x12\x0b\n\x03pin\x18\x01 \x01(\r\x12\x0c\n\x04rssi\x18\x02 \x01(\x05\x12\x14\n\x0cis_connected\x18\x03 \x01(\x08\"<\n\x16SerialConnectionStatus\x12\x0c\n\x04\x62\x61ud\x18\x01 \x01(\r\x12\x14\n\x0cis_connected\x18\x02 \x01(\x08\x42\x65\n\x13\x63om.geeksville.meshB\x10\x43onnStatusProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n+meshtastic/protobuf/connection_status.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/nanopb.proto\"\xd5\x02\n\x16\x44\x65viceConnectionStatus\x12<\n\x04wifi\x18\x01 \x01(\x0b\x32).meshtastic.protobuf.WifiConnectionStatusH\x00\x88\x01\x01\x12\x44\n\x08\x65thernet\x18\x02 \x01(\x0b\x32-.meshtastic.protobuf.EthernetConnectionStatusH\x01\x88\x01\x01\x12\x46\n\tbluetooth\x18\x03 \x01(\x0b\x32..meshtastic.protobuf.BluetoothConnectionStatusH\x02\x88\x01\x01\x12@\n\x06serial\x18\x04 \x01(\x0b\x32+.meshtastic.protobuf.SerialConnectionStatusH\x03\x88\x01\x01\x42\x07\n\x05_wifiB\x0b\n\t_ethernetB\x0c\n\n_bluetoothB\t\n\x07_serial\"w\n\x14WifiConnectionStatus\x12<\n\x06status\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.NetworkConnectionStatus\x12\x13\n\x04ssid\x18\x02 \x01(\tB\x05\x92?\x02\x08!\x12\x0c\n\x04rssi\x18\x03 \x01(\x05\"X\n\x18\x45thernetConnectionStatus\x12<\n\x06status\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.NetworkConnectionStatus\"{\n\x17NetworkConnectionStatus\x12\x12\n\nip_address\x18\x01 \x01(\x07\x12\x14\n\x0cis_connected\x18\x02 \x01(\x08\x12\x19\n\x11is_mqtt_connected\x18\x03 \x01(\x08\x12\x1b\n\x13is_syslog_connected\x18\x04 \x01(\x08\"L\n\x19\x42luetoothConnectionStatus\x12\x0b\n\x03pin\x18\x01 \x01(\r\x12\x0c\n\x04rssi\x18\x02 \x01(\x05\x12\x14\n\x0cis_connected\x18\x03 \x01(\x08\"<\n\x16SerialConnectionStatus\x12\x0c\n\x04\x62\x61ud\x18\x01 \x01(\r\x12\x14\n\x0cis_connected\x18\x02 \x01(\x08\x42\x66\n\x14org.meshtastic.protoB\x10\x43onnStatusProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.connection_status_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\020ConnStatusProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_globals['_DEVICECONNECTIONSTATUS']._serialized_start=69
|
||||
_globals['_DEVICECONNECTIONSTATUS']._serialized_end=410
|
||||
_globals['_WIFICONNECTIONSTATUS']._serialized_start=412
|
||||
_globals['_WIFICONNECTIONSTATUS']._serialized_end=524
|
||||
_globals['_ETHERNETCONNECTIONSTATUS']._serialized_start=526
|
||||
_globals['_ETHERNETCONNECTIONSTATUS']._serialized_end=614
|
||||
_globals['_NETWORKCONNECTIONSTATUS']._serialized_start=616
|
||||
_globals['_NETWORKCONNECTIONSTATUS']._serialized_end=739
|
||||
_globals['_BLUETOOTHCONNECTIONSTATUS']._serialized_start=741
|
||||
_globals['_BLUETOOTHCONNECTIONSTATUS']._serialized_end=817
|
||||
_globals['_SERIALCONNECTIONSTATUS']._serialized_start=819
|
||||
_globals['_SERIALCONNECTIONSTATUS']._serialized_end=879
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\020ConnStatusProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_WIFICONNECTIONSTATUS.fields_by_name['ssid']._options = None
|
||||
_WIFICONNECTIONSTATUS.fields_by_name['ssid']._serialized_options = b'\222?\002\010!'
|
||||
_globals['_DEVICECONNECTIONSTATUS']._serialized_start=103
|
||||
_globals['_DEVICECONNECTIONSTATUS']._serialized_end=444
|
||||
_globals['_WIFICONNECTIONSTATUS']._serialized_start=446
|
||||
_globals['_WIFICONNECTIONSTATUS']._serialized_end=565
|
||||
_globals['_ETHERNETCONNECTIONSTATUS']._serialized_start=567
|
||||
_globals['_ETHERNETCONNECTIONSTATUS']._serialized_end=655
|
||||
_globals['_NETWORKCONNECTIONSTATUS']._serialized_start=657
|
||||
_globals['_NETWORKCONNECTIONSTATUS']._serialized_end=780
|
||||
_globals['_BLUETOOTHCONNECTIONSTATUS']._serialized_start=782
|
||||
_globals['_BLUETOOTHCONNECTIONSTATUS']._serialized_end=858
|
||||
_globals['_SERIALCONNECTIONSTATUS']._serialized_start=860
|
||||
_globals['_SERIALCONNECTIONSTATUS']._serialized_end=920
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
67
meshtastic/protobuf/device_ui_pb2.py
generated
Normal file
67
meshtastic/protobuf/device_ui_pb2.py
generated
Normal file
@@ -0,0 +1,67 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: meshtastic/protobuf/device_ui.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/device_ui.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/nanopb.proto\"\xa9\x06\n\x0e\x44\x65viceUIConfig\x12\x0f\n\x07version\x18\x01 \x01(\r\x12 \n\x11screen_brightness\x18\x02 \x01(\rB\x05\x92?\x02\x38\x08\x12\x1d\n\x0escreen_timeout\x18\x03 \x01(\rB\x05\x92?\x02\x38\x10\x12\x13\n\x0bscreen_lock\x18\x04 \x01(\x08\x12\x15\n\rsettings_lock\x18\x05 \x01(\x08\x12\x10\n\x08pin_code\x18\x06 \x01(\r\x12)\n\x05theme\x18\x07 \x01(\x0e\x32\x1a.meshtastic.protobuf.Theme\x12\x15\n\ralert_enabled\x18\x08 \x01(\x08\x12\x16\n\x0e\x62\x61nner_enabled\x18\t \x01(\x08\x12\x1b\n\x0cring_tone_id\x18\n \x01(\rB\x05\x92?\x02\x38\x08\x12/\n\x08language\x18\x0b \x01(\x0e\x32\x1d.meshtastic.protobuf.Language\x12\x34\n\x0bnode_filter\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.NodeFilter\x12:\n\x0enode_highlight\x18\r \x01(\x0b\x32\".meshtastic.protobuf.NodeHighlight\x12\x1f\n\x10\x63\x61libration_data\x18\x0e \x01(\x0c\x42\x05\x92?\x02\x08\x10\x12*\n\x08map_data\x18\x0f \x01(\x0b\x32\x18.meshtastic.protobuf.Map\x12=\n\x0c\x63ompass_mode\x18\x10 \x01(\x0e\x32 .meshtastic.protobuf.CompassModeB\x05\x92?\x02\x38\x08\x12\x18\n\x10screen_rgb_color\x18\x11 \x01(\r\x12\x1b\n\x13is_clockface_analog\x18\x12 \x01(\x08\x12R\n\ngps_format\x18\x13 \x01(\x0e\x32\x37.meshtastic.protobuf.DeviceUIConfig.GpsCoordinateFormatB\x05\x92?\x02\x38\x08\"V\n\x13GpsCoordinateFormat\x12\x07\n\x03\x44\x45\x43\x10\x00\x12\x07\n\x03\x44MS\x10\x01\x12\x07\n\x03UTM\x10\x02\x12\x08\n\x04MGRS\x10\x03\x12\x07\n\x03OLC\x10\x04\x12\x08\n\x04OSGR\x10\x05\x12\x07\n\x03MLS\x10\x06\"\xbc\x01\n\nNodeFilter\x12\x16\n\x0eunknown_switch\x18\x01 \x01(\x08\x12\x16\n\x0eoffline_switch\x18\x02 \x01(\x08\x12\x19\n\x11public_key_switch\x18\x03 \x01(\x08\x12\x18\n\thops_away\x18\x04 \x01(\x05\x42\x05\x92?\x02\x38\x08\x12\x17\n\x0fposition_switch\x18\x05 \x01(\x08\x12\x18\n\tnode_name\x18\x06 \x01(\tB\x05\x92?\x02\x08\x10\x12\x16\n\x07\x63hannel\x18\x07 \x01(\x05\x42\x05\x92?\x02\x38\x08\"\x85\x01\n\rNodeHighlight\x12\x13\n\x0b\x63hat_switch\x18\x01 \x01(\x08\x12\x17\n\x0fposition_switch\x18\x02 \x01(\x08\x12\x18\n\x10telemetry_switch\x18\x03 \x01(\x08\x12\x12\n\niaq_switch\x18\x04 \x01(\x08\x12\x18\n\tnode_name\x18\x05 \x01(\tB\x05\x92?\x02\x08\x10\"D\n\x08GeoPoint\x12\x13\n\x04zoom\x18\x01 \x01(\x05\x42\x05\x92?\x02\x38\x08\x12\x10\n\x08latitude\x18\x02 \x01(\x05\x12\x11\n\tlongitude\x18\x03 \x01(\x05\"\\\n\x03Map\x12+\n\x04home\x18\x01 \x01(\x0b\x32\x1d.meshtastic.protobuf.GeoPoint\x12\x14\n\x05style\x18\x02 \x01(\tB\x05\x92?\x02\x08\x14\x12\x12\n\nfollow_gps\x18\x03 \x01(\x08*>\n\x0b\x43ompassMode\x12\x0b\n\x07\x44YNAMIC\x10\x00\x12\x0e\n\nFIXED_RING\x10\x01\x12\x12\n\x0e\x46REEZE_HEADING\x10\x02*%\n\x05Theme\x12\x08\n\x04\x44\x41RK\x10\x00\x12\t\n\x05LIGHT\x10\x01\x12\x07\n\x03RED\x10\x02*\xc0\x02\n\x08Language\x12\x0b\n\x07\x45NGLISH\x10\x00\x12\n\n\x06\x46RENCH\x10\x01\x12\n\n\x06GERMAN\x10\x02\x12\x0b\n\x07ITALIAN\x10\x03\x12\x0e\n\nPORTUGUESE\x10\x04\x12\x0b\n\x07SPANISH\x10\x05\x12\x0b\n\x07SWEDISH\x10\x06\x12\x0b\n\x07\x46INNISH\x10\x07\x12\n\n\x06POLISH\x10\x08\x12\x0b\n\x07TURKISH\x10\t\x12\x0b\n\x07SERBIAN\x10\n\x12\x0b\n\x07RUSSIAN\x10\x0b\x12\t\n\x05\x44UTCH\x10\x0c\x12\t\n\x05GREEK\x10\r\x12\r\n\tNORWEGIAN\x10\x0e\x12\r\n\tSLOVENIAN\x10\x0f\x12\r\n\tUKRAINIAN\x10\x10\x12\r\n\tBULGARIAN\x10\x11\x12\t\n\x05\x43ZECH\x10\x12\x12\n\n\x06\x44\x41NISH\x10\x13\x12\x16\n\x12SIMPLIFIED_CHINESE\x10\x1e\x12\x17\n\x13TRADITIONAL_CHINESE\x10\x1f\x42\x64\n\x14org.meshtastic.protoB\x0e\x44\x65viceUIProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.device_ui_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\016DeviceUIProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_DEVICEUICONFIG.fields_by_name['screen_brightness']._options = None
|
||||
_DEVICEUICONFIG.fields_by_name['screen_brightness']._serialized_options = b'\222?\0028\010'
|
||||
_DEVICEUICONFIG.fields_by_name['screen_timeout']._options = None
|
||||
_DEVICEUICONFIG.fields_by_name['screen_timeout']._serialized_options = b'\222?\0028\020'
|
||||
_DEVICEUICONFIG.fields_by_name['ring_tone_id']._options = None
|
||||
_DEVICEUICONFIG.fields_by_name['ring_tone_id']._serialized_options = b'\222?\0028\010'
|
||||
_DEVICEUICONFIG.fields_by_name['calibration_data']._options = None
|
||||
_DEVICEUICONFIG.fields_by_name['calibration_data']._serialized_options = b'\222?\002\010\020'
|
||||
_DEVICEUICONFIG.fields_by_name['compass_mode']._options = None
|
||||
_DEVICEUICONFIG.fields_by_name['compass_mode']._serialized_options = b'\222?\0028\010'
|
||||
_DEVICEUICONFIG.fields_by_name['gps_format']._options = None
|
||||
_DEVICEUICONFIG.fields_by_name['gps_format']._serialized_options = b'\222?\0028\010'
|
||||
_NODEFILTER.fields_by_name['hops_away']._options = None
|
||||
_NODEFILTER.fields_by_name['hops_away']._serialized_options = b'\222?\0028\010'
|
||||
_NODEFILTER.fields_by_name['node_name']._options = None
|
||||
_NODEFILTER.fields_by_name['node_name']._serialized_options = b'\222?\002\010\020'
|
||||
_NODEFILTER.fields_by_name['channel']._options = None
|
||||
_NODEFILTER.fields_by_name['channel']._serialized_options = b'\222?\0028\010'
|
||||
_NODEHIGHLIGHT.fields_by_name['node_name']._options = None
|
||||
_NODEHIGHLIGHT.fields_by_name['node_name']._serialized_options = b'\222?\002\010\020'
|
||||
_GEOPOINT.fields_by_name['zoom']._options = None
|
||||
_GEOPOINT.fields_by_name['zoom']._serialized_options = b'\222?\0028\010'
|
||||
_MAP.fields_by_name['style']._options = None
|
||||
_MAP.fields_by_name['style']._serialized_options = b'\222?\002\010\024'
|
||||
_globals['_COMPASSMODE']._serialized_start=1397
|
||||
_globals['_COMPASSMODE']._serialized_end=1459
|
||||
_globals['_THEME']._serialized_start=1461
|
||||
_globals['_THEME']._serialized_end=1498
|
||||
_globals['_LANGUAGE']._serialized_start=1501
|
||||
_globals['_LANGUAGE']._serialized_end=1821
|
||||
_globals['_DEVICEUICONFIG']._serialized_start=95
|
||||
_globals['_DEVICEUICONFIG']._serialized_end=904
|
||||
_globals['_DEVICEUICONFIG_GPSCOORDINATEFORMAT']._serialized_start=818
|
||||
_globals['_DEVICEUICONFIG_GPSCOORDINATEFORMAT']._serialized_end=904
|
||||
_globals['_NODEFILTER']._serialized_start=907
|
||||
_globals['_NODEFILTER']._serialized_end=1095
|
||||
_globals['_NODEHIGHLIGHT']._serialized_start=1098
|
||||
_globals['_NODEHIGHLIGHT']._serialized_end=1231
|
||||
_globals['_GEOPOINT']._serialized_start=1233
|
||||
_globals['_GEOPOINT']._serialized_end=1301
|
||||
_globals['_MAP']._serialized_start=1303
|
||||
_globals['_MAP']._serialized_end=1395
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
649
meshtastic/protobuf/device_ui_pb2.pyi
generated
Normal file
649
meshtastic/protobuf/device_ui_pb2.pyi
generated
Normal file
@@ -0,0 +1,649 @@
|
||||
"""
|
||||
@generated by mypy-protobuf. Do not edit manually!
|
||||
isort:skip_file
|
||||
"""
|
||||
|
||||
import builtins
|
||||
import google.protobuf.descriptor
|
||||
import google.protobuf.internal.enum_type_wrapper
|
||||
import google.protobuf.message
|
||||
import sys
|
||||
import typing
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
import typing as typing_extensions
|
||||
else:
|
||||
import typing_extensions
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
|
||||
|
||||
class _CompassMode:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _CompassModeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_CompassMode.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
DYNAMIC: _CompassMode.ValueType # 0
|
||||
"""
|
||||
Compass with dynamic ring and heading
|
||||
"""
|
||||
FIXED_RING: _CompassMode.ValueType # 1
|
||||
"""
|
||||
Compass with fixed ring and heading
|
||||
"""
|
||||
FREEZE_HEADING: _CompassMode.ValueType # 2
|
||||
"""
|
||||
Compass with heading and freeze option
|
||||
"""
|
||||
|
||||
class CompassMode(_CompassMode, metaclass=_CompassModeEnumTypeWrapper): ...
|
||||
|
||||
DYNAMIC: CompassMode.ValueType # 0
|
||||
"""
|
||||
Compass with dynamic ring and heading
|
||||
"""
|
||||
FIXED_RING: CompassMode.ValueType # 1
|
||||
"""
|
||||
Compass with fixed ring and heading
|
||||
"""
|
||||
FREEZE_HEADING: CompassMode.ValueType # 2
|
||||
"""
|
||||
Compass with heading and freeze option
|
||||
"""
|
||||
global___CompassMode = CompassMode
|
||||
|
||||
class _Theme:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _ThemeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_Theme.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
DARK: _Theme.ValueType # 0
|
||||
"""
|
||||
Dark
|
||||
"""
|
||||
LIGHT: _Theme.ValueType # 1
|
||||
"""
|
||||
Light
|
||||
"""
|
||||
RED: _Theme.ValueType # 2
|
||||
"""
|
||||
Red
|
||||
"""
|
||||
|
||||
class Theme(_Theme, metaclass=_ThemeEnumTypeWrapper): ...
|
||||
|
||||
DARK: Theme.ValueType # 0
|
||||
"""
|
||||
Dark
|
||||
"""
|
||||
LIGHT: Theme.ValueType # 1
|
||||
"""
|
||||
Light
|
||||
"""
|
||||
RED: Theme.ValueType # 2
|
||||
"""
|
||||
Red
|
||||
"""
|
||||
global___Theme = Theme
|
||||
|
||||
class _Language:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _LanguageEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_Language.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
ENGLISH: _Language.ValueType # 0
|
||||
"""
|
||||
English
|
||||
"""
|
||||
FRENCH: _Language.ValueType # 1
|
||||
"""
|
||||
French
|
||||
"""
|
||||
GERMAN: _Language.ValueType # 2
|
||||
"""
|
||||
German
|
||||
"""
|
||||
ITALIAN: _Language.ValueType # 3
|
||||
"""
|
||||
Italian
|
||||
"""
|
||||
PORTUGUESE: _Language.ValueType # 4
|
||||
"""
|
||||
Portuguese
|
||||
"""
|
||||
SPANISH: _Language.ValueType # 5
|
||||
"""
|
||||
Spanish
|
||||
"""
|
||||
SWEDISH: _Language.ValueType # 6
|
||||
"""
|
||||
Swedish
|
||||
"""
|
||||
FINNISH: _Language.ValueType # 7
|
||||
"""
|
||||
Finnish
|
||||
"""
|
||||
POLISH: _Language.ValueType # 8
|
||||
"""
|
||||
Polish
|
||||
"""
|
||||
TURKISH: _Language.ValueType # 9
|
||||
"""
|
||||
Turkish
|
||||
"""
|
||||
SERBIAN: _Language.ValueType # 10
|
||||
"""
|
||||
Serbian
|
||||
"""
|
||||
RUSSIAN: _Language.ValueType # 11
|
||||
"""
|
||||
Russian
|
||||
"""
|
||||
DUTCH: _Language.ValueType # 12
|
||||
"""
|
||||
Dutch
|
||||
"""
|
||||
GREEK: _Language.ValueType # 13
|
||||
"""
|
||||
Greek
|
||||
"""
|
||||
NORWEGIAN: _Language.ValueType # 14
|
||||
"""
|
||||
Norwegian
|
||||
"""
|
||||
SLOVENIAN: _Language.ValueType # 15
|
||||
"""
|
||||
Slovenian
|
||||
"""
|
||||
UKRAINIAN: _Language.ValueType # 16
|
||||
"""
|
||||
Ukrainian
|
||||
"""
|
||||
BULGARIAN: _Language.ValueType # 17
|
||||
"""
|
||||
Bulgarian
|
||||
"""
|
||||
CZECH: _Language.ValueType # 18
|
||||
"""
|
||||
Czech
|
||||
"""
|
||||
DANISH: _Language.ValueType # 19
|
||||
"""
|
||||
Danish
|
||||
"""
|
||||
SIMPLIFIED_CHINESE: _Language.ValueType # 30
|
||||
"""
|
||||
Simplified Chinese (experimental)
|
||||
"""
|
||||
TRADITIONAL_CHINESE: _Language.ValueType # 31
|
||||
"""
|
||||
Traditional Chinese (experimental)
|
||||
"""
|
||||
|
||||
class Language(_Language, metaclass=_LanguageEnumTypeWrapper):
|
||||
"""
|
||||
Localization
|
||||
"""
|
||||
|
||||
ENGLISH: Language.ValueType # 0
|
||||
"""
|
||||
English
|
||||
"""
|
||||
FRENCH: Language.ValueType # 1
|
||||
"""
|
||||
French
|
||||
"""
|
||||
GERMAN: Language.ValueType # 2
|
||||
"""
|
||||
German
|
||||
"""
|
||||
ITALIAN: Language.ValueType # 3
|
||||
"""
|
||||
Italian
|
||||
"""
|
||||
PORTUGUESE: Language.ValueType # 4
|
||||
"""
|
||||
Portuguese
|
||||
"""
|
||||
SPANISH: Language.ValueType # 5
|
||||
"""
|
||||
Spanish
|
||||
"""
|
||||
SWEDISH: Language.ValueType # 6
|
||||
"""
|
||||
Swedish
|
||||
"""
|
||||
FINNISH: Language.ValueType # 7
|
||||
"""
|
||||
Finnish
|
||||
"""
|
||||
POLISH: Language.ValueType # 8
|
||||
"""
|
||||
Polish
|
||||
"""
|
||||
TURKISH: Language.ValueType # 9
|
||||
"""
|
||||
Turkish
|
||||
"""
|
||||
SERBIAN: Language.ValueType # 10
|
||||
"""
|
||||
Serbian
|
||||
"""
|
||||
RUSSIAN: Language.ValueType # 11
|
||||
"""
|
||||
Russian
|
||||
"""
|
||||
DUTCH: Language.ValueType # 12
|
||||
"""
|
||||
Dutch
|
||||
"""
|
||||
GREEK: Language.ValueType # 13
|
||||
"""
|
||||
Greek
|
||||
"""
|
||||
NORWEGIAN: Language.ValueType # 14
|
||||
"""
|
||||
Norwegian
|
||||
"""
|
||||
SLOVENIAN: Language.ValueType # 15
|
||||
"""
|
||||
Slovenian
|
||||
"""
|
||||
UKRAINIAN: Language.ValueType # 16
|
||||
"""
|
||||
Ukrainian
|
||||
"""
|
||||
BULGARIAN: Language.ValueType # 17
|
||||
"""
|
||||
Bulgarian
|
||||
"""
|
||||
CZECH: Language.ValueType # 18
|
||||
"""
|
||||
Czech
|
||||
"""
|
||||
DANISH: Language.ValueType # 19
|
||||
"""
|
||||
Danish
|
||||
"""
|
||||
SIMPLIFIED_CHINESE: Language.ValueType # 30
|
||||
"""
|
||||
Simplified Chinese (experimental)
|
||||
"""
|
||||
TRADITIONAL_CHINESE: Language.ValueType # 31
|
||||
"""
|
||||
Traditional Chinese (experimental)
|
||||
"""
|
||||
global___Language = Language
|
||||
|
||||
@typing.final
|
||||
class DeviceUIConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
Protobuf structures for device-ui persistency
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
class _GpsCoordinateFormat:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _GpsCoordinateFormatEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[DeviceUIConfig._GpsCoordinateFormat.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
DEC: DeviceUIConfig._GpsCoordinateFormat.ValueType # 0
|
||||
"""
|
||||
GPS coordinates are displayed in the normal decimal degrees format:
|
||||
DD.DDDDDD DDD.DDDDDD
|
||||
"""
|
||||
DMS: DeviceUIConfig._GpsCoordinateFormat.ValueType # 1
|
||||
"""
|
||||
GPS coordinates are displayed in the degrees minutes seconds format:
|
||||
DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant
|
||||
"""
|
||||
UTM: DeviceUIConfig._GpsCoordinateFormat.ValueType # 2
|
||||
"""
|
||||
Universal Transverse Mercator format:
|
||||
ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing
|
||||
"""
|
||||
MGRS: DeviceUIConfig._GpsCoordinateFormat.ValueType # 3
|
||||
"""
|
||||
Military Grid Reference System format:
|
||||
ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
|
||||
E is easting, N is northing
|
||||
"""
|
||||
OLC: DeviceUIConfig._GpsCoordinateFormat.ValueType # 4
|
||||
"""
|
||||
Open Location Code (aka Plus Codes).
|
||||
"""
|
||||
OSGR: DeviceUIConfig._GpsCoordinateFormat.ValueType # 5
|
||||
"""
|
||||
Ordnance Survey Grid Reference (the National Grid System of the UK).
|
||||
Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
|
||||
E is the easting, N is the northing
|
||||
"""
|
||||
MLS: DeviceUIConfig._GpsCoordinateFormat.ValueType # 6
|
||||
"""
|
||||
Maidenhead Locator System
|
||||
Described here: https://en.wikipedia.org/wiki/Maidenhead_Locator_System
|
||||
"""
|
||||
|
||||
class GpsCoordinateFormat(_GpsCoordinateFormat, metaclass=_GpsCoordinateFormatEnumTypeWrapper):
|
||||
"""
|
||||
How the GPS coordinates are displayed on the OLED screen.
|
||||
"""
|
||||
|
||||
DEC: DeviceUIConfig.GpsCoordinateFormat.ValueType # 0
|
||||
"""
|
||||
GPS coordinates are displayed in the normal decimal degrees format:
|
||||
DD.DDDDDD DDD.DDDDDD
|
||||
"""
|
||||
DMS: DeviceUIConfig.GpsCoordinateFormat.ValueType # 1
|
||||
"""
|
||||
GPS coordinates are displayed in the degrees minutes seconds format:
|
||||
DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant
|
||||
"""
|
||||
UTM: DeviceUIConfig.GpsCoordinateFormat.ValueType # 2
|
||||
"""
|
||||
Universal Transverse Mercator format:
|
||||
ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing
|
||||
"""
|
||||
MGRS: DeviceUIConfig.GpsCoordinateFormat.ValueType # 3
|
||||
"""
|
||||
Military Grid Reference System format:
|
||||
ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
|
||||
E is easting, N is northing
|
||||
"""
|
||||
OLC: DeviceUIConfig.GpsCoordinateFormat.ValueType # 4
|
||||
"""
|
||||
Open Location Code (aka Plus Codes).
|
||||
"""
|
||||
OSGR: DeviceUIConfig.GpsCoordinateFormat.ValueType # 5
|
||||
"""
|
||||
Ordnance Survey Grid Reference (the National Grid System of the UK).
|
||||
Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
|
||||
E is the easting, N is the northing
|
||||
"""
|
||||
MLS: DeviceUIConfig.GpsCoordinateFormat.ValueType # 6
|
||||
"""
|
||||
Maidenhead Locator System
|
||||
Described here: https://en.wikipedia.org/wiki/Maidenhead_Locator_System
|
||||
"""
|
||||
|
||||
VERSION_FIELD_NUMBER: builtins.int
|
||||
SCREEN_BRIGHTNESS_FIELD_NUMBER: builtins.int
|
||||
SCREEN_TIMEOUT_FIELD_NUMBER: builtins.int
|
||||
SCREEN_LOCK_FIELD_NUMBER: builtins.int
|
||||
SETTINGS_LOCK_FIELD_NUMBER: builtins.int
|
||||
PIN_CODE_FIELD_NUMBER: builtins.int
|
||||
THEME_FIELD_NUMBER: builtins.int
|
||||
ALERT_ENABLED_FIELD_NUMBER: builtins.int
|
||||
BANNER_ENABLED_FIELD_NUMBER: builtins.int
|
||||
RING_TONE_ID_FIELD_NUMBER: builtins.int
|
||||
LANGUAGE_FIELD_NUMBER: builtins.int
|
||||
NODE_FILTER_FIELD_NUMBER: builtins.int
|
||||
NODE_HIGHLIGHT_FIELD_NUMBER: builtins.int
|
||||
CALIBRATION_DATA_FIELD_NUMBER: builtins.int
|
||||
MAP_DATA_FIELD_NUMBER: builtins.int
|
||||
COMPASS_MODE_FIELD_NUMBER: builtins.int
|
||||
SCREEN_RGB_COLOR_FIELD_NUMBER: builtins.int
|
||||
IS_CLOCKFACE_ANALOG_FIELD_NUMBER: builtins.int
|
||||
GPS_FORMAT_FIELD_NUMBER: builtins.int
|
||||
version: builtins.int
|
||||
"""
|
||||
A version integer used to invalidate saved files when we make incompatible changes.
|
||||
"""
|
||||
screen_brightness: builtins.int
|
||||
"""
|
||||
TFT display brightness 1..255
|
||||
"""
|
||||
screen_timeout: builtins.int
|
||||
"""
|
||||
Screen timeout 0..900
|
||||
"""
|
||||
screen_lock: builtins.bool
|
||||
"""
|
||||
Screen/Settings lock enabled
|
||||
"""
|
||||
settings_lock: builtins.bool
|
||||
pin_code: builtins.int
|
||||
theme: global___Theme.ValueType
|
||||
"""
|
||||
Color theme
|
||||
"""
|
||||
alert_enabled: builtins.bool
|
||||
"""
|
||||
Audible message, banner and ring tone
|
||||
"""
|
||||
banner_enabled: builtins.bool
|
||||
ring_tone_id: builtins.int
|
||||
language: global___Language.ValueType
|
||||
"""
|
||||
Localization
|
||||
"""
|
||||
calibration_data: builtins.bytes
|
||||
"""
|
||||
8 integers for screen calibration data
|
||||
"""
|
||||
compass_mode: global___CompassMode.ValueType
|
||||
"""
|
||||
Compass mode
|
||||
"""
|
||||
screen_rgb_color: builtins.int
|
||||
"""
|
||||
RGB color for BaseUI
|
||||
0xRRGGBB format, e.g. 0xFF0000 for red
|
||||
"""
|
||||
is_clockface_analog: builtins.bool
|
||||
"""
|
||||
Clockface analog style
|
||||
true for analog clockface, false for digital clockface
|
||||
"""
|
||||
gps_format: global___DeviceUIConfig.GpsCoordinateFormat.ValueType
|
||||
"""
|
||||
How the GPS coordinates are formatted on the OLED screen.
|
||||
"""
|
||||
@property
|
||||
def node_filter(self) -> global___NodeFilter:
|
||||
"""
|
||||
Node list filter
|
||||
"""
|
||||
|
||||
@property
|
||||
def node_highlight(self) -> global___NodeHighlight:
|
||||
"""
|
||||
Node list highlightening
|
||||
"""
|
||||
|
||||
@property
|
||||
def map_data(self) -> global___Map:
|
||||
"""
|
||||
Map related data
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
version: builtins.int = ...,
|
||||
screen_brightness: builtins.int = ...,
|
||||
screen_timeout: builtins.int = ...,
|
||||
screen_lock: builtins.bool = ...,
|
||||
settings_lock: builtins.bool = ...,
|
||||
pin_code: builtins.int = ...,
|
||||
theme: global___Theme.ValueType = ...,
|
||||
alert_enabled: builtins.bool = ...,
|
||||
banner_enabled: builtins.bool = ...,
|
||||
ring_tone_id: builtins.int = ...,
|
||||
language: global___Language.ValueType = ...,
|
||||
node_filter: global___NodeFilter | None = ...,
|
||||
node_highlight: global___NodeHighlight | None = ...,
|
||||
calibration_data: builtins.bytes = ...,
|
||||
map_data: global___Map | None = ...,
|
||||
compass_mode: global___CompassMode.ValueType = ...,
|
||||
screen_rgb_color: builtins.int = ...,
|
||||
is_clockface_analog: builtins.bool = ...,
|
||||
gps_format: global___DeviceUIConfig.GpsCoordinateFormat.ValueType = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["map_data", b"map_data", "node_filter", b"node_filter", "node_highlight", b"node_highlight"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["alert_enabled", b"alert_enabled", "banner_enabled", b"banner_enabled", "calibration_data", b"calibration_data", "compass_mode", b"compass_mode", "gps_format", b"gps_format", "is_clockface_analog", b"is_clockface_analog", "language", b"language", "map_data", b"map_data", "node_filter", b"node_filter", "node_highlight", b"node_highlight", "pin_code", b"pin_code", "ring_tone_id", b"ring_tone_id", "screen_brightness", b"screen_brightness", "screen_lock", b"screen_lock", "screen_rgb_color", b"screen_rgb_color", "screen_timeout", b"screen_timeout", "settings_lock", b"settings_lock", "theme", b"theme", "version", b"version"]) -> None: ...
|
||||
|
||||
global___DeviceUIConfig = DeviceUIConfig
|
||||
|
||||
@typing.final
|
||||
class NodeFilter(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
UNKNOWN_SWITCH_FIELD_NUMBER: builtins.int
|
||||
OFFLINE_SWITCH_FIELD_NUMBER: builtins.int
|
||||
PUBLIC_KEY_SWITCH_FIELD_NUMBER: builtins.int
|
||||
HOPS_AWAY_FIELD_NUMBER: builtins.int
|
||||
POSITION_SWITCH_FIELD_NUMBER: builtins.int
|
||||
NODE_NAME_FIELD_NUMBER: builtins.int
|
||||
CHANNEL_FIELD_NUMBER: builtins.int
|
||||
unknown_switch: builtins.bool
|
||||
"""
|
||||
Filter unknown nodes
|
||||
"""
|
||||
offline_switch: builtins.bool
|
||||
"""
|
||||
Filter offline nodes
|
||||
"""
|
||||
public_key_switch: builtins.bool
|
||||
"""
|
||||
Filter nodes w/o public key
|
||||
"""
|
||||
hops_away: builtins.int
|
||||
"""
|
||||
Filter based on hops away
|
||||
"""
|
||||
position_switch: builtins.bool
|
||||
"""
|
||||
Filter nodes w/o position
|
||||
"""
|
||||
node_name: builtins.str
|
||||
"""
|
||||
Filter nodes by matching name string
|
||||
"""
|
||||
channel: builtins.int
|
||||
"""
|
||||
Filter based on channel
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
unknown_switch: builtins.bool = ...,
|
||||
offline_switch: builtins.bool = ...,
|
||||
public_key_switch: builtins.bool = ...,
|
||||
hops_away: builtins.int = ...,
|
||||
position_switch: builtins.bool = ...,
|
||||
node_name: builtins.str = ...,
|
||||
channel: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["channel", b"channel", "hops_away", b"hops_away", "node_name", b"node_name", "offline_switch", b"offline_switch", "position_switch", b"position_switch", "public_key_switch", b"public_key_switch", "unknown_switch", b"unknown_switch"]) -> None: ...
|
||||
|
||||
global___NodeFilter = NodeFilter
|
||||
|
||||
@typing.final
|
||||
class NodeHighlight(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
CHAT_SWITCH_FIELD_NUMBER: builtins.int
|
||||
POSITION_SWITCH_FIELD_NUMBER: builtins.int
|
||||
TELEMETRY_SWITCH_FIELD_NUMBER: builtins.int
|
||||
IAQ_SWITCH_FIELD_NUMBER: builtins.int
|
||||
NODE_NAME_FIELD_NUMBER: builtins.int
|
||||
chat_switch: builtins.bool
|
||||
"""
|
||||
Hightlight nodes w/ active chat
|
||||
"""
|
||||
position_switch: builtins.bool
|
||||
"""
|
||||
Highlight nodes w/ position
|
||||
"""
|
||||
telemetry_switch: builtins.bool
|
||||
"""
|
||||
Highlight nodes w/ telemetry data
|
||||
"""
|
||||
iaq_switch: builtins.bool
|
||||
"""
|
||||
Highlight nodes w/ iaq data
|
||||
"""
|
||||
node_name: builtins.str
|
||||
"""
|
||||
Highlight nodes by matching name string
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
chat_switch: builtins.bool = ...,
|
||||
position_switch: builtins.bool = ...,
|
||||
telemetry_switch: builtins.bool = ...,
|
||||
iaq_switch: builtins.bool = ...,
|
||||
node_name: builtins.str = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["chat_switch", b"chat_switch", "iaq_switch", b"iaq_switch", "node_name", b"node_name", "position_switch", b"position_switch", "telemetry_switch", b"telemetry_switch"]) -> None: ...
|
||||
|
||||
global___NodeHighlight = NodeHighlight
|
||||
|
||||
@typing.final
|
||||
class GeoPoint(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
ZOOM_FIELD_NUMBER: builtins.int
|
||||
LATITUDE_FIELD_NUMBER: builtins.int
|
||||
LONGITUDE_FIELD_NUMBER: builtins.int
|
||||
zoom: builtins.int
|
||||
"""
|
||||
Zoom level
|
||||
"""
|
||||
latitude: builtins.int
|
||||
"""
|
||||
Coordinate: latitude
|
||||
"""
|
||||
longitude: builtins.int
|
||||
"""
|
||||
Coordinate: longitude
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
zoom: builtins.int = ...,
|
||||
latitude: builtins.int = ...,
|
||||
longitude: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["latitude", b"latitude", "longitude", b"longitude", "zoom", b"zoom"]) -> None: ...
|
||||
|
||||
global___GeoPoint = GeoPoint
|
||||
|
||||
@typing.final
|
||||
class Map(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
HOME_FIELD_NUMBER: builtins.int
|
||||
STYLE_FIELD_NUMBER: builtins.int
|
||||
FOLLOW_GPS_FIELD_NUMBER: builtins.int
|
||||
style: builtins.str
|
||||
"""
|
||||
Map tile style
|
||||
"""
|
||||
follow_gps: builtins.bool
|
||||
"""
|
||||
Map scroll follows GPS
|
||||
"""
|
||||
@property
|
||||
def home(self) -> global___GeoPoint:
|
||||
"""
|
||||
Home coordinates
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
home: global___GeoPoint | None = ...,
|
||||
style: builtins.str = ...,
|
||||
follow_gps: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["home", b"home"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["follow_gps", b"follow_gps", "home", b"home", "style", b"style"]) -> None: ...
|
||||
|
||||
global___Map = Map
|
||||
59
meshtastic/protobuf/deviceonly_pb2.py
generated
59
meshtastic/protobuf/deviceonly_pb2.py
generated
@@ -12,34 +12,59 @@ _sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from meshtastic.protobuf import channel_pb2 as meshtastic_dot_protobuf_dot_channel__pb2
|
||||
from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config__pb2
|
||||
from meshtastic.protobuf import localonly_pb2 as meshtastic_dot_protobuf_dot_localonly__pb2
|
||||
from meshtastic.protobuf import mesh_pb2 as meshtastic_dot_protobuf_dot_mesh__pb2
|
||||
from meshtastic.protobuf import telemetry_pb2 as meshtastic_dot_protobuf_dot_telemetry__pb2
|
||||
import nanopb_pb2 as nanopb__pb2
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/deviceonly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a#meshtastic/protobuf/telemetry.proto\x1a\x0cnanopb.proto\"\x99\x01\n\x0cPositionLite\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\x0c\n\x04time\x18\x04 \x01(\x07\x12@\n\x0flocation_source\x18\x05 \x01(\x0e\x32\'.meshtastic.protobuf.Position.LocSource\"\xa1\x02\n\x0cNodeInfoLite\x12\x0b\n\x03num\x18\x01 \x01(\r\x12\'\n\x04user\x18\x02 \x01(\x0b\x32\x19.meshtastic.protobuf.User\x12\x33\n\x08position\x18\x03 \x01(\x0b\x32!.meshtastic.protobuf.PositionLite\x12\x0b\n\x03snr\x18\x04 \x01(\x02\x12\x12\n\nlast_heard\x18\x05 \x01(\x07\x12:\n\x0e\x64\x65vice_metrics\x18\x06 \x01(\x0b\x32\".meshtastic.protobuf.DeviceMetrics\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\r\x12\x10\n\x08via_mqtt\x18\x08 \x01(\x08\x12\x11\n\thops_away\x18\t \x01(\r\x12\x13\n\x0bis_favorite\x18\n \x01(\x08\"\x82\x04\n\x0b\x44\x65viceState\x12\x30\n\x07my_node\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.MyNodeInfo\x12(\n\x05owner\x18\x03 \x01(\x0b\x32\x19.meshtastic.protobuf.User\x12\x36\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12\x38\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x13\n\x07no_save\x18\t \x01(\x08\x42\x02\x18\x01\x12\x15\n\rdid_gps_reset\x18\x0b \x01(\x08\x12\x34\n\x0brx_waypoint\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12M\n\x19node_remote_hardware_pins\x18\r \x03(\x0b\x32*.meshtastic.protobuf.NodeRemoteHardwarePin\x12\x63\n\x0cnode_db_lite\x18\x0e \x03(\x0b\x32!.meshtastic.protobuf.NodeInfoLiteB*\x92?\'\x92\x01$std::vector<meshtastic_NodeInfoLite>\"N\n\x0b\x43hannelFile\x12.\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x1c.meshtastic.protobuf.Channel\x12\x0f\n\x07version\x18\x02 \x01(\r\"\xb2\x02\n\x08OEMStore\x12\x16\n\x0eoem_icon_width\x18\x01 \x01(\r\x12\x17\n\x0foem_icon_height\x18\x02 \x01(\r\x12\x15\n\roem_icon_bits\x18\x03 \x01(\x0c\x12\x32\n\x08oem_font\x18\x04 \x01(\x0e\x32 .meshtastic.protobuf.ScreenFonts\x12\x10\n\x08oem_text\x18\x05 \x01(\t\x12\x13\n\x0boem_aes_key\x18\x06 \x01(\x0c\x12:\n\x10oem_local_config\x18\x07 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfig\x12G\n\x17oem_local_module_config\x18\x08 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfig*>\n\x0bScreenFonts\x12\x0e\n\nFONT_SMALL\x10\x00\x12\x0f\n\x0b\x46ONT_MEDIUM\x10\x01\x12\x0e\n\nFONT_LARGE\x10\x02\x42m\n\x13\x63om.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x92?\x0b\xc2\x01\x08<vector>b\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/deviceonly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a meshtastic/protobuf/config.proto\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a#meshtastic/protobuf/telemetry.proto\x1a meshtastic/protobuf/nanopb.proto\"\x99\x01\n\x0cPositionLite\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\x0c\n\x04time\x18\x04 \x01(\x07\x12@\n\x0flocation_source\x18\x05 \x01(\x0e\x32\'.meshtastic.protobuf.Position.LocSource\"\xb0\x02\n\x08UserLite\x12\x1a\n\x07macaddr\x18\x01 \x01(\x0c\x42\t\x18\x01\x92?\x04\x08\x06x\x01\x12\x18\n\tlong_name\x18\x02 \x01(\tB\x05\x92?\x02\x08(\x12\x19\n\nshort_name\x18\x03 \x01(\tB\x05\x92?\x02\x08\x05\x12\x34\n\x08hw_model\x18\x04 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x13\n\x0bis_licensed\x18\x05 \x01(\x08\x12;\n\x04role\x18\x06 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x19\n\npublic_key\x18\x07 \x01(\x0c\x42\x05\x92?\x02\x08 \x12\x1c\n\x0fis_unmessagable\x18\t \x01(\x08H\x00\x88\x01\x01\x42\x12\n\x10_is_unmessagable\"\x85\x03\n\x0cNodeInfoLite\x12\x0b\n\x03num\x18\x01 \x01(\r\x12+\n\x04user\x18\x02 \x01(\x0b\x32\x1d.meshtastic.protobuf.UserLite\x12\x33\n\x08position\x18\x03 \x01(\x0b\x32!.meshtastic.protobuf.PositionLite\x12\x0b\n\x03snr\x18\x04 \x01(\x02\x12\x12\n\nlast_heard\x18\x05 \x01(\x07\x12:\n\x0e\x64\x65vice_metrics\x18\x06 \x01(\x0b\x32\".meshtastic.protobuf.DeviceMetrics\x12\x16\n\x07\x63hannel\x18\x07 \x01(\rB\x05\x92?\x02\x38\x08\x12\x10\n\x08via_mqtt\x18\x08 \x01(\x08\x12\x1d\n\thops_away\x18\t \x01(\rB\x05\x92?\x02\x38\x08H\x00\x88\x01\x01\x12\x13\n\x0bis_favorite\x18\n \x01(\x08\x12\x12\n\nis_ignored\x18\x0b \x01(\x08\x12\x17\n\x08next_hop\x18\x0c \x01(\rB\x05\x92?\x02\x38\x08\x12\x10\n\x08\x62itfield\x18\r \x01(\rB\x0c\n\n_hops_away\"\xaf\x03\n\x0b\x44\x65viceState\x12\x30\n\x07my_node\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.MyNodeInfo\x12(\n\x05owner\x18\x03 \x01(\x0b\x32\x19.meshtastic.protobuf.User\x12=\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x1f.meshtastic.protobuf.MeshPacketB\x05\x92?\x02\x10\x01\x12\x0f\n\x07version\x18\x08 \x01(\r\x12\x38\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x13\n\x07no_save\x18\t \x01(\x08\x42\x02\x18\x01\x12\x19\n\rdid_gps_reset\x18\x0b \x01(\x08\x42\x02\x18\x01\x12\x34\n\x0brx_waypoint\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12T\n\x19node_remote_hardware_pins\x18\r \x03(\x0b\x32*.meshtastic.protobuf.NodeRemoteHardwarePinB\x05\x92?\x02\x10\x0c\"}\n\x0cNodeDatabase\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\\\n\x05nodes\x18\x02 \x03(\x0b\x32!.meshtastic.protobuf.NodeInfoLiteB*\x92?\'\x92\x01$std::vector<meshtastic_NodeInfoLite>\"U\n\x0b\x43hannelFile\x12\x35\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x1c.meshtastic.protobuf.ChannelB\x05\x92?\x02\x10\x08\x12\x0f\n\x07version\x18\x02 \x01(\r\"\x86\x02\n\x11\x42\x61\x63kupPreferences\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x11\n\ttimestamp\x18\x02 \x01(\x07\x12\x30\n\x06\x63onfig\x18\x03 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfig\x12=\n\rmodule_config\x18\x04 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfig\x12\x32\n\x08\x63hannels\x18\x05 \x01(\x0b\x32 .meshtastic.protobuf.ChannelFile\x12(\n\x05owner\x18\x06 \x01(\x0b\x32\x19.meshtastic.protobuf.UserBn\n\x14org.meshtastic.protoB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x92?\x0b\xc2\x01\x08<vector>b\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.deviceonly_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000\222?\013\302\001\010<vector>'
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000\222?\013\302\001\010<vector>'
|
||||
_USERLITE.fields_by_name['macaddr']._options = None
|
||||
_USERLITE.fields_by_name['macaddr']._serialized_options = b'\030\001\222?\004\010\006x\001'
|
||||
_USERLITE.fields_by_name['long_name']._options = None
|
||||
_USERLITE.fields_by_name['long_name']._serialized_options = b'\222?\002\010('
|
||||
_USERLITE.fields_by_name['short_name']._options = None
|
||||
_USERLITE.fields_by_name['short_name']._serialized_options = b'\222?\002\010\005'
|
||||
_USERLITE.fields_by_name['public_key']._options = None
|
||||
_USERLITE.fields_by_name['public_key']._serialized_options = b'\222?\002\010 '
|
||||
_NODEINFOLITE.fields_by_name['channel']._options = None
|
||||
_NODEINFOLITE.fields_by_name['channel']._serialized_options = b'\222?\0028\010'
|
||||
_NODEINFOLITE.fields_by_name['hops_away']._options = None
|
||||
_NODEINFOLITE.fields_by_name['hops_away']._serialized_options = b'\222?\0028\010'
|
||||
_NODEINFOLITE.fields_by_name['next_hop']._options = None
|
||||
_NODEINFOLITE.fields_by_name['next_hop']._serialized_options = b'\222?\0028\010'
|
||||
_DEVICESTATE.fields_by_name['receive_queue']._options = None
|
||||
_DEVICESTATE.fields_by_name['receive_queue']._serialized_options = b'\222?\002\020\001'
|
||||
_DEVICESTATE.fields_by_name['no_save']._options = None
|
||||
_DEVICESTATE.fields_by_name['no_save']._serialized_options = b'\030\001'
|
||||
_DEVICESTATE.fields_by_name['node_db_lite']._options = None
|
||||
_DEVICESTATE.fields_by_name['node_db_lite']._serialized_options = b'\222?\'\222\001$std::vector<meshtastic_NodeInfoLite>'
|
||||
_globals['_SCREENFONTS']._serialized_start=1570
|
||||
_globals['_SCREENFONTS']._serialized_end=1632
|
||||
_globals['_POSITIONLITE']._serialized_start=217
|
||||
_globals['_POSITIONLITE']._serialized_end=370
|
||||
_globals['_NODEINFOLITE']._serialized_start=373
|
||||
_globals['_NODEINFOLITE']._serialized_end=662
|
||||
_globals['_DEVICESTATE']._serialized_start=665
|
||||
_globals['_DEVICESTATE']._serialized_end=1179
|
||||
_globals['_CHANNELFILE']._serialized_start=1181
|
||||
_globals['_CHANNELFILE']._serialized_end=1259
|
||||
_globals['_OEMSTORE']._serialized_start=1262
|
||||
_globals['_OEMSTORE']._serialized_end=1568
|
||||
_DEVICESTATE.fields_by_name['did_gps_reset']._options = None
|
||||
_DEVICESTATE.fields_by_name['did_gps_reset']._serialized_options = b'\030\001'
|
||||
_DEVICESTATE.fields_by_name['node_remote_hardware_pins']._options = None
|
||||
_DEVICESTATE.fields_by_name['node_remote_hardware_pins']._serialized_options = b'\222?\002\020\014'
|
||||
_NODEDATABASE.fields_by_name['nodes']._options = None
|
||||
_NODEDATABASE.fields_by_name['nodes']._serialized_options = b'\222?\'\222\001$std::vector<meshtastic_NodeInfoLite>'
|
||||
_CHANNELFILE.fields_by_name['channels']._options = None
|
||||
_CHANNELFILE.fields_by_name['channels']._serialized_options = b'\222?\002\020\010'
|
||||
_globals['_POSITIONLITE']._serialized_start=271
|
||||
_globals['_POSITIONLITE']._serialized_end=424
|
||||
_globals['_USERLITE']._serialized_start=427
|
||||
_globals['_USERLITE']._serialized_end=731
|
||||
_globals['_NODEINFOLITE']._serialized_start=734
|
||||
_globals['_NODEINFOLITE']._serialized_end=1123
|
||||
_globals['_DEVICESTATE']._serialized_start=1126
|
||||
_globals['_DEVICESTATE']._serialized_end=1557
|
||||
_globals['_NODEDATABASE']._serialized_start=1559
|
||||
_globals['_NODEDATABASE']._serialized_end=1684
|
||||
_globals['_CHANNELFILE']._serialized_start=1686
|
||||
_globals['_CHANNELFILE']._serialized_end=1771
|
||||
_globals['_BACKUPPREFERENCES']._serialized_start=1774
|
||||
_globals['_BACKUPPREFERENCES']._serialized_end=2036
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
273
meshtastic/protobuf/deviceonly_pb2.pyi
generated
273
meshtastic/protobuf/deviceonly_pb2.pyi
generated
@@ -7,60 +7,16 @@ import builtins
|
||||
import collections.abc
|
||||
import google.protobuf.descriptor
|
||||
import google.protobuf.internal.containers
|
||||
import google.protobuf.internal.enum_type_wrapper
|
||||
import google.protobuf.message
|
||||
import meshtastic.protobuf.channel_pb2
|
||||
import meshtastic.protobuf.config_pb2
|
||||
import meshtastic.protobuf.localonly_pb2
|
||||
import meshtastic.protobuf.mesh_pb2
|
||||
import meshtastic.protobuf.telemetry_pb2
|
||||
import sys
|
||||
import typing
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
import typing as typing_extensions
|
||||
else:
|
||||
import typing_extensions
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
|
||||
|
||||
class _ScreenFonts:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _ScreenFontsEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_ScreenFonts.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
FONT_SMALL: _ScreenFonts.ValueType # 0
|
||||
"""
|
||||
TODO: REPLACE
|
||||
"""
|
||||
FONT_MEDIUM: _ScreenFonts.ValueType # 1
|
||||
"""
|
||||
TODO: REPLACE
|
||||
"""
|
||||
FONT_LARGE: _ScreenFonts.ValueType # 2
|
||||
"""
|
||||
TODO: REPLACE
|
||||
"""
|
||||
|
||||
class ScreenFonts(_ScreenFonts, metaclass=_ScreenFontsEnumTypeWrapper):
|
||||
"""
|
||||
Font sizes for the device screen
|
||||
"""
|
||||
|
||||
FONT_SMALL: ScreenFonts.ValueType # 0
|
||||
"""
|
||||
TODO: REPLACE
|
||||
"""
|
||||
FONT_MEDIUM: ScreenFonts.ValueType # 1
|
||||
"""
|
||||
TODO: REPLACE
|
||||
"""
|
||||
FONT_LARGE: ScreenFonts.ValueType # 2
|
||||
"""
|
||||
TODO: REPLACE
|
||||
"""
|
||||
global___ScreenFonts = ScreenFonts
|
||||
|
||||
@typing.final
|
||||
class PositionLite(google.protobuf.message.Message):
|
||||
"""
|
||||
@@ -112,6 +68,75 @@ class PositionLite(google.protobuf.message.Message):
|
||||
|
||||
global___PositionLite = PositionLite
|
||||
|
||||
@typing.final
|
||||
class UserLite(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
MACADDR_FIELD_NUMBER: builtins.int
|
||||
LONG_NAME_FIELD_NUMBER: builtins.int
|
||||
SHORT_NAME_FIELD_NUMBER: builtins.int
|
||||
HW_MODEL_FIELD_NUMBER: builtins.int
|
||||
IS_LICENSED_FIELD_NUMBER: builtins.int
|
||||
ROLE_FIELD_NUMBER: builtins.int
|
||||
PUBLIC_KEY_FIELD_NUMBER: builtins.int
|
||||
IS_UNMESSAGABLE_FIELD_NUMBER: builtins.int
|
||||
macaddr: builtins.bytes
|
||||
"""
|
||||
This is the addr of the radio.
|
||||
"""
|
||||
long_name: builtins.str
|
||||
"""
|
||||
A full name for this user, i.e. "Kevin Hester"
|
||||
"""
|
||||
short_name: builtins.str
|
||||
"""
|
||||
A VERY short name, ideally two characters.
|
||||
Suitable for a tiny OLED screen
|
||||
"""
|
||||
hw_model: meshtastic.protobuf.mesh_pb2.HardwareModel.ValueType
|
||||
"""
|
||||
TBEAM, HELTEC, etc...
|
||||
Starting in 1.2.11 moved to hw_model enum in the NodeInfo object.
|
||||
Apps will still need the string here for older builds
|
||||
(so OTA update can find the right image), but if the enum is available it will be used instead.
|
||||
"""
|
||||
is_licensed: builtins.bool
|
||||
"""
|
||||
In some regions Ham radio operators have different bandwidth limitations than others.
|
||||
If this user is a licensed operator, set this flag.
|
||||
Also, "long_name" should be their licence number.
|
||||
"""
|
||||
role: meshtastic.protobuf.config_pb2.Config.DeviceConfig.Role.ValueType
|
||||
"""
|
||||
Indicates that the user's role in the mesh
|
||||
"""
|
||||
public_key: builtins.bytes
|
||||
"""
|
||||
The public key of the user's device.
|
||||
This is sent out to other nodes on the mesh to allow them to compute a shared secret key.
|
||||
"""
|
||||
is_unmessagable: builtins.bool
|
||||
"""
|
||||
Whether or not the node can be messaged
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
macaddr: builtins.bytes = ...,
|
||||
long_name: builtins.str = ...,
|
||||
short_name: builtins.str = ...,
|
||||
hw_model: meshtastic.protobuf.mesh_pb2.HardwareModel.ValueType = ...,
|
||||
is_licensed: builtins.bool = ...,
|
||||
role: meshtastic.protobuf.config_pb2.Config.DeviceConfig.Role.ValueType = ...,
|
||||
public_key: builtins.bytes = ...,
|
||||
is_unmessagable: builtins.bool | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_is_unmessagable", b"_is_unmessagable", "is_unmessagable", b"is_unmessagable"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_is_unmessagable", b"_is_unmessagable", "hw_model", b"hw_model", "is_licensed", b"is_licensed", "is_unmessagable", b"is_unmessagable", "long_name", b"long_name", "macaddr", b"macaddr", "public_key", b"public_key", "role", b"role", "short_name", b"short_name"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_is_unmessagable", b"_is_unmessagable"]) -> typing.Literal["is_unmessagable"] | None: ...
|
||||
|
||||
global___UserLite = UserLite
|
||||
|
||||
@typing.final
|
||||
class NodeInfoLite(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
@@ -126,6 +151,9 @@ class NodeInfoLite(google.protobuf.message.Message):
|
||||
VIA_MQTT_FIELD_NUMBER: builtins.int
|
||||
HOPS_AWAY_FIELD_NUMBER: builtins.int
|
||||
IS_FAVORITE_FIELD_NUMBER: builtins.int
|
||||
IS_IGNORED_FIELD_NUMBER: builtins.int
|
||||
NEXT_HOP_FIELD_NUMBER: builtins.int
|
||||
BITFIELD_FIELD_NUMBER: builtins.int
|
||||
num: builtins.int
|
||||
"""
|
||||
The node number
|
||||
@@ -149,15 +177,30 @@ class NodeInfoLite(google.protobuf.message.Message):
|
||||
"""
|
||||
hops_away: builtins.int
|
||||
"""
|
||||
Number of hops away from us this node is (0 if adjacent)
|
||||
Number of hops away from us this node is (0 if direct neighbor)
|
||||
"""
|
||||
is_favorite: builtins.bool
|
||||
"""
|
||||
True if node is in our favorites list
|
||||
Persists between NodeDB internal clean ups
|
||||
"""
|
||||
is_ignored: builtins.bool
|
||||
"""
|
||||
True if node is in our ignored list
|
||||
Persists between NodeDB internal clean ups
|
||||
"""
|
||||
next_hop: builtins.int
|
||||
"""
|
||||
Last byte of the node number of the node that should be used as the next hop to reach this node.
|
||||
"""
|
||||
bitfield: builtins.int
|
||||
"""
|
||||
Bitfield for storing booleans.
|
||||
LSB 0 is_key_manually_verified
|
||||
LSB 1 is_muted
|
||||
"""
|
||||
@property
|
||||
def user(self) -> meshtastic.protobuf.mesh_pb2.User:
|
||||
def user(self) -> global___UserLite:
|
||||
"""
|
||||
The user info for this node
|
||||
"""
|
||||
@@ -179,18 +222,22 @@ class NodeInfoLite(google.protobuf.message.Message):
|
||||
self,
|
||||
*,
|
||||
num: builtins.int = ...,
|
||||
user: meshtastic.protobuf.mesh_pb2.User | None = ...,
|
||||
user: global___UserLite | None = ...,
|
||||
position: global___PositionLite | None = ...,
|
||||
snr: builtins.float = ...,
|
||||
last_heard: builtins.int = ...,
|
||||
device_metrics: meshtastic.protobuf.telemetry_pb2.DeviceMetrics | None = ...,
|
||||
channel: builtins.int = ...,
|
||||
via_mqtt: builtins.bool = ...,
|
||||
hops_away: builtins.int = ...,
|
||||
hops_away: builtins.int | None = ...,
|
||||
is_favorite: builtins.bool = ...,
|
||||
is_ignored: builtins.bool = ...,
|
||||
next_hop: builtins.int = ...,
|
||||
bitfield: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["device_metrics", b"device_metrics", "position", b"position", "user", b"user"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "last_heard", b"last_heard", "num", b"num", "position", b"position", "snr", b"snr", "user", b"user", "via_mqtt", b"via_mqtt"]) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "position", b"position", "user", b"user"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "bitfield", b"bitfield", "channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "is_ignored", b"is_ignored", "last_heard", b"last_heard", "next_hop", b"next_hop", "num", b"num", "position", b"position", "snr", b"snr", "user", b"user", "via_mqtt", b"via_mqtt"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_hops_away", b"_hops_away"]) -> typing.Literal["hops_away"] | None: ...
|
||||
|
||||
global___NodeInfoLite = NodeInfoLite
|
||||
|
||||
@@ -215,7 +262,6 @@ class DeviceState(google.protobuf.message.Message):
|
||||
DID_GPS_RESET_FIELD_NUMBER: builtins.int
|
||||
RX_WAYPOINT_FIELD_NUMBER: builtins.int
|
||||
NODE_REMOTE_HARDWARE_PINS_FIELD_NUMBER: builtins.int
|
||||
NODE_DB_LITE_FIELD_NUMBER: builtins.int
|
||||
version: builtins.int
|
||||
"""
|
||||
A version integer used to invalidate old save files when we make
|
||||
@@ -230,7 +276,8 @@ class DeviceState(google.protobuf.message.Message):
|
||||
"""
|
||||
did_gps_reset: builtins.bool
|
||||
"""
|
||||
Some GPS receivers seem to have bogus settings from the factory, so we always do one factory reset.
|
||||
Previously used to manage GPS factory resets.
|
||||
Deprecated in 2.5.23
|
||||
"""
|
||||
@property
|
||||
def my_node(self) -> meshtastic.protobuf.mesh_pb2.MyNodeInfo:
|
||||
@@ -272,12 +319,6 @@ class DeviceState(google.protobuf.message.Message):
|
||||
The mesh's nodes with their available gpio pins for RemoteHardware module
|
||||
"""
|
||||
|
||||
@property
|
||||
def node_db_lite(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___NodeInfoLite]:
|
||||
"""
|
||||
New lite version of NodeDB to decrease memory footprint
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -290,13 +331,40 @@ class DeviceState(google.protobuf.message.Message):
|
||||
did_gps_reset: builtins.bool = ...,
|
||||
rx_waypoint: meshtastic.protobuf.mesh_pb2.MeshPacket | None = ...,
|
||||
node_remote_hardware_pins: collections.abc.Iterable[meshtastic.protobuf.mesh_pb2.NodeRemoteHardwarePin] | None = ...,
|
||||
node_db_lite: collections.abc.Iterable[global___NodeInfoLite] | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["my_node", b"my_node", "owner", b"owner", "rx_text_message", b"rx_text_message", "rx_waypoint", b"rx_waypoint"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["did_gps_reset", b"did_gps_reset", "my_node", b"my_node", "no_save", b"no_save", "node_db_lite", b"node_db_lite", "node_remote_hardware_pins", b"node_remote_hardware_pins", "owner", b"owner", "receive_queue", b"receive_queue", "rx_text_message", b"rx_text_message", "rx_waypoint", b"rx_waypoint", "version", b"version"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["did_gps_reset", b"did_gps_reset", "my_node", b"my_node", "no_save", b"no_save", "node_remote_hardware_pins", b"node_remote_hardware_pins", "owner", b"owner", "receive_queue", b"receive_queue", "rx_text_message", b"rx_text_message", "rx_waypoint", b"rx_waypoint", "version", b"version"]) -> None: ...
|
||||
|
||||
global___DeviceState = DeviceState
|
||||
|
||||
@typing.final
|
||||
class NodeDatabase(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
VERSION_FIELD_NUMBER: builtins.int
|
||||
NODES_FIELD_NUMBER: builtins.int
|
||||
version: builtins.int
|
||||
"""
|
||||
A version integer used to invalidate old save files when we make
|
||||
incompatible changes This integer is set at build time and is private to
|
||||
NodeDB.cpp in the device code.
|
||||
"""
|
||||
@property
|
||||
def nodes(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___NodeInfoLite]:
|
||||
"""
|
||||
New lite version of NodeDB to decrease memory footprint
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
version: builtins.int = ...,
|
||||
nodes: collections.abc.Iterable[global___NodeInfoLite] | None = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["nodes", b"nodes", "version", b"version"]) -> None: ...
|
||||
|
||||
global___NodeDatabase = NodeDatabase
|
||||
|
||||
@typing.final
|
||||
class ChannelFile(google.protobuf.message.Message):
|
||||
"""
|
||||
@@ -330,71 +398,62 @@ class ChannelFile(google.protobuf.message.Message):
|
||||
global___ChannelFile = ChannelFile
|
||||
|
||||
@typing.final
|
||||
class OEMStore(google.protobuf.message.Message):
|
||||
class BackupPreferences(google.protobuf.message.Message):
|
||||
"""
|
||||
This can be used for customizing the firmware distribution. If populated,
|
||||
show a secondary bootup screen with custom logo and text for 2.5 seconds.
|
||||
The on-disk backup of the node's preferences
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
OEM_ICON_WIDTH_FIELD_NUMBER: builtins.int
|
||||
OEM_ICON_HEIGHT_FIELD_NUMBER: builtins.int
|
||||
OEM_ICON_BITS_FIELD_NUMBER: builtins.int
|
||||
OEM_FONT_FIELD_NUMBER: builtins.int
|
||||
OEM_TEXT_FIELD_NUMBER: builtins.int
|
||||
OEM_AES_KEY_FIELD_NUMBER: builtins.int
|
||||
OEM_LOCAL_CONFIG_FIELD_NUMBER: builtins.int
|
||||
OEM_LOCAL_MODULE_CONFIG_FIELD_NUMBER: builtins.int
|
||||
oem_icon_width: builtins.int
|
||||
VERSION_FIELD_NUMBER: builtins.int
|
||||
TIMESTAMP_FIELD_NUMBER: builtins.int
|
||||
CONFIG_FIELD_NUMBER: builtins.int
|
||||
MODULE_CONFIG_FIELD_NUMBER: builtins.int
|
||||
CHANNELS_FIELD_NUMBER: builtins.int
|
||||
OWNER_FIELD_NUMBER: builtins.int
|
||||
version: builtins.int
|
||||
"""
|
||||
The Logo width in Px
|
||||
The version of the backup
|
||||
"""
|
||||
oem_icon_height: builtins.int
|
||||
timestamp: builtins.int
|
||||
"""
|
||||
The Logo height in Px
|
||||
"""
|
||||
oem_icon_bits: builtins.bytes
|
||||
"""
|
||||
The Logo in XBM bytechar format
|
||||
"""
|
||||
oem_font: global___ScreenFonts.ValueType
|
||||
"""
|
||||
Use this font for the OEM text.
|
||||
"""
|
||||
oem_text: builtins.str
|
||||
"""
|
||||
Use this font for the OEM text.
|
||||
"""
|
||||
oem_aes_key: builtins.bytes
|
||||
"""
|
||||
The default device encryption key, 16 or 32 byte
|
||||
The timestamp of the backup (if node has time)
|
||||
"""
|
||||
@property
|
||||
def oem_local_config(self) -> meshtastic.protobuf.localonly_pb2.LocalConfig:
|
||||
def config(self) -> meshtastic.protobuf.localonly_pb2.LocalConfig:
|
||||
"""
|
||||
A Preset LocalConfig to apply during factory reset
|
||||
The node's configuration
|
||||
"""
|
||||
|
||||
@property
|
||||
def oem_local_module_config(self) -> meshtastic.protobuf.localonly_pb2.LocalModuleConfig:
|
||||
def module_config(self) -> meshtastic.protobuf.localonly_pb2.LocalModuleConfig:
|
||||
"""
|
||||
A Preset LocalModuleConfig to apply during factory reset
|
||||
The node's module configuration
|
||||
"""
|
||||
|
||||
@property
|
||||
def channels(self) -> global___ChannelFile:
|
||||
"""
|
||||
The node's channels
|
||||
"""
|
||||
|
||||
@property
|
||||
def owner(self) -> meshtastic.protobuf.mesh_pb2.User:
|
||||
"""
|
||||
The node's user (owner) information
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
oem_icon_width: builtins.int = ...,
|
||||
oem_icon_height: builtins.int = ...,
|
||||
oem_icon_bits: builtins.bytes = ...,
|
||||
oem_font: global___ScreenFonts.ValueType = ...,
|
||||
oem_text: builtins.str = ...,
|
||||
oem_aes_key: builtins.bytes = ...,
|
||||
oem_local_config: meshtastic.protobuf.localonly_pb2.LocalConfig | None = ...,
|
||||
oem_local_module_config: meshtastic.protobuf.localonly_pb2.LocalModuleConfig | None = ...,
|
||||
version: builtins.int = ...,
|
||||
timestamp: builtins.int = ...,
|
||||
config: meshtastic.protobuf.localonly_pb2.LocalConfig | None = ...,
|
||||
module_config: meshtastic.protobuf.localonly_pb2.LocalModuleConfig | None = ...,
|
||||
channels: global___ChannelFile | None = ...,
|
||||
owner: meshtastic.protobuf.mesh_pb2.User | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["oem_local_config", b"oem_local_config", "oem_local_module_config", b"oem_local_module_config"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["oem_aes_key", b"oem_aes_key", "oem_font", b"oem_font", "oem_icon_bits", b"oem_icon_bits", "oem_icon_height", b"oem_icon_height", "oem_icon_width", b"oem_icon_width", "oem_local_config", b"oem_local_config", "oem_local_module_config", b"oem_local_module_config", "oem_text", b"oem_text"]) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["channels", b"channels", "config", b"config", "module_config", b"module_config", "owner", b"owner"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["channels", b"channels", "config", b"config", "module_config", b"module_config", "owner", b"owner", "timestamp", b"timestamp", "version", b"version"]) -> None: ...
|
||||
|
||||
global___OEMStore = OEMStore
|
||||
global___BackupPreferences = BackupPreferences
|
||||
|
||||
33
meshtastic/protobuf/interdevice_pb2.py
generated
Normal file
33
meshtastic/protobuf/interdevice_pb2.py
generated
Normal file
@@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: meshtastic/protobuf/interdevice.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%meshtastic/protobuf/interdevice.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/nanopb.proto\"s\n\nSensorData\x12.\n\x04type\x18\x01 \x01(\x0e\x32 .meshtastic.protobuf.MessageType\x12\x15\n\x0b\x66loat_value\x18\x02 \x01(\x02H\x00\x12\x16\n\x0cuint32_value\x18\x03 \x01(\rH\x00\x42\x06\n\x04\x64\x61ta\"g\n\x12InterdeviceMessage\x12\x16\n\x04nmea\x18\x01 \x01(\tB\x06\x92?\x03\x08\x80\x08H\x00\x12\x31\n\x06sensor\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.SensorDataH\x00\x42\x06\n\x04\x64\x61ta*\xd5\x01\n\x0bMessageType\x12\x07\n\x03\x41\x43K\x10\x00\x12\x15\n\x10\x43OLLECT_INTERVAL\x10\xa0\x01\x12\x0c\n\x07\x42\x45\x45P_ON\x10\xa1\x01\x12\r\n\x08\x42\x45\x45P_OFF\x10\xa2\x01\x12\r\n\x08SHUTDOWN\x10\xa3\x01\x12\r\n\x08POWER_ON\x10\xa4\x01\x12\x0f\n\nSCD41_TEMP\x10\xb0\x01\x12\x13\n\x0eSCD41_HUMIDITY\x10\xb1\x01\x12\x0e\n\tSCD41_CO2\x10\xb2\x01\x12\x0f\n\nAHT20_TEMP\x10\xb3\x01\x12\x13\n\x0e\x41HT20_HUMIDITY\x10\xb4\x01\x12\x0f\n\nTVOC_INDEX\x10\xb5\x01\x42g\n\x14org.meshtastic.protoB\x11InterdeviceProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.interdevice_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\021InterdeviceProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_INTERDEVICEMESSAGE.fields_by_name['nmea']._options = None
|
||||
_INTERDEVICEMESSAGE.fields_by_name['nmea']._serialized_options = b'\222?\003\010\200\010'
|
||||
_globals['_MESSAGETYPE']._serialized_start=319
|
||||
_globals['_MESSAGETYPE']._serialized_end=532
|
||||
_globals['_SENSORDATA']._serialized_start=96
|
||||
_globals['_SENSORDATA']._serialized_end=211
|
||||
_globals['_INTERDEVICEMESSAGE']._serialized_start=213
|
||||
_globals['_INTERDEVICEMESSAGE']._serialized_end=316
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
105
meshtastic/protobuf/interdevice_pb2.pyi
generated
Normal file
105
meshtastic/protobuf/interdevice_pb2.pyi
generated
Normal file
@@ -0,0 +1,105 @@
|
||||
"""
|
||||
@generated by mypy-protobuf. Do not edit manually!
|
||||
isort:skip_file
|
||||
"""
|
||||
|
||||
import builtins
|
||||
import google.protobuf.descriptor
|
||||
import google.protobuf.internal.enum_type_wrapper
|
||||
import google.protobuf.message
|
||||
import sys
|
||||
import typing
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
import typing as typing_extensions
|
||||
else:
|
||||
import typing_extensions
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
|
||||
|
||||
class _MessageType:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _MessageTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_MessageType.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
ACK: _MessageType.ValueType # 0
|
||||
COLLECT_INTERVAL: _MessageType.ValueType # 160
|
||||
"""in ms"""
|
||||
BEEP_ON: _MessageType.ValueType # 161
|
||||
"""duration ms"""
|
||||
BEEP_OFF: _MessageType.ValueType # 162
|
||||
"""cancel prematurely"""
|
||||
SHUTDOWN: _MessageType.ValueType # 163
|
||||
POWER_ON: _MessageType.ValueType # 164
|
||||
SCD41_TEMP: _MessageType.ValueType # 176
|
||||
SCD41_HUMIDITY: _MessageType.ValueType # 177
|
||||
SCD41_CO2: _MessageType.ValueType # 178
|
||||
AHT20_TEMP: _MessageType.ValueType # 179
|
||||
AHT20_HUMIDITY: _MessageType.ValueType # 180
|
||||
TVOC_INDEX: _MessageType.ValueType # 181
|
||||
|
||||
class MessageType(_MessageType, metaclass=_MessageTypeEnumTypeWrapper):
|
||||
"""encapsulate up to 1k of NMEA string data"""
|
||||
|
||||
ACK: MessageType.ValueType # 0
|
||||
COLLECT_INTERVAL: MessageType.ValueType # 160
|
||||
"""in ms"""
|
||||
BEEP_ON: MessageType.ValueType # 161
|
||||
"""duration ms"""
|
||||
BEEP_OFF: MessageType.ValueType # 162
|
||||
"""cancel prematurely"""
|
||||
SHUTDOWN: MessageType.ValueType # 163
|
||||
POWER_ON: MessageType.ValueType # 164
|
||||
SCD41_TEMP: MessageType.ValueType # 176
|
||||
SCD41_HUMIDITY: MessageType.ValueType # 177
|
||||
SCD41_CO2: MessageType.ValueType # 178
|
||||
AHT20_TEMP: MessageType.ValueType # 179
|
||||
AHT20_HUMIDITY: MessageType.ValueType # 180
|
||||
TVOC_INDEX: MessageType.ValueType # 181
|
||||
global___MessageType = MessageType
|
||||
|
||||
@typing.final
|
||||
class SensorData(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
TYPE_FIELD_NUMBER: builtins.int
|
||||
FLOAT_VALUE_FIELD_NUMBER: builtins.int
|
||||
UINT32_VALUE_FIELD_NUMBER: builtins.int
|
||||
type: global___MessageType.ValueType
|
||||
"""The message type"""
|
||||
float_value: builtins.float
|
||||
uint32_value: builtins.int
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
type: global___MessageType.ValueType = ...,
|
||||
float_value: builtins.float = ...,
|
||||
uint32_value: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["data", b"data", "float_value", b"float_value", "uint32_value", b"uint32_value"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["data", b"data", "float_value", b"float_value", "type", b"type", "uint32_value", b"uint32_value"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["data", b"data"]) -> typing.Literal["float_value", "uint32_value"] | None: ...
|
||||
|
||||
global___SensorData = SensorData
|
||||
|
||||
@typing.final
|
||||
class InterdeviceMessage(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
NMEA_FIELD_NUMBER: builtins.int
|
||||
SENSOR_FIELD_NUMBER: builtins.int
|
||||
nmea: builtins.str
|
||||
@property
|
||||
def sensor(self) -> global___SensorData: ...
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
nmea: builtins.str = ...,
|
||||
sensor: global___SensorData | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["data", b"data", "nmea", b"nmea", "sensor", b"sensor"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["data", b"data", "nmea", b"nmea", "sensor", b"sensor"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["data", b"data"]) -> typing.Literal["nmea", "sensor"] | None: ...
|
||||
|
||||
global___InterdeviceMessage = InterdeviceMessage
|
||||
10
meshtastic/protobuf/localonly_pb2.py
generated
10
meshtastic/protobuf/localonly_pb2.py
generated
@@ -15,16 +15,16 @@ from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config
|
||||
from meshtastic.protobuf import module_config_pb2 as meshtastic_dot_protobuf_dot_module__config__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/localonly.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/config.proto\x1a\'meshtastic/protobuf/module_config.proto\"\xbc\x03\n\x0bLocalConfig\x12\x38\n\x06\x64\x65vice\x18\x01 \x01(\x0b\x32(.meshtastic.protobuf.Config.DeviceConfig\x12<\n\x08position\x18\x02 \x01(\x0b\x32*.meshtastic.protobuf.Config.PositionConfig\x12\x36\n\x05power\x18\x03 \x01(\x0b\x32\'.meshtastic.protobuf.Config.PowerConfig\x12:\n\x07network\x18\x04 \x01(\x0b\x32).meshtastic.protobuf.Config.NetworkConfig\x12:\n\x07\x64isplay\x18\x05 \x01(\x0b\x32).meshtastic.protobuf.Config.DisplayConfig\x12\x34\n\x04lora\x18\x06 \x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfig\x12>\n\tbluetooth\x18\x07 \x01(\x0b\x32+.meshtastic.protobuf.Config.BluetoothConfig\x12\x0f\n\x07version\x18\x08 \x01(\r\"\xf0\x07\n\x11LocalModuleConfig\x12:\n\x04mqtt\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.ModuleConfig.MQTTConfig\x12>\n\x06serial\x18\x02 \x01(\x0b\x32..meshtastic.protobuf.ModuleConfig.SerialConfig\x12[\n\x15\x65xternal_notification\x18\x03 \x01(\x0b\x32<.meshtastic.protobuf.ModuleConfig.ExternalNotificationConfig\x12K\n\rstore_forward\x18\x04 \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.StoreForwardConfig\x12\x45\n\nrange_test\x18\x05 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.RangeTestConfig\x12\x44\n\ttelemetry\x18\x06 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.TelemetryConfig\x12M\n\x0e\x63\x61nned_message\x18\x07 \x01(\x0b\x32\x35.meshtastic.protobuf.ModuleConfig.CannedMessageConfig\x12<\n\x05\x61udio\x18\t \x01(\x0b\x32-.meshtastic.protobuf.ModuleConfig.AudioConfig\x12O\n\x0fremote_hardware\x18\n \x01(\x0b\x32\x36.meshtastic.protobuf.ModuleConfig.RemoteHardwareConfig\x12K\n\rneighbor_info\x18\x0b \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.NeighborInfoConfig\x12Q\n\x10\x61mbient_lighting\x18\x0c \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.AmbientLightingConfig\x12Q\n\x10\x64\x65tection_sensor\x18\r \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.DetectionSensorConfig\x12\x46\n\npaxcounter\x18\x0e \x01(\x0b\x32\x32.meshtastic.protobuf.ModuleConfig.PaxcounterConfig\x12\x0f\n\x07version\x18\x08 \x01(\rBd\n\x13\x63om.geeksville.meshB\x0fLocalOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/localonly.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/config.proto\x1a\'meshtastic/protobuf/module_config.proto\"\xfa\x03\n\x0bLocalConfig\x12\x38\n\x06\x64\x65vice\x18\x01 \x01(\x0b\x32(.meshtastic.protobuf.Config.DeviceConfig\x12<\n\x08position\x18\x02 \x01(\x0b\x32*.meshtastic.protobuf.Config.PositionConfig\x12\x36\n\x05power\x18\x03 \x01(\x0b\x32\'.meshtastic.protobuf.Config.PowerConfig\x12:\n\x07network\x18\x04 \x01(\x0b\x32).meshtastic.protobuf.Config.NetworkConfig\x12:\n\x07\x64isplay\x18\x05 \x01(\x0b\x32).meshtastic.protobuf.Config.DisplayConfig\x12\x34\n\x04lora\x18\x06 \x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfig\x12>\n\tbluetooth\x18\x07 \x01(\x0b\x32+.meshtastic.protobuf.Config.BluetoothConfig\x12\x0f\n\x07version\x18\x08 \x01(\r\x12<\n\x08security\x18\t \x01(\x0b\x32*.meshtastic.protobuf.Config.SecurityConfig\"\xcf\t\n\x11LocalModuleConfig\x12:\n\x04mqtt\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.ModuleConfig.MQTTConfig\x12>\n\x06serial\x18\x02 \x01(\x0b\x32..meshtastic.protobuf.ModuleConfig.SerialConfig\x12[\n\x15\x65xternal_notification\x18\x03 \x01(\x0b\x32<.meshtastic.protobuf.ModuleConfig.ExternalNotificationConfig\x12K\n\rstore_forward\x18\x04 \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.StoreForwardConfig\x12\x45\n\nrange_test\x18\x05 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.RangeTestConfig\x12\x44\n\ttelemetry\x18\x06 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.TelemetryConfig\x12M\n\x0e\x63\x61nned_message\x18\x07 \x01(\x0b\x32\x35.meshtastic.protobuf.ModuleConfig.CannedMessageConfig\x12<\n\x05\x61udio\x18\t \x01(\x0b\x32-.meshtastic.protobuf.ModuleConfig.AudioConfig\x12O\n\x0fremote_hardware\x18\n \x01(\x0b\x32\x36.meshtastic.protobuf.ModuleConfig.RemoteHardwareConfig\x12K\n\rneighbor_info\x18\x0b \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.NeighborInfoConfig\x12Q\n\x10\x61mbient_lighting\x18\x0c \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.AmbientLightingConfig\x12Q\n\x10\x64\x65tection_sensor\x18\r \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.DetectionSensorConfig\x12\x46\n\npaxcounter\x18\x0e \x01(\x0b\x32\x32.meshtastic.protobuf.ModuleConfig.PaxcounterConfig\x12L\n\rstatusmessage\x18\x0f \x01(\x0b\x32\x35.meshtastic.protobuf.ModuleConfig.StatusMessageConfig\x12U\n\x12traffic_management\x18\x10 \x01(\x0b\x32\x39.meshtastic.protobuf.ModuleConfig.TrafficManagementConfig\x12\x38\n\x03tak\x18\x11 \x01(\x0b\x32+.meshtastic.protobuf.ModuleConfig.TAKConfig\x12\x0f\n\x07version\x18\x08 \x01(\rBe\n\x14org.meshtastic.protoB\x0fLocalOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.localonly_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\017LocalOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\017LocalOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_globals['_LOCALCONFIG']._serialized_start=136
|
||||
_globals['_LOCALCONFIG']._serialized_end=580
|
||||
_globals['_LOCALMODULECONFIG']._serialized_start=583
|
||||
_globals['_LOCALMODULECONFIG']._serialized_end=1591
|
||||
_globals['_LOCALCONFIG']._serialized_end=642
|
||||
_globals['_LOCALMODULECONFIG']._serialized_start=645
|
||||
_globals['_LOCALMODULECONFIG']._serialized_end=1876
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
40
meshtastic/protobuf/localonly_pb2.pyi
generated
40
meshtastic/protobuf/localonly_pb2.pyi
generated
@@ -29,6 +29,7 @@ class LocalConfig(google.protobuf.message.Message):
|
||||
LORA_FIELD_NUMBER: builtins.int
|
||||
BLUETOOTH_FIELD_NUMBER: builtins.int
|
||||
VERSION_FIELD_NUMBER: builtins.int
|
||||
SECURITY_FIELD_NUMBER: builtins.int
|
||||
version: builtins.int
|
||||
"""
|
||||
A version integer used to invalidate old save files when we make
|
||||
@@ -77,6 +78,12 @@ class LocalConfig(google.protobuf.message.Message):
|
||||
The part of the config that is specific to the Bluetooth settings
|
||||
"""
|
||||
|
||||
@property
|
||||
def security(self) -> meshtastic.protobuf.config_pb2.Config.SecurityConfig:
|
||||
"""
|
||||
The part of the config that is specific to Security settings
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -88,9 +95,10 @@ class LocalConfig(google.protobuf.message.Message):
|
||||
lora: meshtastic.protobuf.config_pb2.Config.LoRaConfig | None = ...,
|
||||
bluetooth: meshtastic.protobuf.config_pb2.Config.BluetoothConfig | None = ...,
|
||||
version: builtins.int = ...,
|
||||
security: meshtastic.protobuf.config_pb2.Config.SecurityConfig | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "display", b"display", "lora", b"lora", "network", b"network", "position", b"position", "power", b"power"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "display", b"display", "lora", b"lora", "network", b"network", "position", b"position", "power", b"power", "version", b"version"]) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "display", b"display", "lora", b"lora", "network", b"network", "position", b"position", "power", b"power", "security", b"security"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "display", b"display", "lora", b"lora", "network", b"network", "position", b"position", "power", b"power", "security", b"security", "version", b"version"]) -> None: ...
|
||||
|
||||
global___LocalConfig = LocalConfig
|
||||
|
||||
@@ -111,6 +119,9 @@ class LocalModuleConfig(google.protobuf.message.Message):
|
||||
AMBIENT_LIGHTING_FIELD_NUMBER: builtins.int
|
||||
DETECTION_SENSOR_FIELD_NUMBER: builtins.int
|
||||
PAXCOUNTER_FIELD_NUMBER: builtins.int
|
||||
STATUSMESSAGE_FIELD_NUMBER: builtins.int
|
||||
TRAFFIC_MANAGEMENT_FIELD_NUMBER: builtins.int
|
||||
TAK_FIELD_NUMBER: builtins.int
|
||||
VERSION_FIELD_NUMBER: builtins.int
|
||||
version: builtins.int
|
||||
"""
|
||||
@@ -196,6 +207,24 @@ class LocalModuleConfig(google.protobuf.message.Message):
|
||||
Paxcounter Config
|
||||
"""
|
||||
|
||||
@property
|
||||
def statusmessage(self) -> meshtastic.protobuf.module_config_pb2.ModuleConfig.StatusMessageConfig:
|
||||
"""
|
||||
StatusMessage Config
|
||||
"""
|
||||
|
||||
@property
|
||||
def traffic_management(self) -> meshtastic.protobuf.module_config_pb2.ModuleConfig.TrafficManagementConfig:
|
||||
"""
|
||||
The part of the config that is specific to the Traffic Management module
|
||||
"""
|
||||
|
||||
@property
|
||||
def tak(self) -> meshtastic.protobuf.module_config_pb2.ModuleConfig.TAKConfig:
|
||||
"""
|
||||
TAK Config
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -212,9 +241,12 @@ class LocalModuleConfig(google.protobuf.message.Message):
|
||||
ambient_lighting: meshtastic.protobuf.module_config_pb2.ModuleConfig.AmbientLightingConfig | None = ...,
|
||||
detection_sensor: meshtastic.protobuf.module_config_pb2.ModuleConfig.DetectionSensorConfig | None = ...,
|
||||
paxcounter: meshtastic.protobuf.module_config_pb2.ModuleConfig.PaxcounterConfig | None = ...,
|
||||
statusmessage: meshtastic.protobuf.module_config_pb2.ModuleConfig.StatusMessageConfig | None = ...,
|
||||
traffic_management: meshtastic.protobuf.module_config_pb2.ModuleConfig.TrafficManagementConfig | None = ...,
|
||||
tak: meshtastic.protobuf.module_config_pb2.ModuleConfig.TAKConfig | None = ...,
|
||||
version: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["ambient_lighting", b"ambient_lighting", "audio", b"audio", "canned_message", b"canned_message", "detection_sensor", b"detection_sensor", "external_notification", b"external_notification", "mqtt", b"mqtt", "neighbor_info", b"neighbor_info", "paxcounter", b"paxcounter", "range_test", b"range_test", "remote_hardware", b"remote_hardware", "serial", b"serial", "store_forward", b"store_forward", "telemetry", b"telemetry"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["ambient_lighting", b"ambient_lighting", "audio", b"audio", "canned_message", b"canned_message", "detection_sensor", b"detection_sensor", "external_notification", b"external_notification", "mqtt", b"mqtt", "neighbor_info", b"neighbor_info", "paxcounter", b"paxcounter", "range_test", b"range_test", "remote_hardware", b"remote_hardware", "serial", b"serial", "store_forward", b"store_forward", "telemetry", b"telemetry", "version", b"version"]) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["ambient_lighting", b"ambient_lighting", "audio", b"audio", "canned_message", b"canned_message", "detection_sensor", b"detection_sensor", "external_notification", b"external_notification", "mqtt", b"mqtt", "neighbor_info", b"neighbor_info", "paxcounter", b"paxcounter", "range_test", b"range_test", "remote_hardware", b"remote_hardware", "serial", b"serial", "statusmessage", b"statusmessage", "store_forward", b"store_forward", "tak", b"tak", "telemetry", b"telemetry", "traffic_management", b"traffic_management"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["ambient_lighting", b"ambient_lighting", "audio", b"audio", "canned_message", b"canned_message", "detection_sensor", b"detection_sensor", "external_notification", b"external_notification", "mqtt", b"mqtt", "neighbor_info", b"neighbor_info", "paxcounter", b"paxcounter", "range_test", b"range_test", "remote_hardware", b"remote_hardware", "serial", b"serial", "statusmessage", b"statusmessage", "store_forward", b"store_forward", "tak", b"tak", "telemetry", b"telemetry", "traffic_management", b"traffic_management", "version", b"version"]) -> None: ...
|
||||
|
||||
global___LocalModuleConfig = LocalModuleConfig
|
||||
|
||||
288
meshtastic/protobuf/mesh_pb2.py
generated
288
meshtastic/protobuf/mesh_pb2.py
generated
File diff suppressed because one or more lines are too long
1766
meshtastic/protobuf/mesh_pb2.pyi
generated
1766
meshtastic/protobuf/mesh_pb2.pyi
generated
File diff suppressed because it is too large
Load Diff
150
meshtastic/protobuf/module_config_pb2.py
generated
150
meshtastic/protobuf/module_config_pb2.py
generated
File diff suppressed because one or more lines are too long
343
meshtastic/protobuf/module_config_pb2.pyi
generated
343
meshtastic/protobuf/module_config_pb2.pyi
generated
@@ -9,6 +9,7 @@ import google.protobuf.descriptor
|
||||
import google.protobuf.internal.containers
|
||||
import google.protobuf.internal.enum_type_wrapper
|
||||
import google.protobuf.message
|
||||
import meshtastic.protobuf.atak_pb2
|
||||
import sys
|
||||
import typing
|
||||
|
||||
@@ -112,7 +113,7 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
json_enabled: builtins.bool
|
||||
"""
|
||||
Whether to send / consume json packets on MQTT
|
||||
Deprecated: JSON packet support on MQTT was removed, and this field is ignored.
|
||||
"""
|
||||
tls_enabled: builtins.bool
|
||||
"""
|
||||
@@ -165,6 +166,7 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
|
||||
PUBLISH_INTERVAL_SECS_FIELD_NUMBER: builtins.int
|
||||
POSITION_PRECISION_FIELD_NUMBER: builtins.int
|
||||
SHOULD_REPORT_LOCATION_FIELD_NUMBER: builtins.int
|
||||
publish_interval_secs: builtins.int
|
||||
"""
|
||||
How often we should report our info to the map (in seconds)
|
||||
@@ -173,13 +175,18 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
Bits of precision for the location sent (default of 32 is full precision).
|
||||
"""
|
||||
should_report_location: builtins.bool
|
||||
"""
|
||||
Whether we have opted-in to report our location to the map
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
publish_interval_secs: builtins.int = ...,
|
||||
position_precision: builtins.int = ...,
|
||||
should_report_location: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["position_precision", b"position_precision", "publish_interval_secs", b"publish_interval_secs"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["position_precision", b"position_precision", "publish_interval_secs", b"publish_interval_secs", "should_report_location", b"should_report_location"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class RemoteHardwareConfig(google.protobuf.message.Message):
|
||||
@@ -225,6 +232,7 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
|
||||
ENABLED_FIELD_NUMBER: builtins.int
|
||||
UPDATE_INTERVAL_FIELD_NUMBER: builtins.int
|
||||
TRANSMIT_OVER_LORA_FIELD_NUMBER: builtins.int
|
||||
enabled: builtins.bool
|
||||
"""
|
||||
Whether the Module is enabled
|
||||
@@ -232,15 +240,21 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
update_interval: builtins.int
|
||||
"""
|
||||
Interval in seconds of how often we should try to send our
|
||||
Neighbor Info to the mesh
|
||||
Neighbor Info (minimum is 14400, i.e., 4 hours)
|
||||
"""
|
||||
transmit_over_lora: builtins.bool
|
||||
"""
|
||||
Whether in addition to sending it to MQTT and the PhoneAPI, our NeighborInfo should be transmitted over LoRa.
|
||||
Note that this is not available on a channel with default key and name.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
enabled: builtins.bool = ...,
|
||||
update_interval: builtins.int = ...,
|
||||
transmit_over_lora: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["enabled", b"enabled", "update_interval", b"update_interval"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["enabled", b"enabled", "transmit_over_lora", b"transmit_over_lora", "update_interval", b"update_interval"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class DetectionSensorConfig(google.protobuf.message.Message):
|
||||
@@ -250,13 +264,54 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
class _TriggerType:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _TriggerTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[ModuleConfig.DetectionSensorConfig._TriggerType.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
LOGIC_LOW: ModuleConfig.DetectionSensorConfig._TriggerType.ValueType # 0
|
||||
"""Event is triggered if pin is low"""
|
||||
LOGIC_HIGH: ModuleConfig.DetectionSensorConfig._TriggerType.ValueType # 1
|
||||
"""Event is triggered if pin is high"""
|
||||
FALLING_EDGE: ModuleConfig.DetectionSensorConfig._TriggerType.ValueType # 2
|
||||
"""Event is triggered when pin goes high to low"""
|
||||
RISING_EDGE: ModuleConfig.DetectionSensorConfig._TriggerType.ValueType # 3
|
||||
"""Event is triggered when pin goes low to high"""
|
||||
EITHER_EDGE_ACTIVE_LOW: ModuleConfig.DetectionSensorConfig._TriggerType.ValueType # 4
|
||||
"""Event is triggered on every pin state change, low is considered to be
|
||||
"active"
|
||||
"""
|
||||
EITHER_EDGE_ACTIVE_HIGH: ModuleConfig.DetectionSensorConfig._TriggerType.ValueType # 5
|
||||
"""Event is triggered on every pin state change, high is considered to be
|
||||
"active"
|
||||
"""
|
||||
|
||||
class TriggerType(_TriggerType, metaclass=_TriggerTypeEnumTypeWrapper): ...
|
||||
LOGIC_LOW: ModuleConfig.DetectionSensorConfig.TriggerType.ValueType # 0
|
||||
"""Event is triggered if pin is low"""
|
||||
LOGIC_HIGH: ModuleConfig.DetectionSensorConfig.TriggerType.ValueType # 1
|
||||
"""Event is triggered if pin is high"""
|
||||
FALLING_EDGE: ModuleConfig.DetectionSensorConfig.TriggerType.ValueType # 2
|
||||
"""Event is triggered when pin goes high to low"""
|
||||
RISING_EDGE: ModuleConfig.DetectionSensorConfig.TriggerType.ValueType # 3
|
||||
"""Event is triggered when pin goes low to high"""
|
||||
EITHER_EDGE_ACTIVE_LOW: ModuleConfig.DetectionSensorConfig.TriggerType.ValueType # 4
|
||||
"""Event is triggered on every pin state change, low is considered to be
|
||||
"active"
|
||||
"""
|
||||
EITHER_EDGE_ACTIVE_HIGH: ModuleConfig.DetectionSensorConfig.TriggerType.ValueType # 5
|
||||
"""Event is triggered on every pin state change, high is considered to be
|
||||
"active"
|
||||
"""
|
||||
|
||||
ENABLED_FIELD_NUMBER: builtins.int
|
||||
MINIMUM_BROADCAST_SECS_FIELD_NUMBER: builtins.int
|
||||
STATE_BROADCAST_SECS_FIELD_NUMBER: builtins.int
|
||||
SEND_BELL_FIELD_NUMBER: builtins.int
|
||||
NAME_FIELD_NUMBER: builtins.int
|
||||
MONITOR_PIN_FIELD_NUMBER: builtins.int
|
||||
DETECTION_TRIGGERED_HIGH_FIELD_NUMBER: builtins.int
|
||||
DETECTION_TRIGGER_TYPE_FIELD_NUMBER: builtins.int
|
||||
USE_PULLUP_FIELD_NUMBER: builtins.int
|
||||
enabled: builtins.bool
|
||||
"""
|
||||
@@ -264,13 +319,15 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
minimum_broadcast_secs: builtins.int
|
||||
"""
|
||||
Interval in seconds of how often we can send a message to the mesh when a state change is detected
|
||||
Interval in seconds of how often we can send a message to the mesh when a
|
||||
trigger event is detected
|
||||
"""
|
||||
state_broadcast_secs: builtins.int
|
||||
"""
|
||||
Interval in seconds of how often we should send a message to the mesh with the current state regardless of changes
|
||||
When set to 0, only state changes will be broadcasted
|
||||
Works as a sort of status heartbeat for peace of mind
|
||||
Interval in seconds of how often we should send a message to the mesh
|
||||
with the current state regardless of trigger events When set to 0, only
|
||||
trigger events will be broadcasted Works as a sort of status heartbeat
|
||||
for peace of mind
|
||||
"""
|
||||
send_bell: builtins.bool
|
||||
"""
|
||||
@@ -287,10 +344,9 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
GPIO pin to monitor for state changes
|
||||
"""
|
||||
detection_triggered_high: builtins.bool
|
||||
detection_trigger_type: global___ModuleConfig.DetectionSensorConfig.TriggerType.ValueType
|
||||
"""
|
||||
Whether or not the GPIO pin state detection is triggered on HIGH (1)
|
||||
Otherwise LOW (0)
|
||||
The type of trigger event to be used
|
||||
"""
|
||||
use_pullup: builtins.bool
|
||||
"""
|
||||
@@ -306,10 +362,10 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
send_bell: builtins.bool = ...,
|
||||
name: builtins.str = ...,
|
||||
monitor_pin: builtins.int = ...,
|
||||
detection_triggered_high: builtins.bool = ...,
|
||||
detection_trigger_type: global___ModuleConfig.DetectionSensorConfig.TriggerType.ValueType = ...,
|
||||
use_pullup: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["detection_triggered_high", b"detection_triggered_high", "enabled", b"enabled", "minimum_broadcast_secs", b"minimum_broadcast_secs", "monitor_pin", b"monitor_pin", "name", b"name", "send_bell", b"send_bell", "state_broadcast_secs", b"state_broadcast_secs", "use_pullup", b"use_pullup"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["detection_trigger_type", b"detection_trigger_type", "enabled", b"enabled", "minimum_broadcast_secs", b"minimum_broadcast_secs", "monitor_pin", b"monitor_pin", "name", b"name", "send_bell", b"send_bell", "state_broadcast_secs", b"state_broadcast_secs", "use_pullup", b"use_pullup"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class AudioConfig(google.protobuf.message.Message):
|
||||
@@ -437,6 +493,105 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["ble_threshold", b"ble_threshold", "enabled", b"enabled", "paxcounter_update_interval", b"paxcounter_update_interval", "wifi_threshold", b"wifi_threshold"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class TrafficManagementConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
Config for the Traffic Management module.
|
||||
Provides packet inspection and traffic shaping to help reduce channel utilization
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
ENABLED_FIELD_NUMBER: builtins.int
|
||||
POSITION_DEDUP_ENABLED_FIELD_NUMBER: builtins.int
|
||||
POSITION_PRECISION_BITS_FIELD_NUMBER: builtins.int
|
||||
POSITION_MIN_INTERVAL_SECS_FIELD_NUMBER: builtins.int
|
||||
NODEINFO_DIRECT_RESPONSE_FIELD_NUMBER: builtins.int
|
||||
NODEINFO_DIRECT_RESPONSE_MAX_HOPS_FIELD_NUMBER: builtins.int
|
||||
RATE_LIMIT_ENABLED_FIELD_NUMBER: builtins.int
|
||||
RATE_LIMIT_WINDOW_SECS_FIELD_NUMBER: builtins.int
|
||||
RATE_LIMIT_MAX_PACKETS_FIELD_NUMBER: builtins.int
|
||||
DROP_UNKNOWN_ENABLED_FIELD_NUMBER: builtins.int
|
||||
UNKNOWN_PACKET_THRESHOLD_FIELD_NUMBER: builtins.int
|
||||
EXHAUST_HOP_TELEMETRY_FIELD_NUMBER: builtins.int
|
||||
EXHAUST_HOP_POSITION_FIELD_NUMBER: builtins.int
|
||||
ROUTER_PRESERVE_HOPS_FIELD_NUMBER: builtins.int
|
||||
enabled: builtins.bool
|
||||
"""
|
||||
Master enable for traffic management module
|
||||
"""
|
||||
position_dedup_enabled: builtins.bool
|
||||
"""
|
||||
Enable position deduplication to drop redundant position broadcasts
|
||||
"""
|
||||
position_precision_bits: builtins.int
|
||||
"""
|
||||
Number of bits of precision for position deduplication (0-32)
|
||||
"""
|
||||
position_min_interval_secs: builtins.int
|
||||
"""
|
||||
Minimum interval in seconds between position updates from the same node
|
||||
"""
|
||||
nodeinfo_direct_response: builtins.bool
|
||||
"""
|
||||
Enable direct response to NodeInfo requests from local cache
|
||||
"""
|
||||
nodeinfo_direct_response_max_hops: builtins.int
|
||||
"""
|
||||
Minimum hop distance from requestor before responding to NodeInfo requests
|
||||
"""
|
||||
rate_limit_enabled: builtins.bool
|
||||
"""
|
||||
Enable per-node rate limiting to throttle chatty nodes
|
||||
"""
|
||||
rate_limit_window_secs: builtins.int
|
||||
"""
|
||||
Time window in seconds for rate limiting calculations
|
||||
"""
|
||||
rate_limit_max_packets: builtins.int
|
||||
"""
|
||||
Maximum packets allowed per node within the rate limit window
|
||||
"""
|
||||
drop_unknown_enabled: builtins.bool
|
||||
"""
|
||||
Enable dropping of unknown/undecryptable packets per rate_limit_window_secs
|
||||
"""
|
||||
unknown_packet_threshold: builtins.int
|
||||
"""
|
||||
Number of unknown packets before dropping from a node
|
||||
"""
|
||||
exhaust_hop_telemetry: builtins.bool
|
||||
"""
|
||||
Set hop_limit to 0 for relayed telemetry broadcasts (own packets unaffected)
|
||||
"""
|
||||
exhaust_hop_position: builtins.bool
|
||||
"""
|
||||
Set hop_limit to 0 for relayed position broadcasts (own packets unaffected)
|
||||
"""
|
||||
router_preserve_hops: builtins.bool
|
||||
"""
|
||||
Preserve hop_limit for router-to-router traffic
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
enabled: builtins.bool = ...,
|
||||
position_dedup_enabled: builtins.bool = ...,
|
||||
position_precision_bits: builtins.int = ...,
|
||||
position_min_interval_secs: builtins.int = ...,
|
||||
nodeinfo_direct_response: builtins.bool = ...,
|
||||
nodeinfo_direct_response_max_hops: builtins.int = ...,
|
||||
rate_limit_enabled: builtins.bool = ...,
|
||||
rate_limit_window_secs: builtins.int = ...,
|
||||
rate_limit_max_packets: builtins.int = ...,
|
||||
drop_unknown_enabled: builtins.bool = ...,
|
||||
unknown_packet_threshold: builtins.int = ...,
|
||||
exhaust_hop_telemetry: builtins.bool = ...,
|
||||
exhaust_hop_position: builtins.bool = ...,
|
||||
router_preserve_hops: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["drop_unknown_enabled", b"drop_unknown_enabled", "enabled", b"enabled", "exhaust_hop_position", b"exhaust_hop_position", "exhaust_hop_telemetry", b"exhaust_hop_telemetry", "nodeinfo_direct_response", b"nodeinfo_direct_response", "nodeinfo_direct_response_max_hops", b"nodeinfo_direct_response_max_hops", "position_dedup_enabled", b"position_dedup_enabled", "position_min_interval_secs", b"position_min_interval_secs", "position_precision_bits", b"position_precision_bits", "rate_limit_enabled", b"rate_limit_enabled", "rate_limit_max_packets", b"rate_limit_max_packets", "rate_limit_window_secs", b"rate_limit_window_secs", "router_preserve_hops", b"router_preserve_hops", "unknown_packet_threshold", b"unknown_packet_threshold"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class SerialConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
@@ -505,6 +660,20 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
"""NMEA messages specifically tailored for CalTopo"""
|
||||
WS85: ModuleConfig.SerialConfig._Serial_Mode.ValueType # 6
|
||||
"""Ecowitt WS85 weather station"""
|
||||
VE_DIRECT: ModuleConfig.SerialConfig._Serial_Mode.ValueType # 7
|
||||
"""VE.Direct is a serial protocol used by Victron Energy products
|
||||
https://beta.ivc.no/wiki/index.php/Victron_VE_Direct_DIY_Cable
|
||||
"""
|
||||
MS_CONFIG: ModuleConfig.SerialConfig._Serial_Mode.ValueType # 8
|
||||
"""Used to configure and view some parameters of MeshSolar.
|
||||
https://heltec.org/project/meshsolar/
|
||||
"""
|
||||
LOG: ModuleConfig.SerialConfig._Serial_Mode.ValueType # 9
|
||||
"""Logs mesh traffic to the serial pins, ideal for logging via openLog or similar.
|
||||
includes other packets
|
||||
"""
|
||||
LOGTEXT: ModuleConfig.SerialConfig._Serial_Mode.ValueType # 10
|
||||
"""only text (channel & DM)"""
|
||||
|
||||
class Serial_Mode(_Serial_Mode, metaclass=_Serial_ModeEnumTypeWrapper):
|
||||
"""
|
||||
@@ -520,6 +689,20 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
"""NMEA messages specifically tailored for CalTopo"""
|
||||
WS85: ModuleConfig.SerialConfig.Serial_Mode.ValueType # 6
|
||||
"""Ecowitt WS85 weather station"""
|
||||
VE_DIRECT: ModuleConfig.SerialConfig.Serial_Mode.ValueType # 7
|
||||
"""VE.Direct is a serial protocol used by Victron Energy products
|
||||
https://beta.ivc.no/wiki/index.php/Victron_VE_Direct_DIY_Cable
|
||||
"""
|
||||
MS_CONFIG: ModuleConfig.SerialConfig.Serial_Mode.ValueType # 8
|
||||
"""Used to configure and view some parameters of MeshSolar.
|
||||
https://heltec.org/project/meshsolar/
|
||||
"""
|
||||
LOG: ModuleConfig.SerialConfig.Serial_Mode.ValueType # 9
|
||||
"""Logs mesh traffic to the serial pins, ideal for logging via openLog or similar.
|
||||
includes other packets
|
||||
"""
|
||||
LOGTEXT: ModuleConfig.SerialConfig.Serial_Mode.ValueType # 10
|
||||
"""only text (channel & DM)"""
|
||||
|
||||
ENABLED_FIELD_NUMBER: builtins.int
|
||||
ECHO_FIELD_NUMBER: builtins.int
|
||||
@@ -753,6 +936,7 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
ENABLED_FIELD_NUMBER: builtins.int
|
||||
SENDER_FIELD_NUMBER: builtins.int
|
||||
SAVE_FIELD_NUMBER: builtins.int
|
||||
CLEAR_ON_REBOOT_FIELD_NUMBER: builtins.int
|
||||
enabled: builtins.bool
|
||||
"""
|
||||
Enable the Range Test Module
|
||||
@@ -766,14 +950,20 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
Bool value indicating that this node should save a RangeTest.csv file.
|
||||
ESP32 Only
|
||||
"""
|
||||
clear_on_reboot: builtins.bool
|
||||
"""
|
||||
Bool indicating that the node should cleanup / destroy it's RangeTest.csv file.
|
||||
ESP32 Only
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
enabled: builtins.bool = ...,
|
||||
sender: builtins.int = ...,
|
||||
save: builtins.bool = ...,
|
||||
clear_on_reboot: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["enabled", b"enabled", "save", b"save", "sender", b"sender"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["clear_on_reboot", b"clear_on_reboot", "enabled", b"enabled", "save", b"save", "sender", b"sender"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class TelemetryConfig(google.protobuf.message.Message):
|
||||
@@ -793,6 +983,11 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
POWER_MEASUREMENT_ENABLED_FIELD_NUMBER: builtins.int
|
||||
POWER_UPDATE_INTERVAL_FIELD_NUMBER: builtins.int
|
||||
POWER_SCREEN_ENABLED_FIELD_NUMBER: builtins.int
|
||||
HEALTH_MEASUREMENT_ENABLED_FIELD_NUMBER: builtins.int
|
||||
HEALTH_UPDATE_INTERVAL_FIELD_NUMBER: builtins.int
|
||||
HEALTH_SCREEN_ENABLED_FIELD_NUMBER: builtins.int
|
||||
DEVICE_TELEMETRY_ENABLED_FIELD_NUMBER: builtins.int
|
||||
AIR_QUALITY_SCREEN_ENABLED_FIELD_NUMBER: builtins.int
|
||||
device_update_interval: builtins.int
|
||||
"""
|
||||
Interval in seconds of how often we should try to send our
|
||||
@@ -828,18 +1023,39 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
power_measurement_enabled: builtins.bool
|
||||
"""
|
||||
Interval in seconds of how often we should try to send our
|
||||
air quality metrics to the mesh
|
||||
Enable/disable Power metrics
|
||||
"""
|
||||
power_update_interval: builtins.int
|
||||
"""
|
||||
Interval in seconds of how often we should try to send our
|
||||
air quality metrics to the mesh
|
||||
power metrics to the mesh
|
||||
"""
|
||||
power_screen_enabled: builtins.bool
|
||||
"""
|
||||
Enable/Disable the power measurement module on-device display
|
||||
"""
|
||||
health_measurement_enabled: builtins.bool
|
||||
"""
|
||||
Preferences for the (Health) Telemetry Module
|
||||
Enable/Disable the telemetry measurement module measurement collection
|
||||
"""
|
||||
health_update_interval: builtins.int
|
||||
"""
|
||||
Interval in seconds of how often we should try to send our
|
||||
air quality metrics to the mesh
|
||||
health metrics to the mesh
|
||||
"""
|
||||
health_screen_enabled: builtins.bool
|
||||
"""
|
||||
Enable/Disable the health telemetry module on-device display
|
||||
"""
|
||||
device_telemetry_enabled: builtins.bool
|
||||
"""
|
||||
Enable/Disable the device telemetry module to send metrics to the mesh
|
||||
Note: We will still send telemtry to the connected phone / client every minute over the API
|
||||
"""
|
||||
air_quality_screen_enabled: builtins.bool
|
||||
"""
|
||||
Enable/Disable the air quality telemetry measurement module on-device display
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
@@ -854,13 +1070,18 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
power_measurement_enabled: builtins.bool = ...,
|
||||
power_update_interval: builtins.int = ...,
|
||||
power_screen_enabled: builtins.bool = ...,
|
||||
health_measurement_enabled: builtins.bool = ...,
|
||||
health_update_interval: builtins.int = ...,
|
||||
health_screen_enabled: builtins.bool = ...,
|
||||
device_telemetry_enabled: builtins.bool = ...,
|
||||
air_quality_screen_enabled: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["air_quality_enabled", b"air_quality_enabled", "air_quality_interval", b"air_quality_interval", "device_update_interval", b"device_update_interval", "environment_display_fahrenheit", b"environment_display_fahrenheit", "environment_measurement_enabled", b"environment_measurement_enabled", "environment_screen_enabled", b"environment_screen_enabled", "environment_update_interval", b"environment_update_interval", "power_measurement_enabled", b"power_measurement_enabled", "power_screen_enabled", b"power_screen_enabled", "power_update_interval", b"power_update_interval"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["air_quality_enabled", b"air_quality_enabled", "air_quality_interval", b"air_quality_interval", "air_quality_screen_enabled", b"air_quality_screen_enabled", "device_telemetry_enabled", b"device_telemetry_enabled", "device_update_interval", b"device_update_interval", "environment_display_fahrenheit", b"environment_display_fahrenheit", "environment_measurement_enabled", b"environment_measurement_enabled", "environment_screen_enabled", b"environment_screen_enabled", "environment_update_interval", b"environment_update_interval", "health_measurement_enabled", b"health_measurement_enabled", "health_screen_enabled", b"health_screen_enabled", "health_update_interval", b"health_update_interval", "power_measurement_enabled", b"power_measurement_enabled", "power_screen_enabled", b"power_screen_enabled", "power_update_interval", b"power_update_interval"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class CannedMessageConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
TODO: REPLACE
|
||||
Canned Messages Module Config
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
@@ -992,7 +1213,7 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
allow_input_source: builtins.str
|
||||
"""
|
||||
Input event origin accepted by the canned message module.
|
||||
Can be e.g. "rotEnc1", "upDownEnc1" or keyword "_any"
|
||||
Can be e.g. "rotEnc1", "upDownEnc1", "scanAndSelect", "cardkb", "serialkb", or keyword "_any"
|
||||
"""
|
||||
send_bell: builtins.bool
|
||||
"""
|
||||
@@ -1061,6 +1282,54 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["blue", b"blue", "current", b"current", "green", b"green", "led_state", b"led_state", "red", b"red"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class StatusMessageConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
StatusMessage config - Allows setting a status message for a node to periodically rebroadcast
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
NODE_STATUS_FIELD_NUMBER: builtins.int
|
||||
node_status: builtins.str
|
||||
"""
|
||||
The actual status string
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
node_status: builtins.str = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["node_status", b"node_status"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class TAKConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
TAK team/role configuration
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
TEAM_FIELD_NUMBER: builtins.int
|
||||
ROLE_FIELD_NUMBER: builtins.int
|
||||
team: meshtastic.protobuf.atak_pb2.Team.ValueType
|
||||
"""
|
||||
Team color.
|
||||
Default Unspecifed_Color -> firmware uses Cyan
|
||||
"""
|
||||
role: meshtastic.protobuf.atak_pb2.MemberRole.ValueType
|
||||
"""
|
||||
Member role.
|
||||
Default Unspecifed -> firmware uses TeamMember
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
team: meshtastic.protobuf.atak_pb2.Team.ValueType = ...,
|
||||
role: meshtastic.protobuf.atak_pb2.MemberRole.ValueType = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["role", b"role", "team", b"team"]) -> None: ...
|
||||
|
||||
MQTT_FIELD_NUMBER: builtins.int
|
||||
SERIAL_FIELD_NUMBER: builtins.int
|
||||
EXTERNAL_NOTIFICATION_FIELD_NUMBER: builtins.int
|
||||
@@ -1074,6 +1343,9 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
AMBIENT_LIGHTING_FIELD_NUMBER: builtins.int
|
||||
DETECTION_SENSOR_FIELD_NUMBER: builtins.int
|
||||
PAXCOUNTER_FIELD_NUMBER: builtins.int
|
||||
STATUSMESSAGE_FIELD_NUMBER: builtins.int
|
||||
TRAFFIC_MANAGEMENT_FIELD_NUMBER: builtins.int
|
||||
TAK_FIELD_NUMBER: builtins.int
|
||||
@property
|
||||
def mqtt(self) -> global___ModuleConfig.MQTTConfig:
|
||||
"""
|
||||
@@ -1152,6 +1424,24 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
TODO: REPLACE
|
||||
"""
|
||||
|
||||
@property
|
||||
def statusmessage(self) -> global___ModuleConfig.StatusMessageConfig:
|
||||
"""
|
||||
TODO: REPLACE
|
||||
"""
|
||||
|
||||
@property
|
||||
def traffic_management(self) -> global___ModuleConfig.TrafficManagementConfig:
|
||||
"""
|
||||
Traffic management module config for mesh network optimization
|
||||
"""
|
||||
|
||||
@property
|
||||
def tak(self) -> global___ModuleConfig.TAKConfig:
|
||||
"""
|
||||
TAK team/role configuration for TAK_TRACKER
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -1168,10 +1458,13 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
ambient_lighting: global___ModuleConfig.AmbientLightingConfig | None = ...,
|
||||
detection_sensor: global___ModuleConfig.DetectionSensorConfig | None = ...,
|
||||
paxcounter: global___ModuleConfig.PaxcounterConfig | None = ...,
|
||||
statusmessage: global___ModuleConfig.StatusMessageConfig | None = ...,
|
||||
traffic_management: global___ModuleConfig.TrafficManagementConfig | None = ...,
|
||||
tak: global___ModuleConfig.TAKConfig | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["ambient_lighting", b"ambient_lighting", "audio", b"audio", "canned_message", b"canned_message", "detection_sensor", b"detection_sensor", "external_notification", b"external_notification", "mqtt", b"mqtt", "neighbor_info", b"neighbor_info", "paxcounter", b"paxcounter", "payload_variant", b"payload_variant", "range_test", b"range_test", "remote_hardware", b"remote_hardware", "serial", b"serial", "store_forward", b"store_forward", "telemetry", b"telemetry"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["ambient_lighting", b"ambient_lighting", "audio", b"audio", "canned_message", b"canned_message", "detection_sensor", b"detection_sensor", "external_notification", b"external_notification", "mqtt", b"mqtt", "neighbor_info", b"neighbor_info", "paxcounter", b"paxcounter", "payload_variant", b"payload_variant", "range_test", b"range_test", "remote_hardware", b"remote_hardware", "serial", b"serial", "store_forward", b"store_forward", "telemetry", b"telemetry"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["mqtt", "serial", "external_notification", "store_forward", "range_test", "telemetry", "canned_message", "audio", "remote_hardware", "neighbor_info", "ambient_lighting", "detection_sensor", "paxcounter"] | None: ...
|
||||
def HasField(self, field_name: typing.Literal["ambient_lighting", b"ambient_lighting", "audio", b"audio", "canned_message", b"canned_message", "detection_sensor", b"detection_sensor", "external_notification", b"external_notification", "mqtt", b"mqtt", "neighbor_info", b"neighbor_info", "paxcounter", b"paxcounter", "payload_variant", b"payload_variant", "range_test", b"range_test", "remote_hardware", b"remote_hardware", "serial", b"serial", "statusmessage", b"statusmessage", "store_forward", b"store_forward", "tak", b"tak", "telemetry", b"telemetry", "traffic_management", b"traffic_management"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["ambient_lighting", b"ambient_lighting", "audio", b"audio", "canned_message", b"canned_message", "detection_sensor", b"detection_sensor", "external_notification", b"external_notification", "mqtt", b"mqtt", "neighbor_info", b"neighbor_info", "paxcounter", b"paxcounter", "payload_variant", b"payload_variant", "range_test", b"range_test", "remote_hardware", b"remote_hardware", "serial", b"serial", "statusmessage", b"statusmessage", "store_forward", b"store_forward", "tak", b"tak", "telemetry", b"telemetry", "traffic_management", b"traffic_management"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["mqtt", "serial", "external_notification", "store_forward", "range_test", "telemetry", "canned_message", "audio", "remote_hardware", "neighbor_info", "ambient_lighting", "detection_sensor", "paxcounter", "statusmessage", "traffic_management", "tak"] | None: ...
|
||||
|
||||
global___ModuleConfig = ModuleConfig
|
||||
|
||||
|
||||
21
meshtastic/protobuf/mqtt_pb2.py
generated
21
meshtastic/protobuf/mqtt_pb2.py
generated
@@ -13,18 +13,27 @@ _sym_db = _symbol_database.Default()
|
||||
|
||||
from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config__pb2
|
||||
from meshtastic.protobuf import mesh_pb2 as meshtastic_dot_protobuf_dot_mesh__pb2
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1emeshtastic/protobuf/mqtt.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/config.proto\x1a\x1emeshtastic/protobuf/mesh.proto\"j\n\x0fServiceEnvelope\x12/\n\x06packet\x18\x01 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x12\n\nchannel_id\x18\x02 \x01(\t\x12\x12\n\ngateway_id\x18\x03 \x01(\t\"\xe0\x03\n\tMapReport\x12\x11\n\tlong_name\x18\x01 \x01(\t\x12\x12\n\nshort_name\x18\x02 \x01(\t\x12;\n\x04role\x18\x03 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x34\n\x08hw_model\x18\x04 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x18\n\x10\x66irmware_version\x18\x05 \x01(\t\x12\x41\n\x06region\x18\x06 \x01(\x0e\x32\x31.meshtastic.protobuf.Config.LoRaConfig.RegionCode\x12H\n\x0cmodem_preset\x18\x07 \x01(\x0e\x32\x32.meshtastic.protobuf.Config.LoRaConfig.ModemPreset\x12\x1b\n\x13has_default_channel\x18\x08 \x01(\x08\x12\x12\n\nlatitude_i\x18\t \x01(\x0f\x12\x13\n\x0blongitude_i\x18\n \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x0b \x01(\x05\x12\x1a\n\x12position_precision\x18\x0c \x01(\r\x12\x1e\n\x16num_online_local_nodes\x18\r \x01(\rB_\n\x13\x63om.geeksville.meshB\nMQTTProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1emeshtastic/protobuf/mqtt.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/config.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a meshtastic/protobuf/nanopb.proto\"j\n\x0fServiceEnvelope\x12/\n\x06packet\x18\x01 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x12\n\nchannel_id\x18\x02 \x01(\t\x12\x12\n\ngateway_id\x18\x03 \x01(\t\"\x9f\x04\n\tMapReport\x12\x18\n\tlong_name\x18\x01 \x01(\tB\x05\x92?\x02\x08(\x12\x19\n\nshort_name\x18\x02 \x01(\tB\x05\x92?\x02\x08\x05\x12;\n\x04role\x18\x03 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x34\n\x08hw_model\x18\x04 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x1f\n\x10\x66irmware_version\x18\x05 \x01(\tB\x05\x92?\x02\x08\x12\x12\x41\n\x06region\x18\x06 \x01(\x0e\x32\x31.meshtastic.protobuf.Config.LoRaConfig.RegionCode\x12H\n\x0cmodem_preset\x18\x07 \x01(\x0e\x32\x32.meshtastic.protobuf.Config.LoRaConfig.ModemPreset\x12\x1b\n\x13has_default_channel\x18\x08 \x01(\x08\x12\x12\n\nlatitude_i\x18\t \x01(\x0f\x12\x13\n\x0blongitude_i\x18\n \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x0b \x01(\x05\x12\x1a\n\x12position_precision\x18\x0c \x01(\r\x12%\n\x16num_online_local_nodes\x18\r \x01(\rB\x05\x92?\x02\x38\x10\x12!\n\x19has_opted_report_location\x18\x0e \x01(\x08\x42`\n\x14org.meshtastic.protoB\nMQTTProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.mqtt_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nMQTTProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_globals['_SERVICEENVELOPE']._serialized_start=121
|
||||
_globals['_SERVICEENVELOPE']._serialized_end=227
|
||||
_globals['_MAPREPORT']._serialized_start=230
|
||||
_globals['_MAPREPORT']._serialized_end=710
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\nMQTTProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_MAPREPORT.fields_by_name['long_name']._options = None
|
||||
_MAPREPORT.fields_by_name['long_name']._serialized_options = b'\222?\002\010('
|
||||
_MAPREPORT.fields_by_name['short_name']._options = None
|
||||
_MAPREPORT.fields_by_name['short_name']._serialized_options = b'\222?\002\010\005'
|
||||
_MAPREPORT.fields_by_name['firmware_version']._options = None
|
||||
_MAPREPORT.fields_by_name['firmware_version']._serialized_options = b'\222?\002\010\022'
|
||||
_MAPREPORT.fields_by_name['num_online_local_nodes']._options = None
|
||||
_MAPREPORT.fields_by_name['num_online_local_nodes']._serialized_options = b'\222?\0028\020'
|
||||
_globals['_SERVICEENVELOPE']._serialized_start=155
|
||||
_globals['_SERVICEENVELOPE']._serialized_end=261
|
||||
_globals['_MAPREPORT']._serialized_start=264
|
||||
_globals['_MAPREPORT']._serialized_end=807
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
9
meshtastic/protobuf/mqtt_pb2.pyi
generated
9
meshtastic/protobuf/mqtt_pb2.pyi
generated
@@ -72,6 +72,7 @@ class MapReport(google.protobuf.message.Message):
|
||||
ALTITUDE_FIELD_NUMBER: builtins.int
|
||||
POSITION_PRECISION_FIELD_NUMBER: builtins.int
|
||||
NUM_ONLINE_LOCAL_NODES_FIELD_NUMBER: builtins.int
|
||||
HAS_OPTED_REPORT_LOCATION_FIELD_NUMBER: builtins.int
|
||||
long_name: builtins.str
|
||||
"""
|
||||
A full name for this user, i.e. "Kevin Hester"
|
||||
@@ -126,6 +127,11 @@ class MapReport(google.protobuf.message.Message):
|
||||
"""
|
||||
Number of online nodes (heard in the last 2 hours) this node has in its list that were received locally (not via MQTT)
|
||||
"""
|
||||
has_opted_report_location: builtins.bool
|
||||
"""
|
||||
User has opted in to share their location (map report) with the mqtt server
|
||||
Controlled by map_report.should_report_location
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -142,7 +148,8 @@ class MapReport(google.protobuf.message.Message):
|
||||
altitude: builtins.int = ...,
|
||||
position_precision: builtins.int = ...,
|
||||
num_online_local_nodes: builtins.int = ...,
|
||||
has_opted_report_location: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["altitude", b"altitude", "firmware_version", b"firmware_version", "has_default_channel", b"has_default_channel", "hw_model", b"hw_model", "latitude_i", b"latitude_i", "long_name", b"long_name", "longitude_i", b"longitude_i", "modem_preset", b"modem_preset", "num_online_local_nodes", b"num_online_local_nodes", "position_precision", b"position_precision", "region", b"region", "role", b"role", "short_name", b"short_name"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["altitude", b"altitude", "firmware_version", b"firmware_version", "has_default_channel", b"has_default_channel", "has_opted_report_location", b"has_opted_report_location", "hw_model", b"hw_model", "latitude_i", b"latitude_i", "long_name", b"long_name", "longitude_i", b"longitude_i", "modem_preset", b"modem_preset", "num_online_local_nodes", b"num_online_local_nodes", "position_precision", b"position_precision", "region", b"region", "role", b"role", "short_name", b"short_name"]) -> None: ...
|
||||
|
||||
global___MapReport = MapReport
|
||||
|
||||
35
meshtastic/protobuf/nanopb_pb2.py
generated
Normal file
35
meshtastic/protobuf/nanopb_pb2.py
generated
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: meshtastic/protobuf/nanopb.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n meshtastic/protobuf/nanopb.proto\x1a google/protobuf/descriptor.proto\"\xa4\x07\n\rNanoPBOptions\x12\x10\n\x08max_size\x18\x01 \x01(\x05\x12\x12\n\nmax_length\x18\x0e \x01(\x05\x12\x11\n\tmax_count\x18\x02 \x01(\x05\x12&\n\x08int_size\x18\x07 \x01(\x0e\x32\x08.IntSize:\nIS_DEFAULT\x12$\n\x04type\x18\x03 \x01(\x0e\x32\n.FieldType:\nFT_DEFAULT\x12\x18\n\nlong_names\x18\x04 \x01(\x08:\x04true\x12\x1c\n\rpacked_struct\x18\x05 \x01(\x08:\x05\x66\x61lse\x12\x1a\n\x0bpacked_enum\x18\n \x01(\x08:\x05\x66\x61lse\x12\x1b\n\x0cskip_message\x18\x06 \x01(\x08:\x05\x66\x61lse\x12\x18\n\tno_unions\x18\x08 \x01(\x08:\x05\x66\x61lse\x12\r\n\x05msgid\x18\t \x01(\r\x12\x1e\n\x0f\x61nonymous_oneof\x18\x0b \x01(\x08:\x05\x66\x61lse\x12\x15\n\x06proto3\x18\x0c \x01(\x08:\x05\x66\x61lse\x12#\n\x14proto3_singular_msgs\x18\x15 \x01(\x08:\x05\x66\x61lse\x12\x1d\n\x0e\x65num_to_string\x18\r \x01(\x08:\x05\x66\x61lse\x12\x1b\n\x0c\x66ixed_length\x18\x0f \x01(\x08:\x05\x66\x61lse\x12\x1a\n\x0b\x66ixed_count\x18\x10 \x01(\x08:\x05\x66\x61lse\x12\x1e\n\x0fsubmsg_callback\x18\x16 \x01(\x08:\x05\x66\x61lse\x12/\n\x0cmangle_names\x18\x11 \x01(\x0e\x32\x11.TypenameMangling:\x06M_NONE\x12(\n\x11\x63\x61llback_datatype\x18\x12 \x01(\t:\rpb_callback_t\x12\x34\n\x11\x63\x61llback_function\x18\x13 \x01(\t:\x19pb_default_field_callback\x12\x30\n\x0e\x64\x65scriptorsize\x18\x14 \x01(\x0e\x32\x0f.DescriptorSize:\x07\x44S_AUTO\x12\x1a\n\x0b\x64\x65\x66\x61ult_has\x18\x17 \x01(\x08:\x05\x66\x61lse\x12\x0f\n\x07include\x18\x18 \x03(\t\x12\x0f\n\x07\x65xclude\x18\x1a \x03(\t\x12\x0f\n\x07package\x18\x19 \x01(\t\x12\x41\n\rtype_override\x18\x1b \x01(\x0e\x32*.google.protobuf.FieldDescriptorProto.Type\x12\x19\n\x0bsort_by_tag\x18\x1c \x01(\x08:\x04true\x12.\n\rfallback_type\x18\x1d \x01(\x0e\x32\n.FieldType:\x0b\x46T_CALLBACK*i\n\tFieldType\x12\x0e\n\nFT_DEFAULT\x10\x00\x12\x0f\n\x0b\x46T_CALLBACK\x10\x01\x12\x0e\n\nFT_POINTER\x10\x04\x12\r\n\tFT_STATIC\x10\x02\x12\r\n\tFT_IGNORE\x10\x03\x12\r\n\tFT_INLINE\x10\x05*D\n\x07IntSize\x12\x0e\n\nIS_DEFAULT\x10\x00\x12\x08\n\x04IS_8\x10\x08\x12\t\n\x05IS_16\x10\x10\x12\t\n\x05IS_32\x10 \x12\t\n\x05IS_64\x10@*Z\n\x10TypenameMangling\x12\n\n\x06M_NONE\x10\x00\x12\x13\n\x0fM_STRIP_PACKAGE\x10\x01\x12\r\n\tM_FLATTEN\x10\x02\x12\x16\n\x12M_PACKAGE_INITIALS\x10\x03*E\n\x0e\x44\x65scriptorSize\x12\x0b\n\x07\x44S_AUTO\x10\x00\x12\x08\n\x04\x44S_1\x10\x01\x12\x08\n\x04\x44S_2\x10\x02\x12\x08\n\x04\x44S_4\x10\x04\x12\x08\n\x04\x44S_8\x10\x08:E\n\x0enanopb_fileopt\x12\x1c.google.protobuf.FileOptions\x18\xf2\x07 \x01(\x0b\x32\x0e.NanoPBOptions:G\n\rnanopb_msgopt\x12\x1f.google.protobuf.MessageOptions\x18\xf2\x07 \x01(\x0b\x32\x0e.NanoPBOptions:E\n\x0enanopb_enumopt\x12\x1c.google.protobuf.EnumOptions\x18\xf2\x07 \x01(\x0b\x32\x0e.NanoPBOptions:>\n\x06nanopb\x12\x1d.google.protobuf.FieldOptions\x18\xf2\x07 \x01(\x0b\x32\x0e.NanoPBOptionsB>\n\x18\x66i.kapsi.koti.jpa.nanopbZ\"github.com/meshtastic/go/generated')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.nanopb_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\030fi.kapsi.koti.jpa.nanopbZ\"github.com/meshtastic/go/generated'
|
||||
_globals['_FIELDTYPE']._serialized_start=1005
|
||||
_globals['_FIELDTYPE']._serialized_end=1110
|
||||
_globals['_INTSIZE']._serialized_start=1112
|
||||
_globals['_INTSIZE']._serialized_end=1180
|
||||
_globals['_TYPENAMEMANGLING']._serialized_start=1182
|
||||
_globals['_TYPENAMEMANGLING']._serialized_end=1272
|
||||
_globals['_DESCRIPTORSIZE']._serialized_start=1274
|
||||
_globals['_DESCRIPTORSIZE']._serialized_end=1343
|
||||
_globals['_NANOPBOPTIONS']._serialized_start=71
|
||||
_globals['_NANOPBOPTIONS']._serialized_end=1003
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
324
meshtastic/protobuf/nanopb_pb2.pyi
generated
Normal file
324
meshtastic/protobuf/nanopb_pb2.pyi
generated
Normal file
@@ -0,0 +1,324 @@
|
||||
"""
|
||||
@generated by mypy-protobuf. Do not edit manually!
|
||||
isort:skip_file
|
||||
Custom options for defining:
|
||||
- Maximum size of string/bytes
|
||||
- Maximum number of elements in array
|
||||
|
||||
These are used by nanopb to generate statically allocable structures
|
||||
for memory-limited environments.
|
||||
"""
|
||||
|
||||
import builtins
|
||||
import collections.abc
|
||||
import google.protobuf.descriptor
|
||||
import google.protobuf.descriptor_pb2
|
||||
import google.protobuf.internal.containers
|
||||
import google.protobuf.internal.enum_type_wrapper
|
||||
import google.protobuf.internal.extension_dict
|
||||
import google.protobuf.message
|
||||
import sys
|
||||
import typing
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
import typing as typing_extensions
|
||||
else:
|
||||
import typing_extensions
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
|
||||
|
||||
class _FieldType:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _FieldTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_FieldType.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
FT_DEFAULT: _FieldType.ValueType # 0
|
||||
"""Automatically decide field type, generate static field if possible."""
|
||||
FT_CALLBACK: _FieldType.ValueType # 1
|
||||
"""Always generate a callback field."""
|
||||
FT_POINTER: _FieldType.ValueType # 4
|
||||
"""Always generate a dynamically allocated field."""
|
||||
FT_STATIC: _FieldType.ValueType # 2
|
||||
"""Generate a static field or raise an exception if not possible."""
|
||||
FT_IGNORE: _FieldType.ValueType # 3
|
||||
"""Ignore the field completely."""
|
||||
FT_INLINE: _FieldType.ValueType # 5
|
||||
"""Legacy option, use the separate 'fixed_length' option instead"""
|
||||
|
||||
class FieldType(_FieldType, metaclass=_FieldTypeEnumTypeWrapper): ...
|
||||
|
||||
FT_DEFAULT: FieldType.ValueType # 0
|
||||
"""Automatically decide field type, generate static field if possible."""
|
||||
FT_CALLBACK: FieldType.ValueType # 1
|
||||
"""Always generate a callback field."""
|
||||
FT_POINTER: FieldType.ValueType # 4
|
||||
"""Always generate a dynamically allocated field."""
|
||||
FT_STATIC: FieldType.ValueType # 2
|
||||
"""Generate a static field or raise an exception if not possible."""
|
||||
FT_IGNORE: FieldType.ValueType # 3
|
||||
"""Ignore the field completely."""
|
||||
FT_INLINE: FieldType.ValueType # 5
|
||||
"""Legacy option, use the separate 'fixed_length' option instead"""
|
||||
global___FieldType = FieldType
|
||||
|
||||
class _IntSize:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _IntSizeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_IntSize.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
IS_DEFAULT: _IntSize.ValueType # 0
|
||||
"""Default, 32/64bit based on type in .proto"""
|
||||
IS_8: _IntSize.ValueType # 8
|
||||
IS_16: _IntSize.ValueType # 16
|
||||
IS_32: _IntSize.ValueType # 32
|
||||
IS_64: _IntSize.ValueType # 64
|
||||
|
||||
class IntSize(_IntSize, metaclass=_IntSizeEnumTypeWrapper): ...
|
||||
|
||||
IS_DEFAULT: IntSize.ValueType # 0
|
||||
"""Default, 32/64bit based on type in .proto"""
|
||||
IS_8: IntSize.ValueType # 8
|
||||
IS_16: IntSize.ValueType # 16
|
||||
IS_32: IntSize.ValueType # 32
|
||||
IS_64: IntSize.ValueType # 64
|
||||
global___IntSize = IntSize
|
||||
|
||||
class _TypenameMangling:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _TypenameManglingEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_TypenameMangling.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
M_NONE: _TypenameMangling.ValueType # 0
|
||||
"""Default, no typename mangling"""
|
||||
M_STRIP_PACKAGE: _TypenameMangling.ValueType # 1
|
||||
"""Strip current package name"""
|
||||
M_FLATTEN: _TypenameMangling.ValueType # 2
|
||||
"""Only use last path component"""
|
||||
M_PACKAGE_INITIALS: _TypenameMangling.ValueType # 3
|
||||
"""Replace the package name by the initials"""
|
||||
|
||||
class TypenameMangling(_TypenameMangling, metaclass=_TypenameManglingEnumTypeWrapper): ...
|
||||
|
||||
M_NONE: TypenameMangling.ValueType # 0
|
||||
"""Default, no typename mangling"""
|
||||
M_STRIP_PACKAGE: TypenameMangling.ValueType # 1
|
||||
"""Strip current package name"""
|
||||
M_FLATTEN: TypenameMangling.ValueType # 2
|
||||
"""Only use last path component"""
|
||||
M_PACKAGE_INITIALS: TypenameMangling.ValueType # 3
|
||||
"""Replace the package name by the initials"""
|
||||
global___TypenameMangling = TypenameMangling
|
||||
|
||||
class _DescriptorSize:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _DescriptorSizeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_DescriptorSize.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
DS_AUTO: _DescriptorSize.ValueType # 0
|
||||
"""Select minimal size based on field type"""
|
||||
DS_1: _DescriptorSize.ValueType # 1
|
||||
"""1 word; up to 15 byte fields, no arrays"""
|
||||
DS_2: _DescriptorSize.ValueType # 2
|
||||
"""2 words; up to 4095 byte fields, 4095 entry arrays"""
|
||||
DS_4: _DescriptorSize.ValueType # 4
|
||||
"""4 words; up to 2^32-1 byte fields, 2^16-1 entry arrays"""
|
||||
DS_8: _DescriptorSize.ValueType # 8
|
||||
"""8 words; up to 2^32-1 entry arrays"""
|
||||
|
||||
class DescriptorSize(_DescriptorSize, metaclass=_DescriptorSizeEnumTypeWrapper): ...
|
||||
|
||||
DS_AUTO: DescriptorSize.ValueType # 0
|
||||
"""Select minimal size based on field type"""
|
||||
DS_1: DescriptorSize.ValueType # 1
|
||||
"""1 word; up to 15 byte fields, no arrays"""
|
||||
DS_2: DescriptorSize.ValueType # 2
|
||||
"""2 words; up to 4095 byte fields, 4095 entry arrays"""
|
||||
DS_4: DescriptorSize.ValueType # 4
|
||||
"""4 words; up to 2^32-1 byte fields, 2^16-1 entry arrays"""
|
||||
DS_8: DescriptorSize.ValueType # 8
|
||||
"""8 words; up to 2^32-1 entry arrays"""
|
||||
global___DescriptorSize = DescriptorSize
|
||||
|
||||
@typing.final
|
||||
class NanoPBOptions(google.protobuf.message.Message):
|
||||
"""This is the inner options message, which basically defines options for
|
||||
a field. When it is used in message or file scope, it applies to all
|
||||
fields.
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
MAX_SIZE_FIELD_NUMBER: builtins.int
|
||||
MAX_LENGTH_FIELD_NUMBER: builtins.int
|
||||
MAX_COUNT_FIELD_NUMBER: builtins.int
|
||||
INT_SIZE_FIELD_NUMBER: builtins.int
|
||||
TYPE_FIELD_NUMBER: builtins.int
|
||||
LONG_NAMES_FIELD_NUMBER: builtins.int
|
||||
PACKED_STRUCT_FIELD_NUMBER: builtins.int
|
||||
PACKED_ENUM_FIELD_NUMBER: builtins.int
|
||||
SKIP_MESSAGE_FIELD_NUMBER: builtins.int
|
||||
NO_UNIONS_FIELD_NUMBER: builtins.int
|
||||
MSGID_FIELD_NUMBER: builtins.int
|
||||
ANONYMOUS_ONEOF_FIELD_NUMBER: builtins.int
|
||||
PROTO3_FIELD_NUMBER: builtins.int
|
||||
PROTO3_SINGULAR_MSGS_FIELD_NUMBER: builtins.int
|
||||
ENUM_TO_STRING_FIELD_NUMBER: builtins.int
|
||||
FIXED_LENGTH_FIELD_NUMBER: builtins.int
|
||||
FIXED_COUNT_FIELD_NUMBER: builtins.int
|
||||
SUBMSG_CALLBACK_FIELD_NUMBER: builtins.int
|
||||
MANGLE_NAMES_FIELD_NUMBER: builtins.int
|
||||
CALLBACK_DATATYPE_FIELD_NUMBER: builtins.int
|
||||
CALLBACK_FUNCTION_FIELD_NUMBER: builtins.int
|
||||
DESCRIPTORSIZE_FIELD_NUMBER: builtins.int
|
||||
DEFAULT_HAS_FIELD_NUMBER: builtins.int
|
||||
INCLUDE_FIELD_NUMBER: builtins.int
|
||||
EXCLUDE_FIELD_NUMBER: builtins.int
|
||||
PACKAGE_FIELD_NUMBER: builtins.int
|
||||
TYPE_OVERRIDE_FIELD_NUMBER: builtins.int
|
||||
SORT_BY_TAG_FIELD_NUMBER: builtins.int
|
||||
FALLBACK_TYPE_FIELD_NUMBER: builtins.int
|
||||
max_size: builtins.int
|
||||
"""Allocated size for 'bytes' and 'string' fields.
|
||||
For string fields, this should include the space for null terminator.
|
||||
"""
|
||||
max_length: builtins.int
|
||||
"""Maximum length for 'string' fields. Setting this is equivalent
|
||||
to setting max_size to a value of length+1.
|
||||
"""
|
||||
max_count: builtins.int
|
||||
"""Allocated number of entries in arrays ('repeated' fields)"""
|
||||
int_size: global___IntSize.ValueType
|
||||
"""Size of integer fields. Can save some memory if you don't need
|
||||
full 32 bits for the value.
|
||||
"""
|
||||
type: global___FieldType.ValueType
|
||||
"""Force type of field (callback or static allocation)"""
|
||||
long_names: builtins.bool
|
||||
"""Use long names for enums, i.e. EnumName_EnumValue."""
|
||||
packed_struct: builtins.bool
|
||||
"""Add 'packed' attribute to generated structs.
|
||||
Note: this cannot be used on CPUs that break on unaligned
|
||||
accesses to variables.
|
||||
"""
|
||||
packed_enum: builtins.bool
|
||||
"""Add 'packed' attribute to generated enums."""
|
||||
skip_message: builtins.bool
|
||||
"""Skip this message"""
|
||||
no_unions: builtins.bool
|
||||
"""Generate oneof fields as normal optional fields instead of union."""
|
||||
msgid: builtins.int
|
||||
"""integer type tag for a message"""
|
||||
anonymous_oneof: builtins.bool
|
||||
"""decode oneof as anonymous union"""
|
||||
proto3: builtins.bool
|
||||
"""Proto3 singular field does not generate a "has_" flag"""
|
||||
proto3_singular_msgs: builtins.bool
|
||||
"""Force proto3 messages to have no "has_" flag.
|
||||
This was default behavior until nanopb-0.4.0.
|
||||
"""
|
||||
enum_to_string: builtins.bool
|
||||
"""Generate an enum->string mapping function (can take up lots of space)."""
|
||||
fixed_length: builtins.bool
|
||||
"""Generate bytes arrays with fixed length"""
|
||||
fixed_count: builtins.bool
|
||||
"""Generate repeated field with fixed count"""
|
||||
submsg_callback: builtins.bool
|
||||
"""Generate message-level callback that is called before decoding submessages.
|
||||
This can be used to set callback fields for submsgs inside oneofs.
|
||||
"""
|
||||
mangle_names: global___TypenameMangling.ValueType
|
||||
"""Shorten or remove package names from type names.
|
||||
This option applies only on the file level.
|
||||
"""
|
||||
callback_datatype: builtins.str
|
||||
"""Data type for storage associated with callback fields."""
|
||||
callback_function: builtins.str
|
||||
"""Callback function used for encoding and decoding.
|
||||
Prior to nanopb-0.4.0, the callback was specified in per-field pb_callback_t
|
||||
structure. This is still supported, but does not work inside e.g. oneof or pointer
|
||||
fields. Instead, a new method allows specifying a per-message callback that
|
||||
will be called for all callback fields in a message type.
|
||||
"""
|
||||
descriptorsize: global___DescriptorSize.ValueType
|
||||
"""Select the size of field descriptors. This option has to be defined
|
||||
for the whole message, not per-field. Usually automatic selection is
|
||||
ok, but if it results in compilation errors you can increase the field
|
||||
size here.
|
||||
"""
|
||||
default_has: builtins.bool
|
||||
"""Set default value for has_ fields."""
|
||||
package: builtins.str
|
||||
"""Package name that applies only for nanopb."""
|
||||
type_override: google.protobuf.descriptor_pb2.FieldDescriptorProto.Type.ValueType
|
||||
"""Override type of the field in generated C code. Only to be used with related field types"""
|
||||
sort_by_tag: builtins.bool
|
||||
"""Due to historical reasons, nanopb orders fields in structs by their tag number
|
||||
instead of the order in .proto. Set this to false to keep the .proto order.
|
||||
The default value will probably change to false in nanopb-0.5.0.
|
||||
"""
|
||||
fallback_type: global___FieldType.ValueType
|
||||
"""Set the FT_DEFAULT field conversion strategy.
|
||||
A field that can become a static member of a c struct (e.g. int, bool, etc)
|
||||
will be a a static field.
|
||||
Fields with dynamic length are converted to either a pointer or a callback.
|
||||
"""
|
||||
@property
|
||||
def include(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]:
|
||||
"""Extra files to include in generated `.pb.h`"""
|
||||
|
||||
@property
|
||||
def exclude(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]:
|
||||
"""Automatic includes to exclude from generated `.pb.h`
|
||||
Same as nanopb_generator.py command line flag -x.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
max_size: builtins.int | None = ...,
|
||||
max_length: builtins.int | None = ...,
|
||||
max_count: builtins.int | None = ...,
|
||||
int_size: global___IntSize.ValueType | None = ...,
|
||||
type: global___FieldType.ValueType | None = ...,
|
||||
long_names: builtins.bool | None = ...,
|
||||
packed_struct: builtins.bool | None = ...,
|
||||
packed_enum: builtins.bool | None = ...,
|
||||
skip_message: builtins.bool | None = ...,
|
||||
no_unions: builtins.bool | None = ...,
|
||||
msgid: builtins.int | None = ...,
|
||||
anonymous_oneof: builtins.bool | None = ...,
|
||||
proto3: builtins.bool | None = ...,
|
||||
proto3_singular_msgs: builtins.bool | None = ...,
|
||||
enum_to_string: builtins.bool | None = ...,
|
||||
fixed_length: builtins.bool | None = ...,
|
||||
fixed_count: builtins.bool | None = ...,
|
||||
submsg_callback: builtins.bool | None = ...,
|
||||
mangle_names: global___TypenameMangling.ValueType | None = ...,
|
||||
callback_datatype: builtins.str | None = ...,
|
||||
callback_function: builtins.str | None = ...,
|
||||
descriptorsize: global___DescriptorSize.ValueType | None = ...,
|
||||
default_has: builtins.bool | None = ...,
|
||||
include: collections.abc.Iterable[builtins.str] | None = ...,
|
||||
exclude: collections.abc.Iterable[builtins.str] | None = ...,
|
||||
package: builtins.str | None = ...,
|
||||
type_override: google.protobuf.descriptor_pb2.FieldDescriptorProto.Type.ValueType | None = ...,
|
||||
sort_by_tag: builtins.bool | None = ...,
|
||||
fallback_type: global___FieldType.ValueType | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["anonymous_oneof", b"anonymous_oneof", "callback_datatype", b"callback_datatype", "callback_function", b"callback_function", "default_has", b"default_has", "descriptorsize", b"descriptorsize", "enum_to_string", b"enum_to_string", "fallback_type", b"fallback_type", "fixed_count", b"fixed_count", "fixed_length", b"fixed_length", "int_size", b"int_size", "long_names", b"long_names", "mangle_names", b"mangle_names", "max_count", b"max_count", "max_length", b"max_length", "max_size", b"max_size", "msgid", b"msgid", "no_unions", b"no_unions", "package", b"package", "packed_enum", b"packed_enum", "packed_struct", b"packed_struct", "proto3", b"proto3", "proto3_singular_msgs", b"proto3_singular_msgs", "skip_message", b"skip_message", "sort_by_tag", b"sort_by_tag", "submsg_callback", b"submsg_callback", "type", b"type", "type_override", b"type_override"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["anonymous_oneof", b"anonymous_oneof", "callback_datatype", b"callback_datatype", "callback_function", b"callback_function", "default_has", b"default_has", "descriptorsize", b"descriptorsize", "enum_to_string", b"enum_to_string", "exclude", b"exclude", "fallback_type", b"fallback_type", "fixed_count", b"fixed_count", "fixed_length", b"fixed_length", "include", b"include", "int_size", b"int_size", "long_names", b"long_names", "mangle_names", b"mangle_names", "max_count", b"max_count", "max_length", b"max_length", "max_size", b"max_size", "msgid", b"msgid", "no_unions", b"no_unions", "package", b"package", "packed_enum", b"packed_enum", "packed_struct", b"packed_struct", "proto3", b"proto3", "proto3_singular_msgs", b"proto3_singular_msgs", "skip_message", b"skip_message", "sort_by_tag", b"sort_by_tag", "submsg_callback", b"submsg_callback", "type", b"type", "type_override", b"type_override"]) -> None: ...
|
||||
|
||||
global___NanoPBOptions = NanoPBOptions
|
||||
|
||||
NANOPB_FILEOPT_FIELD_NUMBER: builtins.int
|
||||
NANOPB_MSGOPT_FIELD_NUMBER: builtins.int
|
||||
NANOPB_ENUMOPT_FIELD_NUMBER: builtins.int
|
||||
NANOPB_FIELD_NUMBER: builtins.int
|
||||
nanopb_fileopt: google.protobuf.internal.extension_dict._ExtensionFieldDescriptor[google.protobuf.descriptor_pb2.FileOptions, global___NanoPBOptions]
|
||||
nanopb_msgopt: google.protobuf.internal.extension_dict._ExtensionFieldDescriptor[google.protobuf.descriptor_pb2.MessageOptions, global___NanoPBOptions]
|
||||
nanopb_enumopt: google.protobuf.internal.extension_dict._ExtensionFieldDescriptor[google.protobuf.descriptor_pb2.EnumOptions, global___NanoPBOptions]
|
||||
nanopb: google.protobuf.internal.extension_dict._ExtensionFieldDescriptor[google.protobuf.descriptor_pb2.FieldOptions, global___NanoPBOptions]
|
||||
4
meshtastic/protobuf/paxcount_pb2.py
generated
4
meshtastic/protobuf/paxcount_pb2.py
generated
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/paxcount.proto\x12\x13meshtastic.protobuf\"5\n\x08Paxcount\x12\x0c\n\x04wifi\x18\x01 \x01(\r\x12\x0b\n\x03\x62le\x18\x02 \x01(\r\x12\x0e\n\x06uptime\x18\x03 \x01(\rBc\n\x13\x63om.geeksville.meshB\x0ePaxcountProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/paxcount.proto\x12\x13meshtastic.protobuf\"5\n\x08Paxcount\x12\x0c\n\x04wifi\x18\x01 \x01(\r\x12\x0b\n\x03\x62le\x18\x02 \x01(\r\x12\x0e\n\x06uptime\x18\x03 \x01(\rBd\n\x14org.meshtastic.protoB\x0ePaxcountProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.paxcount_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\016PaxcountProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\016PaxcountProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_globals['_PAXCOUNT']._serialized_start=59
|
||||
_globals['_PAXCOUNT']._serialized_end=112
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
6
meshtastic/protobuf/portnums_pb2.py
generated
6
meshtastic/protobuf/portnums_pb2.py
generated
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/portnums.proto\x12\x13meshtastic.protobuf*\xa2\x04\n\x07PortNum\x12\x0f\n\x0bUNKNOWN_APP\x10\x00\x12\x14\n\x10TEXT_MESSAGE_APP\x10\x01\x12\x17\n\x13REMOTE_HARDWARE_APP\x10\x02\x12\x10\n\x0cPOSITION_APP\x10\x03\x12\x10\n\x0cNODEINFO_APP\x10\x04\x12\x0f\n\x0bROUTING_APP\x10\x05\x12\r\n\tADMIN_APP\x10\x06\x12\x1f\n\x1bTEXT_MESSAGE_COMPRESSED_APP\x10\x07\x12\x10\n\x0cWAYPOINT_APP\x10\x08\x12\r\n\tAUDIO_APP\x10\t\x12\x18\n\x14\x44\x45TECTION_SENSOR_APP\x10\n\x12\r\n\tREPLY_APP\x10 \x12\x11\n\rIP_TUNNEL_APP\x10!\x12\x12\n\x0ePAXCOUNTER_APP\x10\"\x12\x0e\n\nSERIAL_APP\x10@\x12\x15\n\x11STORE_FORWARD_APP\x10\x41\x12\x12\n\x0eRANGE_TEST_APP\x10\x42\x12\x11\n\rTELEMETRY_APP\x10\x43\x12\x0b\n\x07ZPS_APP\x10\x44\x12\x11\n\rSIMULATOR_APP\x10\x45\x12\x12\n\x0eTRACEROUTE_APP\x10\x46\x12\x14\n\x10NEIGHBORINFO_APP\x10G\x12\x0f\n\x0b\x41TAK_PLUGIN\x10H\x12\x12\n\x0eMAP_REPORT_APP\x10I\x12\x13\n\x0fPOWERSTRESS_APP\x10J\x12\x10\n\x0bPRIVATE_APP\x10\x80\x02\x12\x13\n\x0e\x41TAK_FORWARDER\x10\x81\x02\x12\x08\n\x03MAX\x10\xff\x03\x42]\n\x13\x63om.geeksville.meshB\x08PortnumsZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/portnums.proto\x12\x13meshtastic.protobuf*\xfd\x05\n\x07PortNum\x12\x0f\n\x0bUNKNOWN_APP\x10\x00\x12\x14\n\x10TEXT_MESSAGE_APP\x10\x01\x12\x17\n\x13REMOTE_HARDWARE_APP\x10\x02\x12\x10\n\x0cPOSITION_APP\x10\x03\x12\x10\n\x0cNODEINFO_APP\x10\x04\x12\x0f\n\x0bROUTING_APP\x10\x05\x12\r\n\tADMIN_APP\x10\x06\x12\x1f\n\x1bTEXT_MESSAGE_COMPRESSED_APP\x10\x07\x12\x10\n\x0cWAYPOINT_APP\x10\x08\x12\r\n\tAUDIO_APP\x10\t\x12\x18\n\x14\x44\x45TECTION_SENSOR_APP\x10\n\x12\r\n\tALERT_APP\x10\x0b\x12\x18\n\x14KEY_VERIFICATION_APP\x10\x0c\x12\x14\n\x10REMOTE_SHELL_APP\x10\r\x12\r\n\tREPLY_APP\x10 \x12\x11\n\rIP_TUNNEL_APP\x10!\x12\x12\n\x0ePAXCOUNTER_APP\x10\"\x12\x1e\n\x1aSTORE_FORWARD_PLUSPLUS_APP\x10#\x12\x13\n\x0fNODE_STATUS_APP\x10$\x12\x0e\n\nSERIAL_APP\x10@\x12\x15\n\x11STORE_FORWARD_APP\x10\x41\x12\x12\n\x0eRANGE_TEST_APP\x10\x42\x12\x11\n\rTELEMETRY_APP\x10\x43\x12\x0b\n\x07ZPS_APP\x10\x44\x12\x11\n\rSIMULATOR_APP\x10\x45\x12\x12\n\x0eTRACEROUTE_APP\x10\x46\x12\x14\n\x10NEIGHBORINFO_APP\x10G\x12\x0f\n\x0b\x41TAK_PLUGIN\x10H\x12\x12\n\x0eMAP_REPORT_APP\x10I\x12\x13\n\x0fPOWERSTRESS_APP\x10J\x12\x12\n\x0eLORAWAN_BRIDGE\x10K\x12\x18\n\x14RETICULUM_TUNNEL_APP\x10L\x12\x0f\n\x0b\x43\x41YENNE_APP\x10M\x12\x12\n\x0e\x41TAK_PLUGIN_V2\x10N\x12\x12\n\x0eGROUPALARM_APP\x10p\x12\x10\n\x0bPRIVATE_APP\x10\x80\x02\x12\x13\n\x0e\x41TAK_FORWARDER\x10\x81\x02\x12\x08\n\x03MAX\x10\xff\x03\x42^\n\x14org.meshtastic.protoB\x08PortnumsZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.portnums_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\010PortnumsZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\010PortnumsZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_globals['_PORTNUM']._serialized_start=60
|
||||
_globals['_PORTNUM']._serialized_end=606
|
||||
_globals['_PORTNUM']._serialized_end=825
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
112
meshtastic/protobuf/portnums_pb2.pyi
generated
112
meshtastic/protobuf/portnums_pb2.pyi
generated
@@ -93,6 +93,18 @@ class _PortNumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTy
|
||||
Same as Text Message but originating from Detection Sensor Module.
|
||||
NOTE: This portnum traffic is not sent to the public MQTT starting at firmware version 2.2.9
|
||||
"""
|
||||
ALERT_APP: _PortNum.ValueType # 11
|
||||
"""
|
||||
Same as Text Message but used for critical alerts.
|
||||
"""
|
||||
KEY_VERIFICATION_APP: _PortNum.ValueType # 12
|
||||
"""
|
||||
Module/port for handling key verification requests.
|
||||
"""
|
||||
REMOTE_SHELL_APP: _PortNum.ValueType # 13
|
||||
"""
|
||||
Module/port for handling primitive remote shell access.
|
||||
"""
|
||||
REPLY_APP: _PortNum.ValueType # 32
|
||||
"""
|
||||
Provides a 'ping' service that replies to any packet it receives.
|
||||
@@ -109,6 +121,20 @@ class _PortNumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTy
|
||||
Paxcounter lib included in the firmware
|
||||
ENCODING: protobuf
|
||||
"""
|
||||
STORE_FORWARD_PLUSPLUS_APP: _PortNum.ValueType # 35
|
||||
"""
|
||||
Store and Forward++ module included in the firmware
|
||||
ENCODING: protobuf
|
||||
This module is specifically for Native Linux nodes, and provides a Git-style
|
||||
chain of messages.
|
||||
"""
|
||||
NODE_STATUS_APP: _PortNum.ValueType # 36
|
||||
"""
|
||||
Node Status module
|
||||
ENCODING: protobuf
|
||||
This module allows setting an extra string of status for a node.
|
||||
Broadcasts on change and on a timer, possibly once a day.
|
||||
"""
|
||||
SERIAL_APP: _PortNum.ValueType # 64
|
||||
"""
|
||||
Provides a hardware serial interface to send and receive from the Meshtastic network.
|
||||
@@ -154,7 +180,7 @@ class _PortNumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTy
|
||||
TRACEROUTE_APP: _PortNum.ValueType # 70
|
||||
"""
|
||||
Provides a traceroute functionality to show the route a packet towards
|
||||
a certain destination would take on the mesh.
|
||||
a certain destination would take on the mesh. Contains a RouteDiscovery message as payload.
|
||||
ENCODING: Protobuf
|
||||
"""
|
||||
NEIGHBORINFO_APP: _PortNum.ValueType # 71
|
||||
@@ -175,6 +201,34 @@ class _PortNumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTy
|
||||
"""
|
||||
PowerStress based monitoring support (for automated power consumption testing)
|
||||
"""
|
||||
LORAWAN_BRIDGE: _PortNum.ValueType # 75
|
||||
"""
|
||||
LoraWAN Payload Transport
|
||||
ENCODING: compact binary LoRaWAN uplink (10-byte RF metadata + PHY payload) - see LoRaWANBridgeModule
|
||||
"""
|
||||
RETICULUM_TUNNEL_APP: _PortNum.ValueType # 76
|
||||
"""
|
||||
Reticulum Network Stack Tunnel App
|
||||
ENCODING: Fragmented RNS Packet. Handled by Meshtastic RNS interface
|
||||
"""
|
||||
CAYENNE_APP: _PortNum.ValueType # 77
|
||||
"""
|
||||
App for transporting Cayenne Low Power Payload, popular for LoRaWAN sensor nodes. Offers ability to send
|
||||
arbitrary telemetry over meshtastic that is not covered by telemetry.proto
|
||||
ENCODING: CayenneLLP
|
||||
"""
|
||||
ATAK_PLUGIN_V2: _PortNum.ValueType # 78
|
||||
"""
|
||||
ATAK Plugin V2
|
||||
Portnum for payloads from the official Meshtastic ATAK plugin using
|
||||
TAKPacketV2 with zstd dictionary compression.
|
||||
"""
|
||||
GROUPALARM_APP: _PortNum.ValueType # 112
|
||||
"""
|
||||
GroupAlarm integration
|
||||
Used for transporting GroupAlarm-related messages between Meshtastic nodes
|
||||
and companion applications/services.
|
||||
"""
|
||||
PRIVATE_APP: _PortNum.ValueType # 256
|
||||
"""
|
||||
Private applications should use portnums >= 256.
|
||||
@@ -278,6 +332,18 @@ DETECTION_SENSOR_APP: PortNum.ValueType # 10
|
||||
Same as Text Message but originating from Detection Sensor Module.
|
||||
NOTE: This portnum traffic is not sent to the public MQTT starting at firmware version 2.2.9
|
||||
"""
|
||||
ALERT_APP: PortNum.ValueType # 11
|
||||
"""
|
||||
Same as Text Message but used for critical alerts.
|
||||
"""
|
||||
KEY_VERIFICATION_APP: PortNum.ValueType # 12
|
||||
"""
|
||||
Module/port for handling key verification requests.
|
||||
"""
|
||||
REMOTE_SHELL_APP: PortNum.ValueType # 13
|
||||
"""
|
||||
Module/port for handling primitive remote shell access.
|
||||
"""
|
||||
REPLY_APP: PortNum.ValueType # 32
|
||||
"""
|
||||
Provides a 'ping' service that replies to any packet it receives.
|
||||
@@ -294,6 +360,20 @@ PAXCOUNTER_APP: PortNum.ValueType # 34
|
||||
Paxcounter lib included in the firmware
|
||||
ENCODING: protobuf
|
||||
"""
|
||||
STORE_FORWARD_PLUSPLUS_APP: PortNum.ValueType # 35
|
||||
"""
|
||||
Store and Forward++ module included in the firmware
|
||||
ENCODING: protobuf
|
||||
This module is specifically for Native Linux nodes, and provides a Git-style
|
||||
chain of messages.
|
||||
"""
|
||||
NODE_STATUS_APP: PortNum.ValueType # 36
|
||||
"""
|
||||
Node Status module
|
||||
ENCODING: protobuf
|
||||
This module allows setting an extra string of status for a node.
|
||||
Broadcasts on change and on a timer, possibly once a day.
|
||||
"""
|
||||
SERIAL_APP: PortNum.ValueType # 64
|
||||
"""
|
||||
Provides a hardware serial interface to send and receive from the Meshtastic network.
|
||||
@@ -339,7 +419,7 @@ ENCODING: Protobuf (?)
|
||||
TRACEROUTE_APP: PortNum.ValueType # 70
|
||||
"""
|
||||
Provides a traceroute functionality to show the route a packet towards
|
||||
a certain destination would take on the mesh.
|
||||
a certain destination would take on the mesh. Contains a RouteDiscovery message as payload.
|
||||
ENCODING: Protobuf
|
||||
"""
|
||||
NEIGHBORINFO_APP: PortNum.ValueType # 71
|
||||
@@ -360,6 +440,34 @@ POWERSTRESS_APP: PortNum.ValueType # 74
|
||||
"""
|
||||
PowerStress based monitoring support (for automated power consumption testing)
|
||||
"""
|
||||
LORAWAN_BRIDGE: PortNum.ValueType # 75
|
||||
"""
|
||||
LoraWAN Payload Transport
|
||||
ENCODING: compact binary LoRaWAN uplink (10-byte RF metadata + PHY payload) - see LoRaWANBridgeModule
|
||||
"""
|
||||
RETICULUM_TUNNEL_APP: PortNum.ValueType # 76
|
||||
"""
|
||||
Reticulum Network Stack Tunnel App
|
||||
ENCODING: Fragmented RNS Packet. Handled by Meshtastic RNS interface
|
||||
"""
|
||||
CAYENNE_APP: PortNum.ValueType # 77
|
||||
"""
|
||||
App for transporting Cayenne Low Power Payload, popular for LoRaWAN sensor nodes. Offers ability to send
|
||||
arbitrary telemetry over meshtastic that is not covered by telemetry.proto
|
||||
ENCODING: CayenneLLP
|
||||
"""
|
||||
ATAK_PLUGIN_V2: PortNum.ValueType # 78
|
||||
"""
|
||||
ATAK Plugin V2
|
||||
Portnum for payloads from the official Meshtastic ATAK plugin using
|
||||
TAKPacketV2 with zstd dictionary compression.
|
||||
"""
|
||||
GROUPALARM_APP: PortNum.ValueType # 112
|
||||
"""
|
||||
GroupAlarm integration
|
||||
Used for transporting GroupAlarm-related messages between Meshtastic nodes
|
||||
and companion applications/services.
|
||||
"""
|
||||
PRIVATE_APP: PortNum.ValueType # 256
|
||||
"""
|
||||
Private applications should use portnums >= 256.
|
||||
|
||||
4
meshtastic/protobuf/powermon_pb2.py
generated
4
meshtastic/protobuf/powermon_pb2.py
generated
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/powermon.proto\x12\x13meshtastic.protobuf\"\xe0\x01\n\x08PowerMon\"\xd3\x01\n\x05State\x12\x08\n\x04None\x10\x00\x12\x11\n\rCPU_DeepSleep\x10\x01\x12\x12\n\x0e\x43PU_LightSleep\x10\x02\x12\x0c\n\x08Vext1_On\x10\x04\x12\r\n\tLora_RXOn\x10\x08\x12\r\n\tLora_TXOn\x10\x10\x12\x11\n\rLora_RXActive\x10 \x12\t\n\x05\x42T_On\x10@\x12\x0b\n\x06LED_On\x10\x80\x01\x12\x0e\n\tScreen_On\x10\x80\x02\x12\x13\n\x0eScreen_Drawing\x10\x80\x04\x12\x0c\n\x07Wifi_On\x10\x80\x08\x12\x0f\n\nGPS_Active\x10\x80\x10\"\x88\x03\n\x12PowerStressMessage\x12;\n\x03\x63md\x18\x01 \x01(\x0e\x32..meshtastic.protobuf.PowerStressMessage.Opcode\x12\x13\n\x0bnum_seconds\x18\x02 \x01(\x02\"\x9f\x02\n\x06Opcode\x12\t\n\x05UNSET\x10\x00\x12\x0e\n\nPRINT_INFO\x10\x01\x12\x0f\n\x0b\x46ORCE_QUIET\x10\x02\x12\r\n\tEND_QUIET\x10\x03\x12\r\n\tSCREEN_ON\x10\x10\x12\x0e\n\nSCREEN_OFF\x10\x11\x12\x0c\n\x08\x43PU_IDLE\x10 \x12\x11\n\rCPU_DEEPSLEEP\x10!\x12\x0e\n\nCPU_FULLON\x10\"\x12\n\n\x06LED_ON\x10\x30\x12\x0b\n\x07LED_OFF\x10\x31\x12\x0c\n\x08LORA_OFF\x10@\x12\x0b\n\x07LORA_TX\x10\x41\x12\x0b\n\x07LORA_RX\x10\x42\x12\n\n\x06\x42T_OFF\x10P\x12\t\n\x05\x42T_ON\x10Q\x12\x0c\n\x08WIFI_OFF\x10`\x12\x0b\n\x07WIFI_ON\x10\x61\x12\x0b\n\x07GPS_OFF\x10p\x12\n\n\x06GPS_ON\x10qBc\n\x13\x63om.geeksville.meshB\x0ePowerMonProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/powermon.proto\x12\x13meshtastic.protobuf\"\xe0\x01\n\x08PowerMon\"\xd3\x01\n\x05State\x12\x08\n\x04None\x10\x00\x12\x11\n\rCPU_DeepSleep\x10\x01\x12\x12\n\x0e\x43PU_LightSleep\x10\x02\x12\x0c\n\x08Vext1_On\x10\x04\x12\r\n\tLora_RXOn\x10\x08\x12\r\n\tLora_TXOn\x10\x10\x12\x11\n\rLora_RXActive\x10 \x12\t\n\x05\x42T_On\x10@\x12\x0b\n\x06LED_On\x10\x80\x01\x12\x0e\n\tScreen_On\x10\x80\x02\x12\x13\n\x0eScreen_Drawing\x10\x80\x04\x12\x0c\n\x07Wifi_On\x10\x80\x08\x12\x0f\n\nGPS_Active\x10\x80\x10\"\x88\x03\n\x12PowerStressMessage\x12;\n\x03\x63md\x18\x01 \x01(\x0e\x32..meshtastic.protobuf.PowerStressMessage.Opcode\x12\x13\n\x0bnum_seconds\x18\x02 \x01(\x02\"\x9f\x02\n\x06Opcode\x12\t\n\x05UNSET\x10\x00\x12\x0e\n\nPRINT_INFO\x10\x01\x12\x0f\n\x0b\x46ORCE_QUIET\x10\x02\x12\r\n\tEND_QUIET\x10\x03\x12\r\n\tSCREEN_ON\x10\x10\x12\x0e\n\nSCREEN_OFF\x10\x11\x12\x0c\n\x08\x43PU_IDLE\x10 \x12\x11\n\rCPU_DEEPSLEEP\x10!\x12\x0e\n\nCPU_FULLON\x10\"\x12\n\n\x06LED_ON\x10\x30\x12\x0b\n\x07LED_OFF\x10\x31\x12\x0c\n\x08LORA_OFF\x10@\x12\x0b\n\x07LORA_TX\x10\x41\x12\x0b\n\x07LORA_RX\x10\x42\x12\n\n\x06\x42T_OFF\x10P\x12\t\n\x05\x42T_ON\x10Q\x12\x0c\n\x08WIFI_OFF\x10`\x12\x0b\n\x07WIFI_ON\x10\x61\x12\x0b\n\x07GPS_OFF\x10p\x12\n\n\x06GPS_ON\x10qBd\n\x14org.meshtastic.protoB\x0ePowerMonProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.powermon_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\016PowerMonProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\016PowerMonProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_globals['_POWERMON']._serialized_start=60
|
||||
_globals['_POWERMON']._serialized_end=284
|
||||
_globals['_POWERMON_STATE']._serialized_start=73
|
||||
|
||||
4
meshtastic/protobuf/remote_hardware_pb2.py
generated
4
meshtastic/protobuf/remote_hardware_pb2.py
generated
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n)meshtastic/protobuf/remote_hardware.proto\x12\x13meshtastic.protobuf\"\xdf\x01\n\x0fHardwareMessage\x12\x37\n\x04type\x18\x01 \x01(\x0e\x32).meshtastic.protobuf.HardwareMessage.Type\x12\x11\n\tgpio_mask\x18\x02 \x01(\x04\x12\x12\n\ngpio_value\x18\x03 \x01(\x04\"l\n\x04Type\x12\t\n\x05UNSET\x10\x00\x12\x0f\n\x0bWRITE_GPIOS\x10\x01\x12\x0f\n\x0bWATCH_GPIOS\x10\x02\x12\x11\n\rGPIOS_CHANGED\x10\x03\x12\x0e\n\nREAD_GPIOS\x10\x04\x12\x14\n\x10READ_GPIOS_REPLY\x10\x05\x42\x63\n\x13\x63om.geeksville.meshB\x0eRemoteHardwareZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n)meshtastic/protobuf/remote_hardware.proto\x12\x13meshtastic.protobuf\"\xdf\x01\n\x0fHardwareMessage\x12\x37\n\x04type\x18\x01 \x01(\x0e\x32).meshtastic.protobuf.HardwareMessage.Type\x12\x11\n\tgpio_mask\x18\x02 \x01(\x04\x12\x12\n\ngpio_value\x18\x03 \x01(\x04\"l\n\x04Type\x12\t\n\x05UNSET\x10\x00\x12\x0f\n\x0bWRITE_GPIOS\x10\x01\x12\x0f\n\x0bWATCH_GPIOS\x10\x02\x12\x11\n\rGPIOS_CHANGED\x10\x03\x12\x0e\n\nREAD_GPIOS\x10\x04\x12\x14\n\x10READ_GPIOS_REPLY\x10\x05\x42\x64\n\x14org.meshtastic.protoB\x0eRemoteHardwareZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.remote_hardware_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\016RemoteHardwareZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\016RemoteHardwareZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_globals['_HARDWAREMESSAGE']._serialized_start=67
|
||||
_globals['_HARDWAREMESSAGE']._serialized_end=290
|
||||
_globals['_HARDWAREMESSAGE_TYPE']._serialized_start=182
|
||||
|
||||
11
meshtastic/protobuf/rtttl_pb2.py
generated
11
meshtastic/protobuf/rtttl_pb2.py
generated
@@ -11,16 +11,19 @@ from google.protobuf.internal import builder as _builder
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fmeshtastic/protobuf/rtttl.proto\x12\x13meshtastic.protobuf\"\x1f\n\x0bRTTTLConfig\x12\x10\n\x08ringtone\x18\x01 \x01(\tBf\n\x13\x63om.geeksville.meshB\x11RTTTLConfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fmeshtastic/protobuf/rtttl.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/nanopb.proto\"\'\n\x0bRTTTLConfig\x12\x18\n\x08ringtone\x18\x01 \x01(\tB\x06\x92?\x03\x08\xe7\x01\x42g\n\x14org.meshtastic.protoB\x11RTTTLConfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.rtttl_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\021RTTTLConfigProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_globals['_RTTTLCONFIG']._serialized_start=56
|
||||
_globals['_RTTTLCONFIG']._serialized_end=87
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\021RTTTLConfigProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_RTTTLCONFIG.fields_by_name['ringtone']._options = None
|
||||
_RTTTLCONFIG.fields_by_name['ringtone']._serialized_options = b'\222?\003\010\347\001'
|
||||
_globals['_RTTTLCONFIG']._serialized_start=90
|
||||
_globals['_RTTTLCONFIG']._serialized_end=129
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
39
meshtastic/protobuf/serial_hal_pb2.py
generated
Normal file
39
meshtastic/protobuf/serial_hal_pb2.py
generated
Normal file
@@ -0,0 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: meshtastic/protobuf/serial_hal.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/serial_hal.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/nanopb.proto\"\xb3\x02\n\x10SerialHalCommand\x12\x16\n\x0etransaction_id\x18\x01 \x01(\r\x12\x38\n\x04type\x18\x02 \x01(\x0e\x32*.meshtastic.protobuf.SerialHalCommand.Type\x12\x0b\n\x03pin\x18\x03 \x01(\r\x12\r\n\x05value\x18\x04 \x01(\r\x12\x0c\n\x04mode\x18\x05 \x01(\r\x12\x14\n\x04\x64\x61ta\x18\x06 \x01(\x0c\x42\x06\x92?\x03\x08\x80\x04\"\x8c\x01\n\x04Type\x12\t\n\x05UNSET\x10\x00\x12\x0c\n\x08PIN_MODE\x10\x01\x12\x11\n\rDIGITAL_WRITE\x10\x02\x12\x10\n\x0c\x44IGITAL_READ\x10\x03\x12\x14\n\x10\x41TTACH_INTERRUPT\x10\x04\x12\x14\n\x10\x44\x45TACH_INTERRUPT\x10\x05\x12\x10\n\x0cSPI_TRANSFER\x10\x06\x12\x08\n\x04NOOP\x10\x07\"\xe4\x01\n\x11SerialHalResponse\x12\x16\n\x0etransaction_id\x18\x01 \x01(\r\x12=\n\x06result\x18\x02 \x01(\x0e\x32-.meshtastic.protobuf.SerialHalResponse.Result\x12\r\n\x05value\x18\x03 \x01(\r\x12\x14\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x42\x06\x92?\x03\x08\x80\x04\x12\x14\n\x05\x65rror\x18\x05 \x01(\tB\x05\x92?\x02\x08P\"=\n\x06Result\x12\x06\n\x02OK\x10\x00\x12\t\n\x05\x45RROR\x10\x01\x12\x0f\n\x0b\x42\x41\x44_REQUEST\x10\x02\x12\x0f\n\x0bUNSUPPORTED\x10\x03\x42_\n\x14org.meshtastic.protoB\tSerialHalZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.serial_hal_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\tSerialHalZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_SERIALHALCOMMAND.fields_by_name['data']._options = None
|
||||
_SERIALHALCOMMAND.fields_by_name['data']._serialized_options = b'\222?\003\010\200\004'
|
||||
_SERIALHALRESPONSE.fields_by_name['data']._options = None
|
||||
_SERIALHALRESPONSE.fields_by_name['data']._serialized_options = b'\222?\003\010\200\004'
|
||||
_SERIALHALRESPONSE.fields_by_name['error']._options = None
|
||||
_SERIALHALRESPONSE.fields_by_name['error']._serialized_options = b'\222?\002\010P'
|
||||
_globals['_SERIALHALCOMMAND']._serialized_start=96
|
||||
_globals['_SERIALHALCOMMAND']._serialized_end=403
|
||||
_globals['_SERIALHALCOMMAND_TYPE']._serialized_start=263
|
||||
_globals['_SERIALHALCOMMAND_TYPE']._serialized_end=403
|
||||
_globals['_SERIALHALRESPONSE']._serialized_start=406
|
||||
_globals['_SERIALHALRESPONSE']._serialized_end=634
|
||||
_globals['_SERIALHALRESPONSE_RESULT']._serialized_start=573
|
||||
_globals['_SERIALHALRESPONSE_RESULT']._serialized_end=634
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
140
meshtastic/protobuf/serial_hal_pb2.pyi
generated
Normal file
140
meshtastic/protobuf/serial_hal_pb2.pyi
generated
Normal file
@@ -0,0 +1,140 @@
|
||||
"""
|
||||
@generated by mypy-protobuf. Do not edit manually!
|
||||
isort:skip_file
|
||||
"""
|
||||
|
||||
import builtins
|
||||
import google.protobuf.descriptor
|
||||
import google.protobuf.internal.enum_type_wrapper
|
||||
import google.protobuf.message
|
||||
import sys
|
||||
import typing
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
import typing as typing_extensions
|
||||
else:
|
||||
import typing_extensions
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
|
||||
|
||||
@typing.final
|
||||
class SerialHalCommand(google.protobuf.message.Message):
|
||||
"""SerialHalCommand messages are sent from host to device over the SerialHal
|
||||
framing stream. Responses normally come back as SerialHalResponse with the
|
||||
same transaction_id.
|
||||
|
||||
Interrupt notifications are the one asynchronous exception: the device emits
|
||||
an unsolicited SerialHalResponse with transaction_id == 0 and value == pin.
|
||||
Host implementations should treat those frames as interrupt events rather
|
||||
than replies to an outstanding request.
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
class _Type:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _TypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[SerialHalCommand._Type.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
UNSET: SerialHalCommand._Type.ValueType # 0
|
||||
PIN_MODE: SerialHalCommand._Type.ValueType # 1
|
||||
DIGITAL_WRITE: SerialHalCommand._Type.ValueType # 2
|
||||
DIGITAL_READ: SerialHalCommand._Type.ValueType # 3
|
||||
ATTACH_INTERRUPT: SerialHalCommand._Type.ValueType # 4
|
||||
DETACH_INTERRUPT: SerialHalCommand._Type.ValueType # 5
|
||||
SPI_TRANSFER: SerialHalCommand._Type.ValueType # 6
|
||||
NOOP: SerialHalCommand._Type.ValueType # 7
|
||||
|
||||
class Type(_Type, metaclass=_TypeEnumTypeWrapper): ...
|
||||
UNSET: SerialHalCommand.Type.ValueType # 0
|
||||
PIN_MODE: SerialHalCommand.Type.ValueType # 1
|
||||
DIGITAL_WRITE: SerialHalCommand.Type.ValueType # 2
|
||||
DIGITAL_READ: SerialHalCommand.Type.ValueType # 3
|
||||
ATTACH_INTERRUPT: SerialHalCommand.Type.ValueType # 4
|
||||
DETACH_INTERRUPT: SerialHalCommand.Type.ValueType # 5
|
||||
SPI_TRANSFER: SerialHalCommand.Type.ValueType # 6
|
||||
NOOP: SerialHalCommand.Type.ValueType # 7
|
||||
|
||||
TRANSACTION_ID_FIELD_NUMBER: builtins.int
|
||||
TYPE_FIELD_NUMBER: builtins.int
|
||||
PIN_FIELD_NUMBER: builtins.int
|
||||
VALUE_FIELD_NUMBER: builtins.int
|
||||
MODE_FIELD_NUMBER: builtins.int
|
||||
DATA_FIELD_NUMBER: builtins.int
|
||||
transaction_id: builtins.int
|
||||
"""Host-assigned request id. Replies echo this id back in
|
||||
SerialHalResponse.transaction_id.
|
||||
"""
|
||||
type: global___SerialHalCommand.Type.ValueType
|
||||
pin: builtins.int
|
||||
value: builtins.int
|
||||
mode: builtins.int
|
||||
data: builtins.bytes
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
transaction_id: builtins.int = ...,
|
||||
type: global___SerialHalCommand.Type.ValueType = ...,
|
||||
pin: builtins.int = ...,
|
||||
value: builtins.int = ...,
|
||||
mode: builtins.int = ...,
|
||||
data: builtins.bytes = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["data", b"data", "mode", b"mode", "pin", b"pin", "transaction_id", b"transaction_id", "type", b"type", "value", b"value"]) -> None: ...
|
||||
|
||||
global___SerialHalCommand = SerialHalCommand
|
||||
|
||||
@typing.final
|
||||
class SerialHalResponse(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
class _Result:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _ResultEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[SerialHalResponse._Result.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
OK: SerialHalResponse._Result.ValueType # 0
|
||||
ERROR: SerialHalResponse._Result.ValueType # 1
|
||||
BAD_REQUEST: SerialHalResponse._Result.ValueType # 2
|
||||
UNSUPPORTED: SerialHalResponse._Result.ValueType # 3
|
||||
|
||||
class Result(_Result, metaclass=_ResultEnumTypeWrapper): ...
|
||||
OK: SerialHalResponse.Result.ValueType # 0
|
||||
ERROR: SerialHalResponse.Result.ValueType # 1
|
||||
BAD_REQUEST: SerialHalResponse.Result.ValueType # 2
|
||||
UNSUPPORTED: SerialHalResponse.Result.ValueType # 3
|
||||
|
||||
TRANSACTION_ID_FIELD_NUMBER: builtins.int
|
||||
RESULT_FIELD_NUMBER: builtins.int
|
||||
VALUE_FIELD_NUMBER: builtins.int
|
||||
DATA_FIELD_NUMBER: builtins.int
|
||||
ERROR_FIELD_NUMBER: builtins.int
|
||||
transaction_id: builtins.int
|
||||
"""Matches the originating SerialHalCommand.transaction_id for normal
|
||||
request/response traffic.
|
||||
|
||||
A value of 0 indicates an unsolicited interrupt notification generated by
|
||||
the device. In that case, the host should interpret value as the GPIO pin
|
||||
that triggered.
|
||||
"""
|
||||
result: global___SerialHalResponse.Result.ValueType
|
||||
value: builtins.int
|
||||
"""Used by DIGITAL_READ replies and interrupt notifications. For interrupt
|
||||
notifications (transaction_id == 0), this carries the pin number.
|
||||
"""
|
||||
data: builtins.bytes
|
||||
error: builtins.str
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
transaction_id: builtins.int = ...,
|
||||
result: global___SerialHalResponse.Result.ValueType = ...,
|
||||
value: builtins.int = ...,
|
||||
data: builtins.bytes = ...,
|
||||
error: builtins.str = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["data", b"data", "error", b"error", "result", b"result", "transaction_id", b"transaction_id", "value", b"value"]) -> None: ...
|
||||
|
||||
global___SerialHalResponse = SerialHalResponse
|
||||
27
meshtastic/protobuf/storeforward_pb2.py
generated
27
meshtastic/protobuf/storeforward_pb2.py
generated
@@ -11,24 +11,27 @@ from google.protobuf.internal import builder as _builder
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n&meshtastic/protobuf/storeforward.proto\x12\x13meshtastic.protobuf\"\xc0\x07\n\x0fStoreAndForward\x12@\n\x02rr\x18\x01 \x01(\x0e\x32\x34.meshtastic.protobuf.StoreAndForward.RequestResponse\x12@\n\x05stats\x18\x02 \x01(\x0b\x32/.meshtastic.protobuf.StoreAndForward.StatisticsH\x00\x12?\n\x07history\x18\x03 \x01(\x0b\x32,.meshtastic.protobuf.StoreAndForward.HistoryH\x00\x12\x43\n\theartbeat\x18\x04 \x01(\x0b\x32..meshtastic.protobuf.StoreAndForward.HeartbeatH\x00\x12\x0e\n\x04text\x18\x05 \x01(\x0cH\x00\x1a\xcd\x01\n\nStatistics\x12\x16\n\x0emessages_total\x18\x01 \x01(\r\x12\x16\n\x0emessages_saved\x18\x02 \x01(\r\x12\x14\n\x0cmessages_max\x18\x03 \x01(\r\x12\x0f\n\x07up_time\x18\x04 \x01(\r\x12\x10\n\x08requests\x18\x05 \x01(\r\x12\x18\n\x10requests_history\x18\x06 \x01(\r\x12\x11\n\theartbeat\x18\x07 \x01(\x08\x12\x12\n\nreturn_max\x18\x08 \x01(\r\x12\x15\n\rreturn_window\x18\t \x01(\r\x1aI\n\x07History\x12\x18\n\x10history_messages\x18\x01 \x01(\r\x12\x0e\n\x06window\x18\x02 \x01(\r\x12\x14\n\x0clast_request\x18\x03 \x01(\r\x1a.\n\tHeartbeat\x12\x0e\n\x06period\x18\x01 \x01(\r\x12\x11\n\tsecondary\x18\x02 \x01(\r\"\xbc\x02\n\x0fRequestResponse\x12\t\n\x05UNSET\x10\x00\x12\x10\n\x0cROUTER_ERROR\x10\x01\x12\x14\n\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n\x0bROUTER_PING\x10\x03\x12\x0f\n\x0bROUTER_PONG\x10\x04\x12\x0f\n\x0bROUTER_BUSY\x10\x05\x12\x12\n\x0eROUTER_HISTORY\x10\x06\x12\x10\n\x0cROUTER_STATS\x10\x07\x12\x16\n\x12ROUTER_TEXT_DIRECT\x10\x08\x12\x19\n\x15ROUTER_TEXT_BROADCAST\x10\t\x12\x10\n\x0c\x43LIENT_ERROR\x10@\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x41\x12\x10\n\x0c\x43LIENT_STATS\x10\x42\x12\x0f\n\x0b\x43LIENT_PING\x10\x43\x12\x0f\n\x0b\x43LIENT_PONG\x10\x44\x12\x10\n\x0c\x43LIENT_ABORT\x10jB\t\n\x07variantBj\n\x13\x63om.geeksville.meshB\x15StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n&meshtastic/protobuf/storeforward.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/nanopb.proto\"\xc8\x07\n\x0fStoreAndForward\x12@\n\x02rr\x18\x01 \x01(\x0e\x32\x34.meshtastic.protobuf.StoreAndForward.RequestResponse\x12@\n\x05stats\x18\x02 \x01(\x0b\x32/.meshtastic.protobuf.StoreAndForward.StatisticsH\x00\x12?\n\x07history\x18\x03 \x01(\x0b\x32,.meshtastic.protobuf.StoreAndForward.HistoryH\x00\x12\x43\n\theartbeat\x18\x04 \x01(\x0b\x32..meshtastic.protobuf.StoreAndForward.HeartbeatH\x00\x12\x16\n\x04text\x18\x05 \x01(\x0c\x42\x06\x92?\x03\x08\xe9\x01H\x00\x1a\xcd\x01\n\nStatistics\x12\x16\n\x0emessages_total\x18\x01 \x01(\r\x12\x16\n\x0emessages_saved\x18\x02 \x01(\r\x12\x14\n\x0cmessages_max\x18\x03 \x01(\r\x12\x0f\n\x07up_time\x18\x04 \x01(\r\x12\x10\n\x08requests\x18\x05 \x01(\r\x12\x18\n\x10requests_history\x18\x06 \x01(\r\x12\x11\n\theartbeat\x18\x07 \x01(\x08\x12\x12\n\nreturn_max\x18\x08 \x01(\r\x12\x15\n\rreturn_window\x18\t \x01(\r\x1aI\n\x07History\x12\x18\n\x10history_messages\x18\x01 \x01(\r\x12\x0e\n\x06window\x18\x02 \x01(\r\x12\x14\n\x0clast_request\x18\x03 \x01(\r\x1a.\n\tHeartbeat\x12\x0e\n\x06period\x18\x01 \x01(\r\x12\x11\n\tsecondary\x18\x02 \x01(\r\"\xbc\x02\n\x0fRequestResponse\x12\t\n\x05UNSET\x10\x00\x12\x10\n\x0cROUTER_ERROR\x10\x01\x12\x14\n\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n\x0bROUTER_PING\x10\x03\x12\x0f\n\x0bROUTER_PONG\x10\x04\x12\x0f\n\x0bROUTER_BUSY\x10\x05\x12\x12\n\x0eROUTER_HISTORY\x10\x06\x12\x10\n\x0cROUTER_STATS\x10\x07\x12\x16\n\x12ROUTER_TEXT_DIRECT\x10\x08\x12\x19\n\x15ROUTER_TEXT_BROADCAST\x10\t\x12\x10\n\x0c\x43LIENT_ERROR\x10@\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x41\x12\x10\n\x0c\x43LIENT_STATS\x10\x42\x12\x0f\n\x0b\x43LIENT_PING\x10\x43\x12\x0f\n\x0b\x43LIENT_PONG\x10\x44\x12\x10\n\x0c\x43LIENT_ABORT\x10jB\t\n\x07variantBk\n\x14org.meshtastic.protoB\x15StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.storeforward_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\025StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_globals['_STOREANDFORWARD']._serialized_start=64
|
||||
_globals['_STOREANDFORWARD']._serialized_end=1024
|
||||
_globals['_STOREANDFORWARD_STATISTICS']._serialized_start=366
|
||||
_globals['_STOREANDFORWARD_STATISTICS']._serialized_end=571
|
||||
_globals['_STOREANDFORWARD_HISTORY']._serialized_start=573
|
||||
_globals['_STOREANDFORWARD_HISTORY']._serialized_end=646
|
||||
_globals['_STOREANDFORWARD_HEARTBEAT']._serialized_start=648
|
||||
_globals['_STOREANDFORWARD_HEARTBEAT']._serialized_end=694
|
||||
_globals['_STOREANDFORWARD_REQUESTRESPONSE']._serialized_start=697
|
||||
_globals['_STOREANDFORWARD_REQUESTRESPONSE']._serialized_end=1013
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\025StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_STOREANDFORWARD.fields_by_name['text']._options = None
|
||||
_STOREANDFORWARD.fields_by_name['text']._serialized_options = b'\222?\003\010\351\001'
|
||||
_globals['_STOREANDFORWARD']._serialized_start=98
|
||||
_globals['_STOREANDFORWARD']._serialized_end=1066
|
||||
_globals['_STOREANDFORWARD_STATISTICS']._serialized_start=408
|
||||
_globals['_STOREANDFORWARD_STATISTICS']._serialized_end=613
|
||||
_globals['_STOREANDFORWARD_HISTORY']._serialized_start=615
|
||||
_globals['_STOREANDFORWARD_HISTORY']._serialized_end=688
|
||||
_globals['_STOREANDFORWARD_HEARTBEAT']._serialized_start=690
|
||||
_globals['_STOREANDFORWARD_HEARTBEAT']._serialized_end=736
|
||||
_globals['_STOREANDFORWARD_REQUESTRESPONSE']._serialized_start=739
|
||||
_globals['_STOREANDFORWARD_REQUESTRESPONSE']._serialized_end=1055
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
69
meshtastic/protobuf/telemetry_pb2.py
generated
69
meshtastic/protobuf/telemetry_pb2.py
generated
File diff suppressed because one or more lines are too long
1024
meshtastic/protobuf/telemetry_pb2.pyi
generated
1024
meshtastic/protobuf/telemetry_pb2.pyi
generated
File diff suppressed because it is too large
Load Diff
19
meshtastic/protobuf/xmodem_pb2.py
generated
19
meshtastic/protobuf/xmodem_pb2.py
generated
@@ -11,18 +11,25 @@ from google.protobuf.internal import builder as _builder
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n meshtastic/protobuf/xmodem.proto\x12\x13meshtastic.protobuf\"\xbf\x01\n\x06XModem\x12\x34\n\x07\x63ontrol\x18\x01 \x01(\x0e\x32#.meshtastic.protobuf.XModem.Control\x12\x0b\n\x03seq\x18\x02 \x01(\r\x12\r\n\x05\x63rc16\x18\x03 \x01(\r\x12\x0e\n\x06\x62uffer\x18\x04 \x01(\x0c\"S\n\x07\x43ontrol\x12\x07\n\x03NUL\x10\x00\x12\x07\n\x03SOH\x10\x01\x12\x07\n\x03STX\x10\x02\x12\x07\n\x03\x45OT\x10\x04\x12\x07\n\x03\x41\x43K\x10\x06\x12\x07\n\x03NAK\x10\x15\x12\x07\n\x03\x43\x41N\x10\x18\x12\t\n\x05\x43TRLZ\x10\x1a\x42\x61\n\x13\x63om.geeksville.meshB\x0cXmodemProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n meshtastic/protobuf/xmodem.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/nanopb.proto\"\xd5\x01\n\x06XModem\x12\x34\n\x07\x63ontrol\x18\x01 \x01(\x0e\x32#.meshtastic.protobuf.XModem.Control\x12\x12\n\x03seq\x18\x02 \x01(\rB\x05\x92?\x02\x38\x10\x12\x14\n\x05\x63rc16\x18\x03 \x01(\rB\x05\x92?\x02\x38\x10\x12\x16\n\x06\x62uffer\x18\x04 \x01(\x0c\x42\x06\x92?\x03\x08\x80\x01\"S\n\x07\x43ontrol\x12\x07\n\x03NUL\x10\x00\x12\x07\n\x03SOH\x10\x01\x12\x07\n\x03STX\x10\x02\x12\x07\n\x03\x45OT\x10\x04\x12\x07\n\x03\x41\x43K\x10\x06\x12\x07\n\x03NAK\x10\x15\x12\x07\n\x03\x43\x41N\x10\x18\x12\t\n\x05\x43TRLZ\x10\x1a\x42\x62\n\x14org.meshtastic.protoB\x0cXmodemProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.xmodem_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\014XmodemProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_globals['_XMODEM']._serialized_start=58
|
||||
_globals['_XMODEM']._serialized_end=249
|
||||
_globals['_XMODEM_CONTROL']._serialized_start=166
|
||||
_globals['_XMODEM_CONTROL']._serialized_end=249
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\014XmodemProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_XMODEM.fields_by_name['seq']._options = None
|
||||
_XMODEM.fields_by_name['seq']._serialized_options = b'\222?\0028\020'
|
||||
_XMODEM.fields_by_name['crc16']._options = None
|
||||
_XMODEM.fields_by_name['crc16']._serialized_options = b'\222?\0028\020'
|
||||
_XMODEM.fields_by_name['buffer']._options = None
|
||||
_XMODEM.fields_by_name['buffer']._serialized_options = b'\222?\003\010\200\001'
|
||||
_globals['_XMODEM']._serialized_start=92
|
||||
_globals['_XMODEM']._serialized_end=305
|
||||
_globals['_XMODEM_CONTROL']._serialized_start=222
|
||||
_globals['_XMODEM_CONTROL']._serialized_end=305
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
@@ -7,10 +7,11 @@ from pubsub import pub # type: ignore[import-untyped]
|
||||
from meshtastic.protobuf import portnums_pb2, remote_hardware_pb2
|
||||
from meshtastic.util import our_exit
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def onGPIOreceive(packet, interface):
|
||||
def onGPIOreceive(packet, interface) -> None:
|
||||
"""Callback for received GPIO responses"""
|
||||
logging.debug(f"packet:{packet} interface:{interface}")
|
||||
logger.debug(f"packet:{packet} interface:{interface}")
|
||||
gpioValue = 0
|
||||
hw = packet["decoded"]["remotehw"]
|
||||
if "gpioValue" in hw:
|
||||
@@ -37,7 +38,7 @@ class RemoteHardwareClient:
|
||||
code for how you can connect to your own custom meshtastic services
|
||||
"""
|
||||
|
||||
def __init__(self, iface):
|
||||
def __init__(self, iface) -> None:
|
||||
"""
|
||||
Constructor
|
||||
|
||||
@@ -76,7 +77,7 @@ class RemoteHardwareClient:
|
||||
Write the specified vals bits to the device GPIOs. Only bits in mask that
|
||||
are 1 will be changed
|
||||
"""
|
||||
logging.debug(f"writeGPIOs nodeid:{nodeid} mask:{mask} vals:{vals}")
|
||||
logger.debug(f"writeGPIOs nodeid:{nodeid} mask:{mask} vals:{vals}")
|
||||
r = remote_hardware_pb2.HardwareMessage()
|
||||
r.type = remote_hardware_pb2.HardwareMessage.Type.WRITE_GPIOS
|
||||
r.gpio_mask = mask
|
||||
@@ -85,7 +86,7 @@ class RemoteHardwareClient:
|
||||
|
||||
def readGPIOs(self, nodeid, mask, onResponse=None):
|
||||
"""Read the specified bits from GPIO inputs on the device"""
|
||||
logging.debug(f"readGPIOs nodeid:{nodeid} mask:{mask}")
|
||||
logger.debug(f"readGPIOs nodeid:{nodeid} mask:{mask}")
|
||||
r = remote_hardware_pb2.HardwareMessage()
|
||||
r.type = remote_hardware_pb2.HardwareMessage.Type.READ_GPIOS
|
||||
r.gpio_mask = mask
|
||||
@@ -93,7 +94,7 @@ class RemoteHardwareClient:
|
||||
|
||||
def watchGPIOs(self, nodeid, mask):
|
||||
"""Watch the specified bits from GPIO inputs on the device for changes"""
|
||||
logging.debug(f"watchGPIOs nodeid:{nodeid} mask:{mask}")
|
||||
logger.debug(f"watchGPIOs nodeid:{nodeid} mask:{mask}")
|
||||
r = remote_hardware_pb2.HardwareMessage()
|
||||
r.type = remote_hardware_pb2.HardwareMessage.Type.WATCH_GPIOS
|
||||
r.gpio_mask = mask
|
||||
|
||||
@@ -1,76 +1,107 @@
|
||||
""" Serial interface class
|
||||
"""
|
||||
# pylint: disable=R0917
|
||||
import logging
|
||||
import platform
|
||||
import sys
|
||||
import time
|
||||
from io import TextIOWrapper
|
||||
|
||||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
|
||||
import serial # type: ignore[import-untyped]
|
||||
|
||||
import meshtastic.util
|
||||
from meshtastic.stream_interface import StreamInterface
|
||||
|
||||
if platform.system() != "Windows":
|
||||
import termios
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class SerialInterface(StreamInterface):
|
||||
"""Interface class for meshtastic devices over a serial link"""
|
||||
|
||||
def __init__(self, devPath: Optional[str]=None, debugOut=None, noProto=False, connectNow=True, noNodes: bool=False):
|
||||
def __init__(
|
||||
self,
|
||||
devPath: Optional[str] = None,
|
||||
debugOut=None,
|
||||
noProto: bool = False,
|
||||
connectNow: bool = True,
|
||||
noNodes: bool = False,
|
||||
timeout: int = 300
|
||||
) -> None:
|
||||
"""Constructor, opens a connection to a specified serial port, or if unspecified try to
|
||||
find one Meshtastic device by probing
|
||||
|
||||
Keyword Arguments:
|
||||
devPath {string} -- A filepath to a device, i.e. /dev/ttyUSB0 (default: {None})
|
||||
debugOut {stream} -- If a stream is provided, any debug serial output from the device will be emitted to that stream. (default: {None})
|
||||
timeout -- How long to wait for replies (default: 300 seconds)
|
||||
"""
|
||||
self.noProto = noProto
|
||||
|
||||
self.devPath: Optional[str] = devPath
|
||||
|
||||
if self.devPath is None:
|
||||
ports = meshtastic.util.findPorts(True)
|
||||
logging.debug(f"ports:{ports}")
|
||||
ports: List[str] = meshtastic.util.findPorts(True)
|
||||
logger.debug(f"ports:{ports}")
|
||||
if len(ports) == 0:
|
||||
print("No Serial Meshtastic device detected, attempting TCP connection on localhost.")
|
||||
return
|
||||
elif len(ports) > 1:
|
||||
message = "Warning: Multiple serial ports were detected so one serial port must be specified with the '--port'.\n"
|
||||
message: str = "Warning: Multiple serial ports were detected so one serial port must be specified with the '--port'.\n"
|
||||
message += f" Ports detected:{ports}"
|
||||
meshtastic.util.our_exit(message)
|
||||
else:
|
||||
self.devPath = ports[0]
|
||||
|
||||
logging.debug(f"Connecting to {self.devPath}")
|
||||
StreamInterface.__init__(
|
||||
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes, timeout=timeout
|
||||
)
|
||||
|
||||
# first we need to set the HUPCL so the device will not reboot based on RTS and/or DTR
|
||||
# see https://github.com/pyserial/pyserial/issues/124
|
||||
if platform.system() != "Windows":
|
||||
with open(self.devPath, encoding="utf8") as f:
|
||||
attrs = termios.tcgetattr(f)
|
||||
attrs[2] = attrs[2] & ~termios.HUPCL
|
||||
termios.tcsetattr(f, termios.TCSAFLUSH, attrs)
|
||||
f.close()
|
||||
def connect(self) -> None:
|
||||
logger.debug(f"Connecting to {self.devPath}")
|
||||
dev_path = self.devPath
|
||||
if dev_path is None:
|
||||
raise RuntimeError("Serial device path is not set")
|
||||
|
||||
if sys.platform != "win32":
|
||||
with open(dev_path, encoding="utf8") as f:
|
||||
self._set_hupcl_with_termios(f)
|
||||
time.sleep(0.1)
|
||||
|
||||
self.stream = serial.Serial(
|
||||
self.devPath, 115200, exclusive=True, timeout=0.5, write_timeout=0
|
||||
dev_path, 115200, exclusive=True, timeout=0.5, write_timeout=0
|
||||
)
|
||||
self.stream.flush()
|
||||
self.stream.flush() # type: ignore[attr-defined]
|
||||
time.sleep(0.1)
|
||||
|
||||
StreamInterface.__init__(
|
||||
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes
|
||||
)
|
||||
super().connect()
|
||||
|
||||
def close(self):
|
||||
def _set_hupcl_with_termios(self, f: TextIOWrapper):
|
||||
"""first we need to set the HUPCL so the device will not reboot based on RTS and/or DTR
|
||||
see https://github.com/pyserial/pyserial/issues/124
|
||||
"""
|
||||
if sys.platform == "win32":
|
||||
return
|
||||
|
||||
import termios # pylint: disable=C0415,E0401
|
||||
attrs = termios.tcgetattr(f)
|
||||
attrs[2] = attrs[2] & ~termios.HUPCL
|
||||
termios.tcsetattr(f, termios.TCSAFLUSH, attrs)
|
||||
|
||||
def __repr__(self):
|
||||
rep = f"SerialInterface(devPath={self.devPath!r}"
|
||||
if hasattr(self, 'debugOut') and self.debugOut is not None:
|
||||
rep += f", debugOut={self.debugOut!r}"
|
||||
if self.noProto:
|
||||
rep += ", noProto=True"
|
||||
if hasattr(self, 'noNodes') and self.noNodes:
|
||||
rep += ", noNodes=True"
|
||||
rep += ")"
|
||||
return rep
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close a connection to the device"""
|
||||
if self.stream: # Stream can be null if we were already closed
|
||||
self.stream.flush() # FIXME: why are there these two flushes with 100ms sleeps? This shouldn't be necessary
|
||||
time.sleep(0.1)
|
||||
self.stream.flush()
|
||||
time.sleep(0.1)
|
||||
logging.debug("Closing Serial stream")
|
||||
logger.debug("Closing Serial stream")
|
||||
StreamInterface.close(self)
|
||||
|
||||
@@ -10,6 +10,7 @@ import time
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from functools import reduce
|
||||
from pathlib import Path
|
||||
from typing import Optional, List, Tuple
|
||||
|
||||
import parse # type: ignore[import-untyped]
|
||||
@@ -22,6 +23,7 @@ from meshtastic.powermon import PowerMeter
|
||||
|
||||
from .arrow import FeatherWriter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def root_dir() -> str:
|
||||
"""Return the root directory for slog files."""
|
||||
@@ -29,9 +31,9 @@ def root_dir() -> str:
|
||||
app_name = "meshtastic"
|
||||
app_author = "meshtastic"
|
||||
app_dir = platformdirs.user_data_dir(app_name, app_author)
|
||||
dir_name = f"{app_dir}/slogs"
|
||||
os.makedirs(dir_name, exist_ok=True)
|
||||
return dir_name
|
||||
dir_name = Path(app_dir, "slogs")
|
||||
dir_name.mkdir(exist_ok=True, parents=True)
|
||||
return str(dir_name)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
@@ -144,7 +146,7 @@ class StructuredLogger:
|
||||
self.power_logger = power_logger
|
||||
|
||||
# Setup the arrow writer (and its schema)
|
||||
self.writer = FeatherWriter(f"{dir_path}/slog")
|
||||
self.writer = FeatherWriter(os.path.join(dir_path, "slog"))
|
||||
all_fields = reduce(
|
||||
(lambda x, y: x + y), map(lambda x: x.fields, log_defs.values())
|
||||
)
|
||||
@@ -164,7 +166,7 @@ class StructuredLogger:
|
||||
self.raw_file: Optional[
|
||||
io.TextIOWrapper
|
||||
] = open( # pylint: disable=consider-using-with
|
||||
f"{dir_path}/raw.txt", "w", encoding="utf8"
|
||||
os.path.join(dir_path, "raw.txt"), "w", encoding="utf8"
|
||||
)
|
||||
|
||||
# We need a closure here because the subscription API is very strict about exact arg matching
|
||||
@@ -197,7 +199,7 @@ class StructuredLogger:
|
||||
if m:
|
||||
src = m.group(1)
|
||||
args = m.group(2)
|
||||
logging.debug(f"SLog {src}, args: {args}")
|
||||
logger.debug(f"SLog {src}, args: {args}")
|
||||
|
||||
d = log_defs.get(src)
|
||||
if d:
|
||||
@@ -219,9 +221,9 @@ class StructuredLogger:
|
||||
# If the last field is an empty string, remove it
|
||||
del di[last_field[0]]
|
||||
else:
|
||||
logging.warning(f"Failed to parse slog {line} with {d.format}")
|
||||
logger.warning(f"Failed to parse slog {line} with {d.format}")
|
||||
else:
|
||||
logging.warning(f"Unknown Structured Log: {line}")
|
||||
logger.warning(f"Unknown Structured Log: {line}")
|
||||
|
||||
# Store our structured log record
|
||||
if di or self.include_raw:
|
||||
@@ -256,23 +258,28 @@ class LogSet:
|
||||
|
||||
if not dir_name:
|
||||
app_dir = root_dir()
|
||||
dir_name = f"{app_dir}/{datetime.now().strftime('%Y%m%d-%H%M%S')}"
|
||||
os.makedirs(dir_name, exist_ok=True)
|
||||
app_time_dir = Path(app_dir, datetime.now().strftime('%Y%m%d-%H%M%S'))
|
||||
app_time_dir.mkdir(exist_ok=True)
|
||||
dir_name = str(app_time_dir)
|
||||
|
||||
# Also make a 'latest' directory that always points to the most recent logs
|
||||
latest_dir = Path(app_dir, "latest")
|
||||
latest_dir.unlink(missing_ok=True)
|
||||
|
||||
# symlink might fail on some platforms, if it does fail silently
|
||||
if os.path.exists(f"{app_dir}/latest"):
|
||||
os.unlink(f"{app_dir}/latest")
|
||||
os.symlink(dir_name, f"{app_dir}/latest", target_is_directory=True)
|
||||
try:
|
||||
latest_dir.symlink_to(dir_name, target_is_directory=True)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
self.dir_name = dir_name
|
||||
|
||||
logging.info(f"Writing slogs to {dir_name}")
|
||||
logger.info(f"Writing slogs to {dir_name}")
|
||||
|
||||
self.power_logger: Optional[PowerLogger] = (
|
||||
None
|
||||
if not power_meter
|
||||
else PowerLogger(power_meter, f"{self.dir_name}/power")
|
||||
else PowerLogger(power_meter, os.path.join(self.dir_name, "power"))
|
||||
)
|
||||
|
||||
self.slog_logger: Optional[StructuredLogger] = StructuredLogger(
|
||||
@@ -286,7 +293,7 @@ class LogSet:
|
||||
"""Close the log set."""
|
||||
|
||||
if self.slog_logger:
|
||||
logging.info(f"Closing slogs in {self.dir_name}")
|
||||
logger.info(f"Closing slogs in {self.dir_name}")
|
||||
atexit.unregister(
|
||||
self.atexit_handler
|
||||
) # docs say it will silently ignore if not found
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
"""Stream Interface base class
|
||||
"""
|
||||
import contextlib
|
||||
import io
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from typing import Optional, cast
|
||||
|
||||
import serial # type: ignore[import-untyped]
|
||||
|
||||
from meshtastic.mesh_interface import MeshInterface
|
||||
@@ -14,27 +18,36 @@ START1 = 0x94
|
||||
START2 = 0xC3
|
||||
HEADER_LEN = 4
|
||||
MAX_TO_FROM_RADIO_SIZE = 512
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StreamInterface(MeshInterface):
|
||||
"""Interface class for meshtastic devices over a stream link (serial, TCP, etc)"""
|
||||
|
||||
def __init__(self, debugOut=None, noProto=False, connectNow=True, noNodes=False):
|
||||
def __init__( # pylint: disable=R0917
|
||||
self,
|
||||
debugOut: Optional[io.TextIOWrapper] = None,
|
||||
noProto: bool = False,
|
||||
connectNow: bool = True,
|
||||
noNodes: bool = False,
|
||||
timeout: int = 300
|
||||
) -> None:
|
||||
"""Constructor, opens a connection to self.stream
|
||||
|
||||
Keyword Arguments:
|
||||
debugOut {stream} -- If a stream is provided, any debug serial output from the
|
||||
device will be emitted to that stream. (default: {None})
|
||||
timeout -- How long to wait for replies (default: 300 seconds)
|
||||
|
||||
Raises:
|
||||
Exception: [description]
|
||||
Exception: [description]
|
||||
RuntimeError: Raised if StreamInterface is instantiated when noProto is false.
|
||||
"""
|
||||
|
||||
if not hasattr(self, "stream") and not noProto:
|
||||
raise Exception( # pylint: disable=W0719
|
||||
if not noProto and type(self) == StreamInterface: # pylint: disable=C0123
|
||||
raise RuntimeError(
|
||||
"StreamInterface is now abstract (to update existing code create SerialInterface instead)"
|
||||
)
|
||||
self.stream: Optional[serial.Serial] = None # only serial uses this, TCPInterface overrides the relevant methods instead
|
||||
self._rxBuf = bytes() # empty
|
||||
self._wantExit = False
|
||||
|
||||
@@ -44,15 +57,23 @@ class StreamInterface(MeshInterface):
|
||||
# FIXME, figure out why daemon=True causes reader thread to exit too early
|
||||
self._rxThread = threading.Thread(target=self.__reader, args=(), daemon=True, name="stream reader")
|
||||
|
||||
MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto, noNodes=noNodes)
|
||||
MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto, noNodes=noNodes, timeout=timeout)
|
||||
|
||||
# Start the reader thread after superclass constructor completes init
|
||||
if connectNow:
|
||||
self.connect()
|
||||
if not noProto:
|
||||
self.waitForConfig()
|
||||
try:
|
||||
self.connect()
|
||||
if not noProto:
|
||||
self.waitForConfig()
|
||||
except Exception:
|
||||
# If the handshake raises, the caller never receives a reference
|
||||
# to this instance and cannot call close() themselves. Clean up
|
||||
# the reader thread + stream here so retries don't leak.
|
||||
with contextlib.suppress(Exception):
|
||||
self.close()
|
||||
raise
|
||||
|
||||
def connect(self):
|
||||
def connect(self) -> None:
|
||||
"""Connect to our radio
|
||||
|
||||
Normally this is called automatically by the constructor, but if you
|
||||
@@ -63,7 +84,7 @@ class StreamInterface(MeshInterface):
|
||||
# if the reading statemachine was parsing a bad packet make sure
|
||||
# we write enough start bytes to force it to resync (we don't use START1
|
||||
# because we want to ensure it is looking for START1)
|
||||
p = bytearray([START2] * 32)
|
||||
p: bytes = bytearray([START2] * 32)
|
||||
self._writeBytes(p)
|
||||
time.sleep(0.1) # wait 100ms to give device time to start running
|
||||
|
||||
@@ -74,11 +95,11 @@ class StreamInterface(MeshInterface):
|
||||
if not self.noProto: # Wait for the db download if using the protocol
|
||||
self._waitConnected()
|
||||
|
||||
def _disconnected(self):
|
||||
def _disconnected(self) -> None:
|
||||
"""We override the superclass implementation to close our port"""
|
||||
MeshInterface._disconnected(self)
|
||||
|
||||
logging.debug("Closing our port")
|
||||
logger.debug("Closing our port")
|
||||
# pylint: disable=E0203
|
||||
if not self.stream is None:
|
||||
# pylint: disable=E0203
|
||||
@@ -86,7 +107,7 @@ class StreamInterface(MeshInterface):
|
||||
# pylint: disable=W0201
|
||||
self.stream = None
|
||||
|
||||
def _writeBytes(self, b):
|
||||
def _writeBytes(self, b: bytes) -> None:
|
||||
"""Write an array of bytes to our stream and flush"""
|
||||
if self.stream: # ignore writes when stream is closed
|
||||
self.stream.write(b)
|
||||
@@ -98,32 +119,41 @@ class StreamInterface(MeshInterface):
|
||||
# we sleep here to give the TBeam a chance to work
|
||||
time.sleep(0.1)
|
||||
|
||||
def _readBytes(self, length):
|
||||
def _readBytes(self, length) -> Optional[bytes]:
|
||||
"""Read an array of bytes from our stream"""
|
||||
if self.stream:
|
||||
return self.stream.read(length)
|
||||
else:
|
||||
return None
|
||||
|
||||
def _sendToRadioImpl(self, toRadio):
|
||||
def _sendToRadioImpl(self, toRadio) -> None:
|
||||
"""Send a ToRadio protobuf to the device"""
|
||||
logging.debug(f"Sending: {stripnl(toRadio)}")
|
||||
b = toRadio.SerializeToString()
|
||||
bufLen = len(b)
|
||||
logger.debug(f"Sending: {stripnl(toRadio)}")
|
||||
b: bytes = toRadio.SerializeToString()
|
||||
bufLen: int = len(b)
|
||||
# We convert into a string, because the TCP code doesn't work with byte arrays
|
||||
header = bytes([START1, START2, (bufLen >> 8) & 0xFF, bufLen & 0xFF])
|
||||
logging.debug(f"sending header:{header} b:{b}")
|
||||
header: bytes = bytes([START1, START2, (bufLen >> 8) & 0xFF, bufLen & 0xFF])
|
||||
logger.debug(f"sending header:{header!r} b:{b!r}")
|
||||
self._writeBytes(header + b)
|
||||
|
||||
def close(self):
|
||||
def close(self) -> None:
|
||||
"""Close a connection to the device"""
|
||||
logging.debug("Closing stream")
|
||||
logger.debug("Closing stream")
|
||||
MeshInterface.close(self)
|
||||
# pyserial cancel_read doesn't seem to work, therefore we ask the
|
||||
# reader thread to close things for us
|
||||
self._wantExit = True
|
||||
if self._rxThread != threading.current_thread():
|
||||
self._rxThread.join() # wait for it to exit
|
||||
try:
|
||||
self._rxThread.join() # wait for it to exit
|
||||
except RuntimeError:
|
||||
# Thread was never started — happens when close() is invoked
|
||||
# from a failed __init__ before connect() could spawn it.
|
||||
# In this case there is no reader thread to close the stream.
|
||||
if self.stream is not None:
|
||||
with contextlib.suppress(Exception):
|
||||
self.stream.close()
|
||||
self.stream = None
|
||||
|
||||
def _handleLogByte(self, b):
|
||||
"""Handle a byte that is part of a log message from the device."""
|
||||
@@ -142,21 +172,21 @@ class StreamInterface(MeshInterface):
|
||||
else:
|
||||
self.cur_log_line += utf
|
||||
|
||||
def __reader(self):
|
||||
def __reader(self) -> None:
|
||||
"""The reader thread that reads bytes from our stream"""
|
||||
logging.debug("in __reader()")
|
||||
logger.debug("in __reader()")
|
||||
empty = bytes()
|
||||
|
||||
try:
|
||||
while not self._wantExit:
|
||||
# logging.debug("reading character")
|
||||
b = self._readBytes(1)
|
||||
# logging.debug("In reader loop")
|
||||
# logging.debug(f"read returned {b}")
|
||||
if len(b) > 0:
|
||||
c = b[0]
|
||||
# logging.debug(f'c:{c}')
|
||||
ptr = len(self._rxBuf)
|
||||
# logger.debug("reading character")
|
||||
b: Optional[bytes] = self._readBytes(1)
|
||||
# logger.debug("In reader loop")
|
||||
# logger.debug(f"read returned {b}")
|
||||
if b is not None and len(cast(bytes, b)) > 0:
|
||||
c: int = b[0]
|
||||
# logger.debug(f'c:{c}')
|
||||
ptr: int = len(self._rxBuf)
|
||||
|
||||
# Assume we want to append this byte, fixme use bytearray instead
|
||||
self._rxBuf = self._rxBuf + b
|
||||
@@ -172,7 +202,7 @@ class StreamInterface(MeshInterface):
|
||||
if c != START2:
|
||||
self._rxBuf = empty # failed to find start2
|
||||
elif ptr >= HEADER_LEN - 1: # we've at least got a header
|
||||
# logging.debug('at least we received a header')
|
||||
# logger.debug('at least we received a header')
|
||||
# big endian length follows header
|
||||
packetlen = (self._rxBuf[2] << 8) + self._rxBuf[3]
|
||||
|
||||
@@ -188,32 +218,32 @@ class StreamInterface(MeshInterface):
|
||||
try:
|
||||
self._handleFromRadio(self._rxBuf[HEADER_LEN:])
|
||||
except Exception as ex:
|
||||
logging.error(
|
||||
logger.error(
|
||||
f"Error while handling message from radio {ex}"
|
||||
)
|
||||
traceback.print_exc()
|
||||
self._rxBuf = empty
|
||||
else:
|
||||
# logging.debug(f"timeout")
|
||||
# logger.debug(f"timeout")
|
||||
pass
|
||||
except serial.SerialException as ex:
|
||||
if (
|
||||
not self._wantExit
|
||||
): # We might intentionally get an exception during shutdown
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f"Meshtastic serial port disconnected, disconnecting... {ex}"
|
||||
)
|
||||
except OSError as ex:
|
||||
if (
|
||||
not self._wantExit
|
||||
): # We might intentionally get an exception during shutdown
|
||||
logging.error(
|
||||
logger.error(
|
||||
f"Unexpected OSError, terminating meshtastic reader... {ex}"
|
||||
)
|
||||
except Exception as ex:
|
||||
logging.error(
|
||||
logger.error(
|
||||
f"Unexpected exception, terminating meshtastic reader... {ex}"
|
||||
)
|
||||
finally:
|
||||
logging.debug("reader is exiting")
|
||||
logger.debug("reader is exiting")
|
||||
self._disconnected()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
""" Supported Meshtastic Devices - This is a class and collection of Meshtastic devices.
|
||||
It is used for auto detection as to which device might be connected.
|
||||
"""
|
||||
# pylint: disable=R0917
|
||||
|
||||
# Goal is to detect which device and port to use from the supported devices
|
||||
# without installing any libraries that are not currently in the python meshtastic library
|
||||
@@ -206,6 +207,30 @@ nano_g1 = SupportedDevice(
|
||||
usb_product_id_in_hex="55d4",
|
||||
)
|
||||
|
||||
seeed_xiao_s3 = SupportedDevice(
|
||||
name = "Seeed Xiao ESP32-S3",
|
||||
version = "",
|
||||
for_firmware="seeed-xiao-esp32s3",
|
||||
baseport_on_linux="ttyACM",
|
||||
baseport_on_mac="cu.usbmodem",
|
||||
usb_vendor_id_in_hex="2886",
|
||||
usb_product_id_in_hex="0059",
|
||||
)
|
||||
|
||||
tdeck = SupportedDevice(
|
||||
name="T-Deck",
|
||||
version="",
|
||||
for_firmware="t-deck", # Confirmed firmware identifier
|
||||
device_class="esp32",
|
||||
baseport_on_linux="ttyACM",
|
||||
baseport_on_mac="cu.usbmodem",
|
||||
baseport_on_windows="COM",
|
||||
usb_vendor_id_in_hex="303a", # Espressif Systems (VERIFIED)
|
||||
usb_product_id_in_hex="1001", # VERIFIED from actual device
|
||||
)
|
||||
|
||||
|
||||
|
||||
supported_devices = [
|
||||
tbeam_v0_7,
|
||||
tbeam_v1_1,
|
||||
@@ -225,4 +250,6 @@ supported_devices = [
|
||||
rak4631_19003,
|
||||
rak11200,
|
||||
nano_g1,
|
||||
seeed_xiao_s3,
|
||||
tdeck, # T-Deck support added
|
||||
]
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
"""TCPInterface class for interfacing with http endpoint
|
||||
"""
|
||||
# pylint: disable=R0917
|
||||
import contextlib
|
||||
import logging
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from meshtastic.stream_interface import StreamInterface
|
||||
|
||||
DEFAULT_TCP_PORT = 4403
|
||||
|
||||
# How long close() gives the device to consume what we last wrote before forcing
|
||||
# the connection down. See close() for why this exists.
|
||||
GRACEFUL_CLOSE_TIMEOUT = 0.25
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TCPInterface(StreamInterface):
|
||||
"""Interface class for meshtastic devices over a TCP link"""
|
||||
@@ -14,64 +26,155 @@ class TCPInterface(StreamInterface):
|
||||
self,
|
||||
hostname: str,
|
||||
debugOut=None,
|
||||
noProto=False,
|
||||
connectNow=True,
|
||||
portNumber=4403,
|
||||
noNodes:bool=False,
|
||||
noProto: bool = False,
|
||||
connectNow: bool = True,
|
||||
portNumber: int = DEFAULT_TCP_PORT,
|
||||
noNodes: bool = False,
|
||||
timeout: int = 300,
|
||||
):
|
||||
"""Constructor, opens a connection to a specified IP address/hostname
|
||||
|
||||
Keyword Arguments:
|
||||
hostname {string} -- Hostname/IP address of the device to connect to
|
||||
timeout -- How long to wait for replies (default: 300 seconds)
|
||||
"""
|
||||
self.hostname: str = hostname
|
||||
self.portNumber: int = portNumber
|
||||
|
||||
self.stream = None
|
||||
self.socket: Optional[socket.socket] = None
|
||||
self.reconnectLock = threading.Lock()
|
||||
|
||||
self.hostname = hostname
|
||||
self.portNumber = portNumber
|
||||
|
||||
if connectNow:
|
||||
logging.debug(f"Connecting to {hostname}") # type: ignore[str-bytes-safe]
|
||||
server_address = (hostname, portNumber)
|
||||
sock = socket.create_connection(server_address)
|
||||
self.socket: Optional[socket.socket] = sock
|
||||
else:
|
||||
self.socket = None
|
||||
|
||||
StreamInterface.__init__(
|
||||
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes
|
||||
super().__init__(
|
||||
debugOut=debugOut,
|
||||
noProto=noProto,
|
||||
connectNow=connectNow,
|
||||
noNodes=noNodes,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
def _socket_shutdown(self):
|
||||
def __repr__(self):
|
||||
rep = f"TCPInterface({self.hostname!r}"
|
||||
if self.debugOut is not None:
|
||||
rep += f", debugOut={self.debugOut!r}"
|
||||
if self.noProto:
|
||||
rep += ", noProto=True"
|
||||
if self.socket is None:
|
||||
rep += ", connectNow=False"
|
||||
if self.portNumber != DEFAULT_TCP_PORT:
|
||||
rep += f", portNumber={self.portNumber!r}"
|
||||
if self.noNodes:
|
||||
rep += ", noNodes=True"
|
||||
rep += ")"
|
||||
return rep
|
||||
|
||||
def _socket_shutdown(self) -> None:
|
||||
"""Shutdown the socket.
|
||||
Note: Broke out this line so the exception could be unit tested.
|
||||
"""
|
||||
self.socket.shutdown(socket.SHUT_RDWR)
|
||||
if self.socket is not None:
|
||||
self.socket.shutdown(socket.SHUT_RDWR)
|
||||
|
||||
def myConnect(self):
|
||||
"""Connect to socket"""
|
||||
def connect(self) -> None:
|
||||
"""Connect the interface"""
|
||||
self.myConnect()
|
||||
super().connect()
|
||||
|
||||
def myConnect(self) -> None:
|
||||
"""Connect to socket (without attempting to start the interface's receive thread)"""
|
||||
logger.debug(f"Connecting to {self.hostname}") # type: ignore[str-bytes-safe]
|
||||
server_address = (self.hostname, self.portNumber)
|
||||
sock = socket.create_connection(server_address)
|
||||
self.socket = sock
|
||||
self.socket = socket.create_connection(server_address)
|
||||
|
||||
def close(self):
|
||||
"""Close a connection to the device"""
|
||||
logging.debug("Closing TCP stream")
|
||||
StreamInterface.close(self)
|
||||
def _wait_for_reader_exit(self, timeout: float) -> None:
|
||||
"""Wait briefly for the reader thread to drain and exit after a half-close.
|
||||
|
||||
Returns as soon as it exits, or after timeout: the device is not obliged
|
||||
to close just because we did.
|
||||
"""
|
||||
rx = getattr(self, "_rxThread", None)
|
||||
if rx is None or rx is threading.current_thread():
|
||||
return
|
||||
with contextlib.suppress(Exception):
|
||||
rx.join(timeout)
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close a connection to the device."""
|
||||
logger.debug("Closing TCP stream")
|
||||
# Sometimes the socket read might be blocked in the reader thread.
|
||||
# Therefore we force the shutdown by closing the socket here
|
||||
# Therefore force a shutdown first to unblock reader thread reads.
|
||||
self._wantExit = True
|
||||
if not self.socket is None:
|
||||
try:
|
||||
if self.socket is not None:
|
||||
# Half-close first. shutdown(SHUT_WR) sends FIN, which tells the
|
||||
# device we are done writing and lets it consume what we last wrote
|
||||
# before the connection goes away -- typically the admin message a
|
||||
# one-shot command such as `--set` just sent.
|
||||
#
|
||||
# Going straight to shutdown(SHUT_RDWR) + close() while either side
|
||||
# still has unread data makes the stack send RST instead. Winsock
|
||||
# then discards data the peer had already received but not yet read,
|
||||
# so the write is lost; Linux delivers it before reporting
|
||||
# ECONNRESET, which is why this only bites on Windows.
|
||||
with contextlib.suppress(Exception):
|
||||
self.socket.shutdown(socket.SHUT_WR)
|
||||
self._wait_for_reader_exit(GRACEFUL_CLOSE_TIMEOUT)
|
||||
|
||||
with contextlib.suppress(
|
||||
Exception
|
||||
): # Ignore errors in shutdown, because we might have a race with the server
|
||||
self._socket_shutdown()
|
||||
except:
|
||||
pass # Ignore errors in shutdown, because we might have a race with the server
|
||||
self.socket.close()
|
||||
with contextlib.suppress(Exception):
|
||||
self.socket.close()
|
||||
|
||||
def _writeBytes(self, b):
|
||||
"""Write an array of bytes to our stream and flush"""
|
||||
self.socket.send(b)
|
||||
self.socket = None
|
||||
super().close()
|
||||
|
||||
def _readBytes(self, length):
|
||||
def _writeBytes(self, b: bytes) -> None:
|
||||
"""Write an array of bytes to our stream"""
|
||||
if self.socket is not None:
|
||||
try:
|
||||
self.socket.sendall(b)
|
||||
except OSError as e:
|
||||
logger.error(f"Socket send error, reconnecting: {e}")
|
||||
if not self._wantExit:
|
||||
self._reconnect()
|
||||
raise
|
||||
|
||||
def _readBytes(self, length) -> Optional[bytes]:
|
||||
"""Read an array of bytes from our stream"""
|
||||
return self.socket.recv(length)
|
||||
if self.socket is not None:
|
||||
data = self.socket.recv(length)
|
||||
# empty byte indicates a disconnected socket,
|
||||
# we need to handle it to avoid an infinite loop reading from null socket
|
||||
if data == b"":
|
||||
logger.debug("Closed socket, re-connecting")
|
||||
if not self._wantExit:
|
||||
self._reconnect()
|
||||
return data
|
||||
|
||||
# no socket, break reader thread
|
||||
self._wantExit = True
|
||||
return None
|
||||
|
||||
def _reconnect(self) -> None:
|
||||
"""Reconnect to the socket"""
|
||||
# Save the socket reference before attempting to acquire the lock.
|
||||
sock = self.socket
|
||||
start_config = False
|
||||
with self.reconnectLock:
|
||||
if self._wantExit:
|
||||
return
|
||||
# Don't reconnect: someone else already did it.
|
||||
if sock is not self.socket:
|
||||
return
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
self._socket_shutdown()
|
||||
if self.socket is not None:
|
||||
self.socket.close()
|
||||
self.socket = None
|
||||
time.sleep(1)
|
||||
self.myConnect()
|
||||
start_config = True
|
||||
|
||||
if start_config and not self._wantExit and self.socket is not None:
|
||||
self._startConfig()
|
||||
|
||||
@@ -5,6 +5,9 @@ import logging
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import io
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from dotmap import DotMap # type: ignore[import-untyped]
|
||||
from pubsub import pub # type: ignore[import-untyped]
|
||||
@@ -15,19 +18,20 @@ from meshtastic.serial_interface import SerialInterface
|
||||
from meshtastic.tcp_interface import TCPInterface
|
||||
|
||||
"""The interfaces we are using for our tests"""
|
||||
interfaces = None
|
||||
interfaces: List = []
|
||||
|
||||
"""A list of all packets we received while the current test was running"""
|
||||
receivedPackets = None
|
||||
receivedPackets: Optional[List] = None
|
||||
|
||||
testsRunning = False
|
||||
testsRunning: bool = False
|
||||
|
||||
testNumber = 0
|
||||
testNumber: int = 0
|
||||
|
||||
sendingInterface = None
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def onReceive(packet, interface):
|
||||
def onReceive(packet, interface) -> None:
|
||||
"""Callback invoked when a packet arrives"""
|
||||
if sendingInterface == interface:
|
||||
pass
|
||||
@@ -42,20 +46,20 @@ def onReceive(packet, interface):
|
||||
receivedPackets.append(p)
|
||||
|
||||
|
||||
def onNode(node):
|
||||
def onNode(node) -> None:
|
||||
"""Callback invoked when the node DB changes"""
|
||||
print(f"Node changed: {node}")
|
||||
|
||||
|
||||
def subscribe():
|
||||
def subscribe() -> None:
|
||||
"""Subscribe to the topics the user probably wants to see, prints output to stdout"""
|
||||
|
||||
pub.subscribe(onNode, "meshtastic.node")
|
||||
|
||||
|
||||
def testSend(
|
||||
fromInterface, toInterface, isBroadcast=False, asBinary=False, wantAck=False
|
||||
):
|
||||
fromInterface, toInterface, isBroadcast: bool=False, asBinary: bool=False, wantAck: bool=False
|
||||
) -> bool:
|
||||
"""
|
||||
Sends one test packet between two nodes and then returns success or failure
|
||||
|
||||
@@ -76,7 +80,7 @@ def testSend(
|
||||
else:
|
||||
toNode = toInterface.myInfo.my_node_num
|
||||
|
||||
logging.debug(f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}")
|
||||
logger.debug(f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}")
|
||||
# pylint: disable=W0603
|
||||
global sendingInterface
|
||||
sendingInterface = fromInterface
|
||||
@@ -93,43 +97,43 @@ def testSend(
|
||||
return False # Failed to send
|
||||
|
||||
|
||||
def runTests(numTests=50, wantAck=False, maxFailures=0):
|
||||
def runTests(numTests: int=50, wantAck: bool=False, maxFailures: int=0) -> bool:
|
||||
"""Run the tests."""
|
||||
logging.info(f"Running {numTests} tests with wantAck={wantAck}")
|
||||
numFail = 0
|
||||
numSuccess = 0
|
||||
logger.info(f"Running {numTests} tests with wantAck={wantAck}")
|
||||
numFail: int = 0
|
||||
numSuccess: int = 0
|
||||
for _ in range(numTests):
|
||||
# pylint: disable=W0603
|
||||
global testNumber
|
||||
testNumber = testNumber + 1
|
||||
isBroadcast = True
|
||||
isBroadcast:bool = True
|
||||
# asBinary=(i % 2 == 0)
|
||||
success = testSend(
|
||||
interfaces[0], interfaces[1], isBroadcast, asBinary=False, wantAck=wantAck
|
||||
)
|
||||
if not success:
|
||||
numFail = numFail + 1
|
||||
logging.error(
|
||||
logger.error(
|
||||
f"Test {testNumber} failed, expected packet not received ({numFail} failures so far)"
|
||||
)
|
||||
else:
|
||||
numSuccess = numSuccess + 1
|
||||
logging.info(
|
||||
logger.info(
|
||||
f"Test {testNumber} succeeded {numSuccess} successes {numFail} failures so far"
|
||||
)
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
if numFail > maxFailures:
|
||||
logging.error("Too many failures! Test failed!")
|
||||
logger.error("Too many failures! Test failed!")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def testThread(numTests=50):
|
||||
def testThread(numTests=50) -> bool:
|
||||
"""Test thread"""
|
||||
logging.info("Found devices, starting tests...")
|
||||
result = runTests(numTests, wantAck=True)
|
||||
logger.info("Found devices, starting tests...")
|
||||
result: bool = runTests(numTests, wantAck=True)
|
||||
if result:
|
||||
# Run another test
|
||||
# Allow a few dropped packets
|
||||
@@ -137,25 +141,25 @@ def testThread(numTests=50):
|
||||
return result
|
||||
|
||||
|
||||
def onConnection(topic=pub.AUTO_TOPIC):
|
||||
def onConnection(topic=pub.AUTO_TOPIC) -> None:
|
||||
"""Callback invoked when we connect/disconnect from a radio"""
|
||||
print(f"Connection changed: {topic.getName()}")
|
||||
|
||||
|
||||
def openDebugLog(portName):
|
||||
def openDebugLog(portName) -> io.TextIOWrapper:
|
||||
"""Open the debug log file"""
|
||||
debugname = "log" + portName.replace("/", "_")
|
||||
logging.info(f"Writing serial debugging to {debugname}")
|
||||
logger.info(f"Writing serial debugging to {debugname}")
|
||||
return open(debugname, "w+", buffering=1, encoding="utf8")
|
||||
|
||||
|
||||
def testAll(numTests=5):
|
||||
def testAll(numTests: int=5) -> bool:
|
||||
"""
|
||||
Run a series of tests using devices we can find.
|
||||
This is called from the cli with the "--test" option.
|
||||
|
||||
"""
|
||||
ports = meshtastic.util.findPorts(True)
|
||||
ports: List[str] = meshtastic.util.findPorts(True)
|
||||
if len(ports) < 2:
|
||||
meshtastic.util.our_exit(
|
||||
"Warning: Must have at least two devices connected to USB."
|
||||
@@ -174,8 +178,8 @@ def testAll(numTests=5):
|
||||
)
|
||||
)
|
||||
|
||||
logging.info("Ports opened, starting test")
|
||||
result = testThread(numTests)
|
||||
logger.info("Ports opened, starting test")
|
||||
result: bool = testThread(numTests)
|
||||
|
||||
for i in interfaces:
|
||||
i.close()
|
||||
@@ -183,7 +187,7 @@ def testAll(numTests=5):
|
||||
return result
|
||||
|
||||
|
||||
def testSimulator():
|
||||
def testSimulator() -> None:
|
||||
"""
|
||||
Assume that someone has launched meshtastic-native as a simulated node.
|
||||
Talk to that node over TCP, do some operations and if they are successful
|
||||
@@ -193,14 +197,14 @@ def testSimulator():
|
||||
python3 -c 'from meshtastic.test import testSimulator; testSimulator()'
|
||||
"""
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logging.info("Connecting to simulator on localhost!")
|
||||
logger.info("Connecting to simulator on localhost!")
|
||||
try:
|
||||
iface = TCPInterface("localhost")
|
||||
iface: meshtastic.tcp_interface.TCPInterface = TCPInterface("localhost")
|
||||
iface.showInfo()
|
||||
iface.localNode.showInfo()
|
||||
iface.localNode.exitSimulator()
|
||||
iface.close()
|
||||
logging.info("Integration test successful!")
|
||||
logger.info("Integration test successful!")
|
||||
except:
|
||||
print("Error while testing simulator:", sys.exc_info()[0])
|
||||
traceback.print_exc()
|
||||
|
||||
@@ -8,6 +8,87 @@ import pytest
|
||||
from meshtastic import mt_config
|
||||
|
||||
from ..mesh_interface import MeshInterface
|
||||
from .firmware_harness import (
|
||||
CHAIN_TOPOLOGY,
|
||||
DEFAULT_BASE_PORT,
|
||||
SimMesh,
|
||||
find_meshtasticd,
|
||||
is_compatible_host,
|
||||
)
|
||||
from .fw_helpers import set_region
|
||||
|
||||
# Use a different base port for the single-node fixture so it doesn't
|
||||
# conflict with the multi-node mesh fixture.
|
||||
SINGLE_NODE_BASE_PORT = DEFAULT_BASE_PORT + 100
|
||||
|
||||
|
||||
def _skip_firmware_if_unavailable() -> None:
|
||||
"""Skip the test when meshtasticd can't run on this host."""
|
||||
if not is_compatible_host():
|
||||
pytest.skip("meshtasticd firmware tests require Linux")
|
||||
if find_meshtasticd() is None:
|
||||
pytest.skip(
|
||||
"meshtasticd not found — set MESHTASTICD_BIN or install it on PATH"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def firmware_node():
|
||||
"""A single meshtasticd sim node for smokevirt tests.
|
||||
|
||||
Function-scoped so every test gets a freshly-erased node with no
|
||||
state leaking from previous tests. This makes destructive commands
|
||||
(``--reboot``, ``--set factory_reset true``) safe to run and lets
|
||||
tests be order-independent.
|
||||
|
||||
Yields the SimNode instance. The node is booted with a fresh erased
|
||||
config and listens on localhost at its TCP port. Region is set to US
|
||||
so modem-preset tests work against firmware >= 2.8, which clamps
|
||||
presets to the legal set for the current region.
|
||||
"""
|
||||
_skip_firmware_if_unavailable()
|
||||
mesh = SimMesh(n_nodes=1, base_port=SINGLE_NODE_BASE_PORT)
|
||||
mesh.start()
|
||||
node = mesh.get_node(0)
|
||||
set_region(node.port, "US")
|
||||
# The region commit restarts the TCP listener, so reconnect the harness
|
||||
# interface in case a test wants to use it directly.
|
||||
if node.iface is not None:
|
||||
try:
|
||||
node.iface.close()
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
node.connect()
|
||||
yield node
|
||||
mesh.stop()
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def firmware_mesh():
|
||||
"""A 3-node chain (A-B-C) meshtasticd sim mesh for smokemesh tests.
|
||||
|
||||
Yields the SimMesh instance. Nodes are connected and the SIMULATOR_APP
|
||||
packet bridge is running. Region is set to US for firmware >= 2.8
|
||||
compatibility, interfaces are reconnected after the region change, and
|
||||
node DB convergence is awaited.
|
||||
"""
|
||||
_skip_firmware_if_unavailable()
|
||||
mesh = SimMesh(n_nodes=3, topology=CHAIN_TOPOLOGY)
|
||||
mesh.start()
|
||||
for node in mesh.nodes:
|
||||
set_region(node.port, "US")
|
||||
# The region commit restarts each node's TCP listener, so reconnect the
|
||||
# harness interfaces before waiting for convergence.
|
||||
for node in mesh.nodes:
|
||||
if node.iface is not None:
|
||||
try:
|
||||
node.iface.close()
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
node.connect()
|
||||
mesh.wait_for_convergence(timeout=30)
|
||||
yield mesh
|
||||
mesh.stop()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
357
meshtastic/tests/firmware_harness.py
Normal file
357
meshtastic/tests/firmware_harness.py
Normal file
@@ -0,0 +1,357 @@
|
||||
"""Test harness for running real meshtasticd firmware instances.
|
||||
|
||||
Launches one or more meshtasticd processes in simulator mode (-s) and bridges
|
||||
their "over-the-air" packets via the SIMULATOR_APP protocol so that multiple
|
||||
instances can communicate as if via LoRa, with a configurable topology.
|
||||
|
||||
The harness expects meshtasticd to be available on PATH or via the
|
||||
MESHTASTICD_BIN environment variable. It does not download or build the
|
||||
binary itself.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import signal
|
||||
import socket
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
from typing import Dict, List, Optional, Set
|
||||
|
||||
from pubsub import pub # type: ignore[import-untyped]
|
||||
|
||||
from meshtastic import BROADCAST_NUM, mesh_pb2, portnums_pb2
|
||||
from meshtastic.tcp_interface import TCPInterface
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
HW_ID_OFFSET = 16
|
||||
DEFAULT_BASE_PORT = 4404
|
||||
DEFAULT_RSSI = -50
|
||||
DEFAULT_SNR = 10.0
|
||||
BOOT_TIMEOUT = 30
|
||||
CONNECT_TIMEOUT = 30
|
||||
|
||||
CHAIN_TOPOLOGY: Dict[int, Set[int]] = {
|
||||
0: {1},
|
||||
1: {0, 2},
|
||||
2: {1},
|
||||
}
|
||||
|
||||
|
||||
def find_meshtasticd() -> Optional[str]:
|
||||
"""Return the path to the meshtasticd binary, or None if not found."""
|
||||
env_path = os.environ.get("MESHTASTICD_BIN")
|
||||
if env_path and os.path.isfile(env_path) and os.access(env_path, os.X_OK):
|
||||
return env_path
|
||||
return shutil.which("meshtasticd")
|
||||
|
||||
|
||||
def is_compatible_host() -> bool:
|
||||
"""True when the host can run meshtasticd natively (Linux only)."""
|
||||
return platform.system() == "Linux"
|
||||
|
||||
|
||||
def _wait_for_port(port: int, timeout: int = BOOT_TIMEOUT) -> bool:
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
s = socket.create_connection(("localhost", port), timeout=0.5)
|
||||
s.close()
|
||||
return True
|
||||
except OSError:
|
||||
time.sleep(0.5)
|
||||
return False
|
||||
|
||||
|
||||
class SimNode:
|
||||
"""A single meshtasticd simulator instance."""
|
||||
|
||||
def __init__(self, node_id: int, base_port: int = DEFAULT_BASE_PORT):
|
||||
self.node_id = node_id
|
||||
self.hw_id = node_id + HW_ID_OFFSET
|
||||
self.port = base_port + node_id
|
||||
self.process: Optional[subprocess.Popen] = None
|
||||
self.workdir: Optional[str] = None
|
||||
self.iface: Optional[TCPInterface] = None
|
||||
self._log_files: list = []
|
||||
|
||||
@property
|
||||
def node_num(self) -> int:
|
||||
"""The firmware-assigned node number (== hw_id)."""
|
||||
if self.iface and self.iface.myInfo:
|
||||
return self.iface.myInfo.my_node_num
|
||||
return self.hw_id
|
||||
|
||||
def start(self, binary: str) -> None:
|
||||
"""Launch the meshtasticd process in simulator mode."""
|
||||
self.workdir = tempfile.mkdtemp(prefix=f"mtd_node{self.node_id}_")
|
||||
vfs_dir = os.path.join(self.workdir, "vfs")
|
||||
os.mkdir(vfs_dir)
|
||||
# Files are closed in _kill(); keep them open for the process lifetime.
|
||||
log_stdout = open( # pylint: disable=consider-using-with
|
||||
os.path.join(self.workdir, "meshtasticd.log"), "wb", buffering=0
|
||||
)
|
||||
log_stderr = open( # pylint: disable=consider-using-with
|
||||
os.path.join(self.workdir, "meshtasticd.err"), "wb", buffering=0
|
||||
)
|
||||
self._log_files = [log_stdout, log_stderr]
|
||||
self.process = subprocess.Popen( # pylint: disable=consider-using-with
|
||||
[
|
||||
binary,
|
||||
"-s",
|
||||
"-h", str(self.hw_id),
|
||||
"-p", str(self.port),
|
||||
"-d", vfs_dir,
|
||||
"-e",
|
||||
],
|
||||
stdout=log_stdout,
|
||||
stderr=log_stderr,
|
||||
start_new_session=True,
|
||||
)
|
||||
if not _wait_for_port(self.port):
|
||||
self._kill()
|
||||
raise RuntimeError(
|
||||
f"meshtasticd node {self.node_id} did not start listening on port {self.port}"
|
||||
)
|
||||
def connect(self) -> None:
|
||||
"""Open a TCPInterface connection to this node."""
|
||||
self.iface = TCPInterface(
|
||||
hostname="localhost",
|
||||
portNumber=self.port,
|
||||
connectNow=False,
|
||||
)
|
||||
self.iface.myConnect()
|
||||
self.iface.connect()
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the interface and kill the process."""
|
||||
if self.iface is not None:
|
||||
try:
|
||||
self.iface.localNode.exitSimulator()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
self.iface.close()
|
||||
except Exception:
|
||||
pass
|
||||
self.iface = None
|
||||
self._kill()
|
||||
if self.workdir:
|
||||
shutil.rmtree(self.workdir, ignore_errors=True)
|
||||
self.workdir = None
|
||||
|
||||
def _kill(self) -> None:
|
||||
for f in self._log_files:
|
||||
try:
|
||||
f.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._log_files = []
|
||||
if self.process is not None:
|
||||
try:
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
|
||||
except (ProcessLookupError, OSError):
|
||||
pass
|
||||
try:
|
||||
self.process.wait(timeout=5)
|
||||
except Exception:
|
||||
try:
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGKILL)
|
||||
except Exception:
|
||||
pass
|
||||
# Give OS time to release TCP port (avoid TIME_WAIT preventing
|
||||
# next instance from binding the same port)
|
||||
time.sleep(1.0)
|
||||
self.process = None
|
||||
|
||||
|
||||
class SimMesh:
|
||||
"""Manages N meshtasticd sim instances and bridges their SIMULATOR_APP packets.
|
||||
|
||||
When *topology* is None every node hears every other node (full mesh).
|
||||
Otherwise *topology* maps a transmitter's node index to the set of receiver
|
||||
node indices that can hear it.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
n_nodes: int = 1,
|
||||
topology: Optional[Dict[int, Set[int]]] = None,
|
||||
base_port: int = DEFAULT_BASE_PORT,
|
||||
):
|
||||
self.n_nodes = n_nodes
|
||||
self.topology = topology
|
||||
self.base_port = base_port
|
||||
self.nodes: List[SimNode] = [
|
||||
SimNode(i, base_port) for i in range(n_nodes)
|
||||
]
|
||||
self._port_to_idx: Dict[int, int] = {}
|
||||
self._started = False
|
||||
|
||||
def start(self) -> None:
|
||||
"""Launch all nodes, connect, and start the packet bridge."""
|
||||
binary = find_meshtasticd()
|
||||
if binary is None:
|
||||
raise RuntimeError(
|
||||
"meshtasticd not found. Set MESHTASTICD_BIN or install it on PATH."
|
||||
)
|
||||
|
||||
for node in self.nodes:
|
||||
node.start(binary)
|
||||
|
||||
for node in self.nodes:
|
||||
node.connect()
|
||||
self._port_to_idx[node.port] = node.node_id
|
||||
|
||||
pub.subscribe(self._on_sim_packet, "meshtastic.receive.simulator")
|
||||
self._started = True
|
||||
|
||||
if self.n_nodes > 1:
|
||||
self._trigger_convergence()
|
||||
|
||||
def _trigger_convergence(self) -> None:
|
||||
"""Actively trigger NodeInfo exchange instead of waiting passively.
|
||||
|
||||
Sends a NODEINFO_APP packet with wantResponse from each node so the
|
||||
firmware's NodeInfoModule responds with its own user info, populating
|
||||
all node DBs deterministically.
|
||||
"""
|
||||
for node in self.nodes:
|
||||
iface = node.iface
|
||||
if iface is None:
|
||||
continue
|
||||
user = mesh_pb2.User()
|
||||
user.id = f"!{node.node_num:08x}"
|
||||
user.long_name = f"Node {node.node_id}"
|
||||
user.short_name = f"{node.node_id:04d}"[:4]
|
||||
user.hw_model = mesh_pb2.HardwareModel.PORTDUINO
|
||||
try:
|
||||
iface.sendData(
|
||||
user,
|
||||
destinationId=BROADCAST_NUM,
|
||||
portNum=portnums_pb2.PortNum.NODEINFO_APP,
|
||||
wantAck=False,
|
||||
wantResponse=True,
|
||||
)
|
||||
except Exception as ex:
|
||||
logger.debug("NodeInfo trigger for node %d failed: %s", node.node_id, ex)
|
||||
time.sleep(5)
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Shut down all nodes and clean up."""
|
||||
if not self._started:
|
||||
return
|
||||
try:
|
||||
pub.unsubscribe(self._on_sim_packet, "meshtastic.receive.simulator")
|
||||
except Exception:
|
||||
pass
|
||||
for node in self.nodes:
|
||||
node.close()
|
||||
self._started = False
|
||||
|
||||
def get_node(self, idx: int) -> SimNode:
|
||||
"""Return the SimNode at the given index."""
|
||||
return self.nodes[idx]
|
||||
|
||||
def get_iface(self, idx: int) -> TCPInterface:
|
||||
"""Return the TCPInterface for the node at the given index."""
|
||||
iface = self.nodes[idx].iface
|
||||
assert iface is not None, f"node {idx} has no interface"
|
||||
return iface
|
||||
|
||||
def wait_for_convergence(self, timeout: int = 30) -> bool:
|
||||
"""Wait until every node sees all others in its node DB.
|
||||
|
||||
Returns True if converged, False if timed out (non-fatal — the packet
|
||||
bridge forwards regardless of node DB state).
|
||||
"""
|
||||
if self.n_nodes <= 1:
|
||||
return True
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
if all(
|
||||
node.iface is not None
|
||||
and node.iface.nodes is not None
|
||||
and len(node.iface.nodes) >= self.n_nodes
|
||||
for node in self.nodes
|
||||
):
|
||||
return True
|
||||
time.sleep(2)
|
||||
logger.warning("Mesh did not fully converge within %ds", timeout)
|
||||
return False
|
||||
|
||||
def _get_receivers(self, tx_idx: int) -> List[int]:
|
||||
"""Return node indices that can hear a transmission from *tx_idx*."""
|
||||
if self.topology is not None:
|
||||
return sorted(self.topology.get(tx_idx, set()))
|
||||
return [i for i in range(self.n_nodes) if i != tx_idx]
|
||||
|
||||
def _on_sim_packet(self, interface, packet) -> None:
|
||||
"""Bridge callback: forward a SIMULATOR_APP packet to receiving nodes."""
|
||||
tx_port = getattr(interface, "portNumber", None)
|
||||
tx_idx = self._port_to_idx.get(tx_port) if tx_port else None
|
||||
if tx_idx is None:
|
||||
return
|
||||
|
||||
rx_indices = self._get_receivers(tx_idx)
|
||||
if not rx_indices:
|
||||
return
|
||||
|
||||
data = packet["decoded"]["payload"]
|
||||
if hasattr(data, "SerializeToString"):
|
||||
data = data.SerializeToString()
|
||||
|
||||
if len(data) > mesh_pb2.Constants.DATA_PAYLOAD_LEN:
|
||||
logger.warning("Simulator payload too big (%d bytes), dropping", len(data))
|
||||
return
|
||||
|
||||
mesh_packet = _build_mesh_packet(packet, data)
|
||||
|
||||
for rx_idx in rx_indices:
|
||||
rx_iface = self.nodes[rx_idx].iface
|
||||
if rx_iface is None:
|
||||
continue
|
||||
mesh_packet.rx_rssi = DEFAULT_RSSI
|
||||
mesh_packet.rx_snr = DEFAULT_SNR
|
||||
to_radio = mesh_pb2.ToRadio()
|
||||
to_radio.packet.CopyFrom(mesh_packet)
|
||||
try:
|
||||
rx_iface._sendToRadio(to_radio)
|
||||
except Exception as ex:
|
||||
logger.error("Error forwarding packet to node %d: %s", rx_idx, ex)
|
||||
|
||||
def __enter__(self):
|
||||
self.start()
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc):
|
||||
self.stop()
|
||||
|
||||
|
||||
def _build_mesh_packet(packet: dict, data: bytes) -> mesh_pb2.MeshPacket:
|
||||
"""Reconstruct a MeshPacket for SIMULATOR_APP injection."""
|
||||
mp = mesh_pb2.MeshPacket()
|
||||
mp.decoded.payload = data
|
||||
mp.decoded.portnum = portnums_pb2.PortNum.SIMULATOR_APP
|
||||
mp.to = packet.get("to", BROADCAST_NUM)
|
||||
setattr(mp, "from", packet.get("from", 0))
|
||||
mp.id = packet.get("id", 0)
|
||||
mp.want_ack = packet.get("wantAck", False)
|
||||
mp.hop_limit = packet.get("hopLimit", 0)
|
||||
mp.hop_start = packet.get("hopStart", 0)
|
||||
mp.via_mqtt = packet.get("viaMQTT", False)
|
||||
mp.relay_node = packet.get("relayNode", 0)
|
||||
mp.next_hop = packet.get("nextHop", 0)
|
||||
mp.channel = int(packet.get("channel", 0))
|
||||
|
||||
decoded = packet.get("decoded", {})
|
||||
if "requestId" in decoded:
|
||||
mp.decoded.request_id = decoded["requestId"]
|
||||
if "wantResponse" in decoded:
|
||||
mp.decoded.want_response = decoded["wantResponse"]
|
||||
if "bitfield" in decoded:
|
||||
mp.decoded.bitfield = decoded["bitfield"]
|
||||
|
||||
return mp
|
||||
374
meshtastic/tests/fw_helpers.py
Normal file
374
meshtastic/tests/fw_helpers.py
Normal file
@@ -0,0 +1,374 @@
|
||||
"""Shared helpers for meshtasticd-backed smoke tests.
|
||||
|
||||
Both ``test_smokevirt`` (single node) and ``test_smokemesh`` (multi-node
|
||||
chain) use these helpers to drive the ``meshtastic`` CLI against real
|
||||
``meshtasticd`` simulator instances and then verify the resulting firmware
|
||||
state through the Python library's ``TCPInterface``.
|
||||
|
||||
Verifying through the library (rather than regex on CLI stdout) is the
|
||||
core design choice: it makes tests robust against CLI wording changes
|
||||
while still exercising both the CLI argparse path and the firmware I/O
|
||||
path of every feature.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import shlex
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from typing import Callable, List, Optional, Tuple
|
||||
|
||||
from pubsub import pub # type: ignore[import-untyped]
|
||||
|
||||
from meshtastic.tcp_interface import TCPInterface
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Pause between a CLI command finishing and a verification interface
|
||||
# opening, to let the firmware flush its TCP bookkeeping. Keeps the
|
||||
# simulator happy when many short-lived connections are happening.
|
||||
PAUSE_AFTER_CLI = 0.2
|
||||
|
||||
# Pause after changing the LoRa region. The firmware may restart the TCP
|
||||
# listener while committing the new radio config, so give it time to settle
|
||||
# before the next CLI call or harness reconnect.
|
||||
PAUSE_AFTER_REGION_CHANGE = 2.0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CLI invocation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def resolve_cli() -> str:
|
||||
"""Return a shell-invokable ``meshtastic`` command.
|
||||
|
||||
Prefers the in-tree module run through the current interpreter so
|
||||
tests exercise the source we are editing, regardless of any
|
||||
separately-installed ``meshtastic`` entry point on PATH.
|
||||
"""
|
||||
# The PATH binary may live outside the nono sandbox's allowed paths;
|
||||
# ``python -m meshtastic`` is more portable and always available.
|
||||
return f"{sys.executable} -m meshtastic"
|
||||
|
||||
|
||||
def run_cli(
|
||||
port: int,
|
||||
*args: str,
|
||||
timeout: int = 60,
|
||||
retries: int = 2,
|
||||
retry_delay: float = 1.0,
|
||||
) -> Tuple[int, str]:
|
||||
"""Run the ``meshtastic`` CLI against the sim node on *port*.
|
||||
|
||||
Returns ``(return_code, merged_stdout_stderr)``. ``--host
|
||||
localhost:PORT`` is prefixed automatically. stderr is merged into
|
||||
stdout so callers can match warning text such as "Warning: Need to
|
||||
specify ..." regardless of which stream it lands on.
|
||||
|
||||
If the CLI fails to connect on the first attempt (which happens
|
||||
transiently when a freshly-booted sim node needs a moment to settle
|
||||
after the harness interface connects), retry up to *retries*
|
||||
additional times after *retry_delay* seconds.
|
||||
"""
|
||||
cli = resolve_cli()
|
||||
argv = [*_shlex_split(cli)]
|
||||
argv.extend(["--host", f"localhost:{port}"])
|
||||
argv.extend(args)
|
||||
logger.debug("run_cli: %s", argv)
|
||||
|
||||
last_out = ""
|
||||
for attempt in range(retries + 1):
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
argv,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
timeout=timeout,
|
||||
check=False,
|
||||
)
|
||||
except subprocess.TimeoutExpired as ex:
|
||||
last_out = ex.stdout.decode("utf-8", errors="replace") if ex.stdout else ""
|
||||
if attempt < retries:
|
||||
time.sleep(retry_delay)
|
||||
continue
|
||||
return 124, last_out
|
||||
|
||||
out = proc.stdout.decode("utf-8", errors="replace")
|
||||
rc = proc.returncode
|
||||
|
||||
# Retry on transient connection-refused / timed-out errors that
|
||||
# are common right after a sim node spins up.
|
||||
transient = (
|
||||
"Error connecting" in out
|
||||
or "Timed out waiting for connection" in out
|
||||
or "Connection reset by peer" in out
|
||||
)
|
||||
if rc != 0 and transient and attempt < retries:
|
||||
logger.debug("run_cli: transient failure, retry %d/%d", attempt + 1, retries)
|
||||
time.sleep(retry_delay)
|
||||
last_out = out
|
||||
continue
|
||||
return rc, out
|
||||
return rc, last_out
|
||||
|
||||
|
||||
def _shlex_split(cmd: str) -> List[str]:
|
||||
"""Split a shell string into argv, honoring quotes."""
|
||||
return shlex.split(cmd)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fresh-connection state verification
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _wait_for_port(port: int, timeout: float = 30.0) -> None:
|
||||
"""Wait until *port* accepts a TCP connection (firmware sim comes up
|
||||
or comes back after a config-commit reboot)."""
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
s = socket.create_connection(("localhost", port), timeout=0.5)
|
||||
s.close()
|
||||
return
|
||||
except OSError:
|
||||
time.sleep(0.2)
|
||||
raise TimeoutError(
|
||||
f"port {port} did not accept connections within {timeout}s"
|
||||
)
|
||||
|
||||
|
||||
def connect_iface(
|
||||
port: int,
|
||||
no_nodes: bool = False,
|
||||
retries: int = 4,
|
||||
wait_timeout: float = 30.0,
|
||||
) -> TCPInterface:
|
||||
"""Open a fresh ``TCPInterface`` to *port* and block on the config exchange.
|
||||
|
||||
Firmware config writes (``writeChannel``, ``--seturl``, ``--factory_reset``)
|
||||
can briefly restart the sim's TCP listener. We wait for the port to
|
||||
come up first, then retry the connect+config-exchange a few times.
|
||||
"""
|
||||
last_exc: Optional[Exception] = None
|
||||
for attempt in range(retries + 1):
|
||||
try:
|
||||
_wait_for_port(port, timeout=wait_timeout)
|
||||
return TCPInterface(
|
||||
hostname="localhost",
|
||||
portNumber=port,
|
||||
connectNow=True,
|
||||
noNodes=no_nodes,
|
||||
)
|
||||
except Exception as ex: # pylint: disable=broad-except
|
||||
last_exc = ex
|
||||
if attempt < retries:
|
||||
logger.debug(
|
||||
"connect_iface attempt %d/%d failed: %s",
|
||||
attempt + 1, retries, ex,
|
||||
)
|
||||
time.sleep(0.5)
|
||||
continue
|
||||
raise
|
||||
assert last_exc is not None # type guard; unreachable
|
||||
raise last_exc # pragma: no cover
|
||||
|
||||
|
||||
def verify_state(
|
||||
port: int,
|
||||
verifier: Callable[[TCPInterface], None],
|
||||
*,
|
||||
no_nodes: bool = False,
|
||||
) -> None:
|
||||
"""Open a fresh interface and run *verifier(iface)*, then close.
|
||||
|
||||
Used after a CLI mutation to verify firmware state through the
|
||||
library. Always closes the interface so the next test starts clean.
|
||||
"""
|
||||
iface = connect_iface(port, no_nodes=no_nodes)
|
||||
try:
|
||||
verifier(iface)
|
||||
finally:
|
||||
try:
|
||||
iface.close()
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
time.sleep(PAUSE_AFTER_CLI)
|
||||
|
||||
|
||||
def cli_then_verify(
|
||||
port: int,
|
||||
cli_args: List[str],
|
||||
verifier: Optional[Callable[[TCPInterface], None]],
|
||||
*,
|
||||
expect_rc: Optional[int] = 0,
|
||||
no_nodes: bool = False,
|
||||
cli_timeout: int = 60,
|
||||
) -> str:
|
||||
"""Run *cli_args* against *port*, optionally asserting *expect_rc*,
|
||||
then (if *verifier* is not None) open a fresh interface and run
|
||||
*verifier(iface)* against the just-mutated firmware state.
|
||||
|
||||
Returns the CLI stdout.
|
||||
"""
|
||||
rc, out = run_cli(port, *cli_args, timeout=cli_timeout)
|
||||
if expect_rc is not None:
|
||||
assert rc == expect_rc, f"CLI rc={rc} (expected {expect_rc}): {out}"
|
||||
time.sleep(PAUSE_AFTER_CLI)
|
||||
if verifier is not None:
|
||||
verify_state(port, verifier, no_nodes=no_nodes)
|
||||
return out
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Packet collectors (used by smokemesh receive-verification tests)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
RECEIVE_TIMEOUT = 15.0
|
||||
|
||||
|
||||
class PacketCollector:
|
||||
"""Collect packets received on a specific interface via pubsub.
|
||||
|
||||
``Listener`` (pubsub 4.x) wraps handlers with a weak reference, so
|
||||
we keep a strong reference to ``handler`` on the instance to prevent
|
||||
garbage collection before the publishing thread gets to call it.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.packets: List[dict] = []
|
||||
self._handler: Optional[Callable] = None
|
||||
|
||||
def on_receive(self, packet, interface): # pylint: disable=unused-argument
|
||||
"""Append a received packet to the internal list."""
|
||||
self.packets.append(packet)
|
||||
|
||||
def wait_for(self, count: int, timeout: float = RECEIVE_TIMEOUT) -> bool:
|
||||
"""Wait until *count* packets have been collected or *timeout* expires."""
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
if len(self.packets) >= count:
|
||||
return True
|
||||
time.sleep(0.2)
|
||||
return len(self.packets) >= count
|
||||
|
||||
@property
|
||||
def texts(self) -> List[str]:
|
||||
"""Return text payloads from TEXT_MESSAGE_APP packets."""
|
||||
return [
|
||||
p.get("decoded", {}).get("text", "")
|
||||
for p in self.packets
|
||||
if p.get("decoded", {}).get("portnum") == "TEXT_MESSAGE_APP"
|
||||
]
|
||||
|
||||
@property
|
||||
def traceroutes(self) -> List[dict]:
|
||||
"""Return TRACEROUTE_APP packets."""
|
||||
return [
|
||||
p for p in self.packets
|
||||
if p.get("decoded", {}).get("portnum") == "TRACEROUTE_APP"
|
||||
]
|
||||
|
||||
@property
|
||||
def telemetries(self) -> List[dict]:
|
||||
"""Return TELEMETRY_APP packets."""
|
||||
return [
|
||||
p for p in self.packets
|
||||
if p.get("decoded", {}).get("portnum") == "TELEMETRY_APP"
|
||||
]
|
||||
|
||||
@property
|
||||
def positions(self) -> List[dict]:
|
||||
"""Return POSITION_APP packets."""
|
||||
return [
|
||||
p for p in self.packets
|
||||
if p.get("decoded", {}).get("portnum") == "POSITION_APP"
|
||||
]
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Clear all collected packets."""
|
||||
self.packets.clear()
|
||||
|
||||
|
||||
def _subscribe_topic(
|
||||
iface: TCPInterface, topic: str
|
||||
) -> PacketCollector:
|
||||
"""Internal: subscribe a fully-qualified *topic* and return a collector.
|
||||
|
||||
Filters by *interface* so multi-node tests can subscribe several
|
||||
collectors concurrently without cross-talk.
|
||||
"""
|
||||
collector = PacketCollector()
|
||||
|
||||
def handler(packet, interface):
|
||||
if interface is iface:
|
||||
collector.on_receive(packet, interface)
|
||||
|
||||
pub.subscribe(handler, topic)
|
||||
collector._handler = handler # strong ref; see PacketCollector docstring
|
||||
return collector
|
||||
|
||||
|
||||
def subscribe_texts(iface: TCPInterface) -> PacketCollector:
|
||||
"""Subscribe to ``meshtastic.receive.text`` filtered to *iface*."""
|
||||
return _subscribe_topic(iface, "meshtastic.receive.text")
|
||||
|
||||
|
||||
def subscribe_traceroutes(iface: TCPInterface) -> PacketCollector:
|
||||
"""Subscribe to ``meshtastic.receive.traceroute`` filtered to *iface*."""
|
||||
return _subscribe_topic(iface, "meshtastic.receive.traceroute")
|
||||
|
||||
|
||||
def subscribe_telemetries(iface: TCPInterface) -> PacketCollector:
|
||||
"""Subscribe to ``meshtastic.receive.telemetry`` filtered to *iface*."""
|
||||
return _subscribe_topic(iface, "meshtastic.receive.telemetry")
|
||||
|
||||
|
||||
def subscribe_positions(iface: TCPInterface) -> PacketCollector:
|
||||
"""Subscribe to ``meshtastic.receive.position`` filtered to *iface*."""
|
||||
return _subscribe_topic(iface, "meshtastic.receive.position")
|
||||
|
||||
|
||||
def set_region(port: int, region: str = "US") -> None:
|
||||
"""Set the LoRa region on a sim node via the CLI.
|
||||
|
||||
The node briefly restarts its TCP listener while committing the radio
|
||||
config; ``run_cli()`` handles the reconnect retry, and we sleep long
|
||||
enough for the new region to take effect before callers continue.
|
||||
"""
|
||||
rc, out = run_cli(port, "--set", "lora.region", region)
|
||||
if rc != 0:
|
||||
raise RuntimeError(f"Failed to set lora.region={region}: {out}")
|
||||
time.sleep(PAUSE_AFTER_REGION_CHANGE)
|
||||
|
||||
|
||||
def unsubscribe_all(topic: str) -> None:
|
||||
"""Drop every handler currently registered on *topic*.
|
||||
|
||||
Tests use this in a ``finally`` block to keep pubsub clean across
|
||||
the function-scoped mesh fixtures.
|
||||
"""
|
||||
try:
|
||||
pub.unsubAll(topic)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
|
||||
|
||||
__all__ = [
|
||||
"PAUSE_AFTER_CLI",
|
||||
"PAUSE_AFTER_REGION_CHANGE",
|
||||
"PacketCollector",
|
||||
"RECEIVE_TIMEOUT",
|
||||
"cli_then_verify",
|
||||
"connect_iface",
|
||||
"resolve_cli",
|
||||
"run_cli",
|
||||
"set_region",
|
||||
"subscribe_positions",
|
||||
"subscribe_telemetries",
|
||||
"subscribe_texts",
|
||||
"subscribe_traceroutes",
|
||||
"unsubscribe_all",
|
||||
"verify_state",
|
||||
]
|
||||
@@ -6,7 +6,11 @@ import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from meshtastic.analysis.__main__ import main
|
||||
try:
|
||||
# Depends upon matplotlib & other packages in poetry's analysis group, not installed by default
|
||||
from meshtastic.analysis.__main__ import main
|
||||
except ImportError:
|
||||
pytest.skip("Can't import meshtastic.analysis", allow_module_level=True)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
|
||||
69
meshtastic/tests/test_ble_interface.py
Normal file
69
meshtastic/tests/test_ble_interface.py
Normal file
@@ -0,0 +1,69 @@
|
||||
"""Meshtastic unit tests for ble_interface.py"""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from bleak.exc import BleakError
|
||||
|
||||
from ..ble_interface import BLEInterface
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_ble_error_default_kind_unknown():
|
||||
"""BLEError defaults to UNKNOWN kind."""
|
||||
error = BLEInterface.BLEError("test")
|
||||
assert error.kind == BLEInterface.BLEError.UNKNOWN
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_ble_find_device_not_found_sets_kind():
|
||||
"""find_device emits DEVICE_NOT_FOUND for no scan results."""
|
||||
iface = object.__new__(BLEInterface)
|
||||
with patch("meshtastic.ble_interface.BLEInterface.scan", return_value=[]):
|
||||
with pytest.raises(BLEInterface.BLEError) as excinfo:
|
||||
iface.find_device("missing")
|
||||
assert excinfo.value.kind == BLEInterface.BLEError.DEVICE_NOT_FOUND
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_ble_find_device_multiple_sets_kind():
|
||||
"""find_device emits MULTIPLE_DEVICES for ambiguous matches."""
|
||||
iface = object.__new__(BLEInterface)
|
||||
first = MagicMock()
|
||||
first.name = "dup"
|
||||
first.address = "AA:AA:AA:AA:AA:01"
|
||||
second = MagicMock()
|
||||
second.name = "dup"
|
||||
second.address = "AA:AA:AA:AA:AA:02"
|
||||
with patch(
|
||||
"meshtastic.ble_interface.BLEInterface.scan", return_value=[first, second]
|
||||
):
|
||||
with pytest.raises(BLEInterface.BLEError) as excinfo:
|
||||
iface.find_device("dup")
|
||||
assert excinfo.value.kind == BLEInterface.BLEError.MULTIPLE_DEVICES
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_ble_send_to_radio_wraps_write_errors_with_kind():
|
||||
"""_sendToRadioImpl wraps write failures with WRITE_ERROR."""
|
||||
iface = object.__new__(BLEInterface)
|
||||
iface.client = MagicMock()
|
||||
iface.client.write_gatt_char.side_effect = RuntimeError("boom")
|
||||
to_radio = MagicMock()
|
||||
to_radio.SerializeToString.return_value = b"\x01"
|
||||
with pytest.raises(BLEInterface.BLEError) as excinfo:
|
||||
iface._sendToRadioImpl(to_radio)
|
||||
assert excinfo.value.kind == BLEInterface.BLEError.WRITE_ERROR
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_ble_receive_wraps_unexpected_bleak_error_with_kind():
|
||||
"""_receiveFromRadioImpl wraps unexpected BleakError with READ_ERROR."""
|
||||
iface = object.__new__(BLEInterface)
|
||||
iface.should_read = True
|
||||
iface._want_receive = True
|
||||
iface.client = MagicMock()
|
||||
iface.client.read_gatt_char.side_effect = BleakError("some other BLE failure")
|
||||
with pytest.raises(BLEInterface.BLEError) as excinfo:
|
||||
iface._receiveFromRadioImpl()
|
||||
assert excinfo.value.kind == BLEInterface.BLEError.READ_ERROR
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user