Compare commits

...

242 Commits

Author SHA1 Message Date
mkinney
b864bbdd5f Update setup.py 2022-01-29 21:48:57 -08:00
mkinney
5a740eddc3 Merge pull request #250 from mkinney/bugfix_for_owner_short
fix bug
2022-01-29 21:48:17 -08:00
Mike Kinney
fdced6f225 fix bug 2022-01-29 21:37:34 -08:00
mkinney
3d772845f9 Merge pull request #249 from mkinney/device_detection
Device detection
2022-01-29 21:11:40 -08:00
Mike Kinney
72326d467e only show active windows ports 2022-01-29 20:53:09 -08:00
Mike Kinney
a0944961b5 fix lint warnings 2022-01-29 20:47:58 -08:00
Mike Kinney
1fa61ece93 add windows device and port detection 2022-01-29 20:45:29 -08:00
Mike Kinney
288d0bb884 add detection on mac 2022-01-29 17:12:36 -08:00
Mike Kinney
28a2aa47e8 add device and port detection on linux 2022-01-30 00:57:56 +00:00
mkinney
069056edad Update setup.py 2022-01-27 17:12:50 -08:00
github-actions
19bd510975 Update protobuf submodule 2022-01-28 01:12:41 +00:00
mkinney
7979efc0a1 Merge pull request #247 from mkinney/remote_hardware
remote hardware tests
2022-01-27 17:11:37 -08:00
Mike Kinney
66866a4c65 fix logic 2022-01-27 17:09:13 -08:00
Mike Kinney
e6fb066fe5 get last couple of lines covered in remote_hardware 2022-01-27 17:05:16 -08:00
Mike Kinney
5841979566 remove line 2022-01-27 17:00:53 -08:00
Mike Kinney
529f50edc6 remote hardware tests 2022-01-27 16:54:47 -08:00
github-actions
5895e8fb4d Update protobuf submodule 2022-01-27 14:21:10 +00:00
mkinney
49dcf71116 Update setup.py 2022-01-26 20:32:45 -08:00
Jm Casler
5778552380 updating proto submodule to latest 2022-01-26 13:35:48 -08:00
Jm Casler
592ecc9997 updating proto submodule to latest 2022-01-26 12:27:52 -08:00
mkinney
1aaa205cc9 Merge pull request #246 from mkinney/update_help
add more info on --ch-set help
2022-01-26 11:22:05 -08:00
Mike Kinney
ff5a0927fa add more info on --ch-set help 2022-01-26 11:16:01 -08:00
mkinney
607127d46e Update regen-protos.sh 2022-01-23 15:15:14 -08:00
mkinney
cf61a5d39d Update update_protobufs.yml 2022-01-23 15:09:44 -08:00
mkinney
69b2599abb Update setup.py 2022-01-23 14:51:38 -08:00
mkinney
533e50de9d Update release.yml 2022-01-23 14:51:21 -08:00
mkinney
12b99f80dc Update release.yml 2022-01-23 14:48:59 -08:00
mkinney
0193ca052f Update setup.py 2022-01-23 14:47:26 -08:00
mkinney
e0af620cbe Merge pull request #245 from mkinney/standalone_readme
add readme.txt to release for standalone zip files
2022-01-23 14:47:06 -08:00
Mike Kinney
80367232ae add readme.txt to release for standalone zip files 2022-01-23 14:44:59 -08:00
mkinney
1c480bd74f Update setup.py 2022-01-23 14:25:01 -08:00
mkinney
ba05de263c Update release.yml 2022-01-23 14:24:48 -08:00
mkinney
f045c3bc2b Update setup.py 2022-01-23 14:17:05 -08:00
mkinney
9ec5f33a85 Update release.yml 2022-01-23 14:16:50 -08:00
mkinney
e1cc657059 Update setup.py 2022-01-23 14:10:58 -08:00
mkinney
9c3dce4bbb Update release.yml 2022-01-23 14:10:43 -08:00
mkinney
5418539275 Update setup.py 2022-01-23 14:04:51 -08:00
mkinney
3103640688 Update release.yml 2022-01-23 14:04:36 -08:00
mkinney
86beb2f212 Update setup.py 2022-01-23 14:03:05 -08:00
mkinney
dd1c96fcba Update release.yml 2022-01-23 14:02:48 -08:00
mkinney
b282baadb7 Update setup.py 2022-01-23 13:55:08 -08:00
mkinney
fccb38fced Update release.yml 2022-01-23 13:54:53 -08:00
mkinney
700a7e27d3 Update setup.py 2022-01-23 13:51:06 -08:00
mkinney
bb1c40c7f8 Update release.yml 2022-01-23 13:50:49 -08:00
mkinney
407490c654 Update setup.py 2022-01-23 13:38:54 -08:00
mkinney
dfd61af346 Update release.yml 2022-01-23 13:38:36 -08:00
mkinney
a85b4ad985 Update setup.py 2022-01-23 13:35:02 -08:00
mkinney
eacffa79fa Update release.yml 2022-01-23 13:34:47 -08:00
mkinney
ca5449f1b8 Update release.yml 2022-01-23 13:21:50 -08:00
mkinney
6751f5bd53 Update release.yml 2022-01-23 13:12:17 -08:00
mkinney
c5c70fe83e Update setup.py 2022-01-23 13:10:06 -08:00
mkinney
0c0cd81253 Update release.yml 2022-01-23 13:09:40 -08:00
mkinney
553a03e40f Update setup.py 2022-01-23 12:53:51 -08:00
mkinney
b50190aeb0 Update release.yml 2022-01-23 12:53:32 -08:00
mkinney
bc231a8a9d Update setup.py 2022-01-23 12:34:36 -08:00
mkinney
c1c9cb8139 Update release.yml 2022-01-23 12:34:12 -08:00
mkinney
0a915e305d Update setup.py 2022-01-23 12:09:55 -08:00
mkinney
308eb1f321 Update release.yml 2022-01-23 12:09:36 -08:00
mkinney
d576c0e30b Update setup.py 2022-01-23 12:01:41 -08:00
mkinney
e63d4c3a30 Update release.yml 2022-01-23 12:01:17 -08:00
mkinney
2041e81101 Update setup.py 2022-01-23 11:49:04 -08:00
mkinney
f396374fa8 Update release.yml 2022-01-23 11:48:13 -08:00
mkinney
1a0db63f32 Merge pull request #244 from mkinney/release_improvements
improve the release process
2022-01-23 11:44:33 -08:00
Mike Kinney
0d32a83e66 improve the release process 2022-01-23 11:41:49 -08:00
mkinney
7ac3328db8 Merge pull request #243 from mkinney/short_name
Short name
2022-01-23 11:25:06 -08:00
Mike Kinney
4f72987a29 add some config tests 2022-01-23 11:22:05 -08:00
Mike Kinney
aa5af53348 add option to configure just the owner short name 2022-01-23 11:17:41 -08:00
Jm Casler
030ee8554c updating proto submodule to latest 2022-01-23 09:10:37 -08:00
mkinney
80d447d64b Update setup.py 2022-01-21 17:15:26 -08:00
mkinney
bfac5a418c Merge pull request #239 from meshtastic/update-protos-for-shutdown
Shutdown command (should mirror reboot)
2022-01-21 17:05:26 -08:00
mkinney
a3b09f028a Merge pull request #242 from mkinney/help_update_protos_for_shutdown
ignore new pb file
2022-01-21 17:00:19 -08:00
Mike Kinney
930292aaad ignore new pb file 2022-01-21 16:59:17 -08:00
mkinney
1d3b59a7e0 Merge pull request #240 from mkinney/help_update_protos_for_shutdown
update protos
2022-01-21 16:56:59 -08:00
Mike Kinney
d54b518bfd update protos 2022-01-21 16:55:09 -08:00
Ben Meadors
ccb8bb83a6 Consistent spacing 2022-01-21 18:33:09 -06:00
Ben Meadors
076e865f77 Node level test 2022-01-21 18:20:50 -06:00
Ben Meadors
8ceb9faf2d Shutdown command 2022-01-21 18:11:26 -06:00
github-actions
b15e5f516a Update protobuf submodule 2022-01-21 23:58:36 +00:00
github-actions
e161d75986 Update protobuf submodule 2022-01-20 22:18:17 +00:00
mkinney
084473a340 Update README.md
add pypi downloads
2022-01-20 10:10:42 -08:00
Jm Casler
74d6d4d634 updating proto submodule to latest 2022-01-19 18:18:59 -08:00
github-actions
38ac4298a0 Update protobuf submodule 2022-01-19 23:56:04 +00:00
mkinney
9b2c0e7954 Merge pull request #237 from mkinney/master
regen proto from updated protobufs
2022-01-16 17:06:35 -08:00
Mike Kinney
62472034f1 regen proto from updated protobufs 2022-01-17 00:26:31 +00:00
github-actions
13cfc19841 Update protobuf submodule 2022-01-17 00:21:35 +00:00
mkinney
4955ab8df2 Update setup.py 2022-01-16 12:48:35 -08:00
mkinney
de39c98e50 Merge pull request #236 from mkinney/fix_enum_listing
fix enum listing; add tests for pref values
2022-01-16 12:47:49 -08:00
Mike Kinney
51378bb0eb fix enum listing; add tests for pref values 2022-01-16 12:45:28 -08:00
mkinney
aff3bdd78e Update setup.py 2022-01-15 14:19:36 -08:00
mkinney
e9a8e26e76 Merge pull request #235 from mkinney/fix_for_setPref
looks like we needed some of that dead code after all
2022-01-15 14:19:07 -08:00
Mike Kinney
83439679c1 looks like we needed some of that dead code after all 2022-01-15 14:15:55 -08:00
mkinney
968027a439 Update setup.py 2022-01-15 10:38:40 -08:00
mkinney
cc24b6ebc5 Merge pull request #231 from mkinney/camelCase
handle snake_case or camelCase
2022-01-15 10:32:08 -08:00
Mike Kinney
db09b4718d add two more lines to code coverage 2022-01-15 10:21:24 -08:00
Jm Casler
e0edbc6288 updating proto submodule to latest 2022-01-15 09:34:14 -08:00
Mike Kinney
ae9ae91af5 remove dead code 2022-01-15 00:24:41 -08:00
Mike Kinney
7921db007b add some coverage to getPref() and setPref() 2022-01-15 00:01:44 -08:00
Mike Kinney
e85af2f9e9 Merge branch 'camelCase' of github.com:mkinney/Meshtastic-python into camelCase 2022-01-14 20:16:26 -08:00
Mike Kinney
8ccc64f92e Merge remote-tracking branch 'upstream/master' into camelCase 2022-01-14 20:16:10 -08:00
Mike Kinney
afb21c6dc3 remove code not needed 2022-01-14 20:15:25 -08:00
mkinney
3291bc7097 Merge branch 'meshtastic:master' into camelCase 2022-01-14 20:06:40 -08:00
Mike Kinney
a7d56504be handle snake_case or camelCase 2022-01-14 16:36:53 -08:00
Jm Casler
90e5b473d9 updating proto submodule to latest 2022-01-13 16:55:22 -08:00
mkinney
52834e9966 Update setup.py 2022-01-12 17:25:00 -08:00
mkinney
63c60d4cea Merge pull request #229 from mkinney/check_for_multiple_mac_conversions
suggested fix from MitchConner912 for not converting mac address more…
2022-01-12 17:13:14 -08:00
Mike Kinney
6a2a9d2093 suggested fix from MitchConner912 for not converting mac address more than once 2022-01-12 17:10:51 -08:00
mkinney
1410448808 Merge pull request #228 from mkinney/more_unit_tests
add more unit tests
2022-01-12 17:06:24 -08:00
Mike Kinney
ad8f2222db cover a few more lines 2022-01-12 16:50:29 -08:00
Mike Kinney
48265e73b1 no need to import pygatt here 2022-01-12 15:54:07 -08:00
Mike Kinney
f3139a8aa0 add more unit tests 2022-01-12 15:50:16 -08:00
mkinney
8d68e36703 Merge pull request #227 from mkinney/pylint_fixes
fix the consider-using-f-string warnings
2022-01-12 14:57:50 -08:00
Mike Kinney
d2d93fbe80 no longer need to disable this pylint warning 2022-01-12 14:50:10 -08:00
Mike Kinney
ed8510468d no need to disable these pylint warnings anymore 2022-01-12 14:47:49 -08:00
Mike Kinney
e7680e07c2 fix pylint global-statement warnings 2022-01-12 14:46:21 -08:00
Mike Kinney
0f89baa36e fix the pylint unused-argument warnings 2022-01-12 14:41:49 -08:00
Mike Kinney
48ed7690af fix the consider-using-f-string warnings 2022-01-12 13:46:01 -08:00
mkinney
59b94ea650 Merge pull request #226 from mkinney/add_get_hw_example
add example how you can get the hwModel using api
2022-01-12 10:59:43 -08:00
Mike Kinney
5b992734fb add example how you can get the hwModel using api 2022-01-12 10:57:03 -08:00
mkinney
3b74b911f8 Merge pull request #224 from mkinney/add_set_owner_example
simple example showing how you can set the long and short name
2022-01-12 10:26:22 -08:00
Mike Kinney
b6570e3c27 simple example showing how you can set the long and short name 2022-01-12 10:23:51 -08:00
mkinney
e1e1664b96 Merge pull request #223 from mkinney/make_pygatt_linux_only
make pygatt linux only in requirements.txt
2022-01-12 10:23:06 -08:00
Mike Kinney
cb1913dfc3 make pygatt linux only in requirements.txt 2022-01-12 10:15:06 -08:00
Sacha Weatherstone
b813a6f8c5 Update requirements.txt 2022-01-12 14:39:20 +11:00
mkinney
0b662318e1 Merge pull request #222 from mkinney/testing_on_mac_air
fixes for working on mac air
2022-01-11 16:48:01 -08:00
Mike Kinney
a6ccc1a246 add conditional lib if linux 2022-01-11 16:42:25 -08:00
Mike Kinney
bc17e9b389 fixes for working on mac air 2022-01-11 16:36:39 -08:00
mkinney
b08e96b66a Update setup.py
bump version for release
2022-01-11 11:02:26 -08:00
mkinney
9e74ead54e Merge pull request #219 from mkinney/smoke_virt
add smoke test for virtual device
2022-01-11 10:56:33 -08:00
Mike Kinney
2cf52f3df6 add a convenience target in Makefile for running the smokevirt test 2022-01-11 18:50:18 +00:00
Mike Kinney
50e9a0a9f7 exclude smokevirt from ci 2022-01-11 18:44:39 +00:00
Mike Kinney
841c44e05c add smoke test for virtual device 2022-01-11 18:39:49 +00:00
mkinney
465bafeb30 Merge pull request #218 from mkinney/fix_remote_admin_message
refactor code to only call local node when necessary; fix tests
2022-01-10 16:51:04 -08:00
Mike Kinney
cb8dafbd31 add more coverage 2022-01-10 16:40:47 -08:00
Mike Kinney
52db617b06 refactor code to only call local node when necessary; fix tests 2022-01-10 16:20:11 -08:00
Jm Casler
ec622590da updating proto submodule to latest 2022-01-09 22:23:07 -08:00
mkinney
345cb1bdc4 Merge pull request #217 from mkinney/fix_remote_admin_message
Fix remote admin message
2022-01-06 12:36:03 -08:00
Mike Kinney
8ca692a26e cannot call os.getlogin() on github instances 2022-01-06 12:33:30 -08:00
Mike Kinney
d1ea68d7dc add code coverage to recently added code 2022-01-06 12:17:42 -08:00
Mike Kinney
b56440a4e8 should not need to talk with remote node if just doing sendtext 2022-01-06 11:55:36 -08:00
github-actions
1401b949a3 Update protobuf submodule 2022-01-06 17:09:45 +00:00
mkinney
97f3ce6198 Merge pull request #215 from mkinney/combine_build_single_executables
combine the building of single executables into one action
2022-01-06 00:25:46 -08:00
Mike Kinney
8019391914 combine the building of single executables into one action 2022-01-06 00:24:59 -08:00
mkinney
8d10010ab1 Merge pull request #214 from mkinney/more_testing_stuff
move slower unit tests to unitslow
2022-01-06 00:00:42 -08:00
mkinney
a2b4d2a96a Update build_mac.yml 2022-01-05 23:47:02 -08:00
mkinney
4de558bdf6 Update build_mac.yml 2022-01-05 23:40:49 -08:00
mkinney
8ff06a0de1 Update build_mac.yml 2022-01-05 23:37:35 -08:00
mkinney
f8ad6061c1 Update build_mac.yml
revert
2022-01-05 23:06:49 -08:00
mkinney
aa4cdb6aea Update build_mac.yml 2022-01-05 23:01:13 -08:00
Mike Kinney
cba424fde0 move slower unit tests to unitslow 2022-01-05 21:12:50 -08:00
mkinney
6c7a870645 Merge pull request #213 from mkinney/add_python_310
check python v3.10 as well
2022-01-05 18:20:55 -08:00
Mike Kinney
f42b1ad4e0 check python v3.10 as well 2022-01-05 18:18:31 -08:00
mkinney
30a51952e0 Merge pull request #212 from mkinney/serial_perms
improve the permission error on linux
2022-01-05 15:08:24 -08:00
Mike Kinney
5de754c5ab improve the permission error on linux 2022-01-05 23:03:06 +00:00
mkinney
bda446c7b5 Update build_mac.yml 2022-01-05 14:13:42 -08:00
mkinney
f79540f197 Update build_mac.yml 2022-01-05 14:09:17 -08:00
mkinney
d507697e56 Merge pull request #210 from mkinney/work_on_single_executable
install meshtastic before building single executable
2022-01-05 13:58:47 -08:00
Mike Kinney
acbc1f2e30 install meshtastic before building single executable 2022-01-05 13:56:07 -08:00
mkinney
7b3c68119c Merge pull request #209 from mkinney/add_modules_for_building_single_executables
collect the meshtastic libs
2022-01-05 13:36:25 -08:00
Mike Kinney
39f97166b0 collect the meshtastic libs 2022-01-05 13:34:28 -08:00
mkinney
541d19cafb Merge pull request #208 from mkinney/create_actions_for_building_single_executables
Create actions for building single executables
2022-01-05 13:26:47 -08:00
Mike Kinney
969a81b779 Merge remote-tracking branch 'upstream/master' into create_actions_for_building_single_executables 2022-01-05 13:24:33 -08:00
Mike Kinney
3f76c1efb0 refactor version info so pyinstaller will work; add build mac and ubuntu standalone executables 2022-01-05 13:22:37 -08:00
mkinney
774849189f Merge pull request #207 from mkinney/create_build_windows_action
add a build windows action
2022-01-05 12:46:10 -08:00
Mike Kinney
53d626aa72 add a build windows action 2022-01-05 12:44:05 -08:00
mkinney
1a3a840269 Merge pull request #206 from mkinney/fully_qualify_imports
need to fully qualify imports so projects consuming the library will …
2022-01-05 11:24:13 -08:00
Mike Kinney
fe69f05e75 add python 3.6, 3.7, 3.8, and 3.9 for ci and validation 2022-01-05 11:20:04 -08:00
Mike Kinney
5c662822b9 need to fully qualify imports so projects consuming the library will work 2022-01-05 11:16:08 -08:00
mkinney
c049d3424a Merge pull request #205 from mkinney/remove_nested_keys
remove nested keys from nodes so we do not display garbage
2022-01-02 11:28:07 -08:00
Mike Kinney
471535853b bump version 2022-01-02 11:20:15 -08:00
Mike Kinney
676148cc14 meant to use decoded not decode 2022-01-02 11:19:17 -08:00
Mike Kinney
a915b05240 remove nested keys from nodes so we do not display garbage 2022-01-02 11:15:19 -08:00
Jm Casler
a1668e8c66 updating proto submodule to latest 2022-01-01 23:25:22 -08:00
mkinney
e7664cb40b Merge pull request #204 from mkinney/add_more_unit_tests
get last two lines covered in node
2022-01-01 15:51:21 -08:00
Mike Kinney
83c18f4008 working on more unit tests 2022-01-01 15:48:33 -08:00
Mike Kinney
8b6321ce7f add a few more tests 2022-01-01 15:21:53 -08:00
Mike Kinney
9fac981ba6 test heartbeatTimer 2022-01-01 14:53:57 -08:00
Mike Kinney
ccc71930f7 get last two lines covered in node 2022-01-01 13:25:36 -08:00
mkinney
9380f048fa Update setup.py
bump version
2022-01-01 11:11:54 -08:00
mkinney
0a655ac8df Merge pull request #203 from mkinney/minor_changes
do not print line for export; comment out ble test; do not send decoded
2022-01-01 09:51:34 -08:00
Mike Kinney
0b6676c5b3 do not print line for export; comment out ble test; do not send decoded 2022-01-01 09:49:21 -08:00
mkinney
e5ecba7ec0 Merge pull request #202 from mkinney/format_mac_address
if mac address is in nodes, format it like a valid mac address
2021-12-31 20:04:19 -08:00
Mike Kinney
a1809f5b84 if mac address is in nodes, format it like a valid mac address 2021-12-31 20:01:14 -08:00
mkinney
9c66447913 Merge pull request #201 from mkinney/more_testing
added tests for _getOrCreateByNum(), nodeNumToId(), and _fixupPositio…
2021-12-31 14:26:37 -08:00
Mike Kinney
65960fb982 added tests for _getOrCreateByNum(), nodeNumToId(), and _fixupPosition(); found/fixed bug on _fixupPosition 2021-12-31 13:43:37 -08:00
mkinney
9d0bc09e0f Merge pull request #200 from mkinney/tuning_tests
Tuning tests
2021-12-31 12:30:00 -08:00
Mike Kinney
475ddcc8dd add tests for _ipToNodeId() 2021-12-31 12:28:14 -08:00
Mike Kinney
105276f98e add unit tests for _shouldFilterPacket() 2021-12-31 12:17:04 -08:00
Mike Kinney
4ee647403b fix output on tests using pytest -s option; fixed some tests 2021-12-31 10:55:13 -08:00
Mike Kinney
10f48f130f move some unit tests to unitslow 2021-12-31 09:59:22 -08:00
mkinney
bd697864e4 Merge pull request #199 from mkinney/unit_testing_continues
revert the stream interface change; fix tunnel tests
2021-12-31 09:40:58 -08:00
Mike Kinney
ab876c9efd add unit test for findPorts() 2021-12-31 09:38:44 -08:00
Mike Kinney
aba303c677 figured out issue; had device connected to serial port; needed to patch; fixed tunnel test in main 2021-12-31 09:28:17 -08:00
Mike Kinney
43d59ca8d8 temp comment out tests that pass locally but not when run from CI 2021-12-31 08:53:17 -08:00
Mike Kinney
177705aeff revert the stream interface change; fix tunnel tests 2021-12-31 08:49:13 -08:00
Sacha Weatherstone
b92fff0da6 Create vercel.json 2021-12-31 20:46:21 +11:00
mkinney
6a6b72a2ae Merge pull request #198 from mkinney/keep_working_on_unit_tests
start to add unit tests for tunnel
2021-12-30 23:01:05 -08:00
Mike Kinney
614a90c0eb unit test a few more lines 2021-12-30 22:59:01 -08:00
Mike Kinney
9adbed4be6 add unit tests for onTunnelReceive() 2021-12-30 22:52:49 -08:00
Mike Kinney
809f005f61 add unit tests for ipstr(), hexstr(), and readnet_u16() 2021-12-30 22:26:26 -08:00
Mike Kinney
d366e74e86 refactor of Tunnel() for unit testing; create unit tests for Tunnel() 2021-12-30 21:24:32 -08:00
Mike Kinney
3f307880f9 add unit tests for tunnel and subnet 2021-12-30 20:04:32 -08:00
Mike Kinney
50523ec1b1 start to add unit tests for tunnel 2021-12-30 19:37:38 -08:00
mkinney
684b2885aa Merge pull request #197 from mkinney/work_on_unit_tests
add more tests; do not need the old --reply test
2021-12-30 12:28:36 -08:00
Mike Kinney
f5eb8738fb added unit tests for --ch-set and onNode() 2021-12-30 12:20:24 -08:00
Mike Kinney
14941c742a add more tests; do not need the old --reply test 2021-12-30 11:28:03 -08:00
mkinney
217add3b00 Update README.md
add code coverage badge
2021-12-30 09:32:18 -08:00
mkinney
4bac85b6a9 Update ci.yml 2021-12-30 09:21:17 -08:00
mkinney
36bed11959 Update publish_to_pypi.yml 2021-12-30 09:12:53 -08:00
mkinney
6f9bcfaaff Merge pull request #196 from mkinney/bump_version
bump version for testing pub to pypi
2021-12-30 09:06:54 -08:00
Mike Kinney
f17b66c872 bump version for testing pub to pypi 2021-12-30 09:05:26 -08:00
Sacha Weatherstone
040cb9bf34 Update and rename publish_to_pypi.py to publish_to_pypi.yml 2021-12-31 04:00:03 +11:00
mkinney
3269b3018f Merge pull request #195 from mkinney/publish_to_pypi
add workflow to publish to pypi
2021-12-30 08:49:26 -08:00
Mike Kinney
aab10b0912 add workflow to publish to pypi 2021-12-30 08:46:34 -08:00
mkinney
e2e9b7d55e Merge pull request #194 from mkinney/remove_raw_from_nodes_display
remove the raw key from the nodes dict
2021-12-30 08:28:36 -08:00
Mike Kinney
cecc5c3b25 remove the raw key from the nodes dict 2021-12-30 08:26:13 -08:00
mkinney
54bb846d00 Merge pull request #193 from mkinney/temp_disable_reply_unittest
need to comment out unittest with change to --reply
2021-12-30 07:51:15 -08:00
Mike Kinney
1a5f525632 need to comment out unittest with change to --reply 2021-12-30 07:49:34 -08:00
mkinney
8ba3d26d63 Merge pull request #191 from Beiri22/patch-1
Update __main__.py
2021-12-30 07:46:40 -08:00
Beiri22
b341b6cfdb Update __main__.py
Main loop also in reply mode.
2021-12-30 10:31:27 +01:00
mkinney
38e7972191 Merge pull request #189 from mkinney/master
bump version; remove docs dir; no need to regen docs on release
2021-12-29 22:02:01 -08:00
mkinney
5be70328fe Merge branch 'meshtastic:master' into master 2021-12-29 21:58:15 -08:00
Mike Kinney
dfe798dbdf no longer gen docs 2021-12-29 21:57:16 -08:00
Mike Kinney
d98b23dba0 remove docs dir 2021-12-29 21:56:03 -08:00
Mike Kinney
4fbe5c7863 bump version 2021-12-29 21:55:41 -08:00
Sacha Weatherstone
69bb8bcca2 Fx protobuf path 2021-12-30 16:52:25 +11:00
Sacha Weatherstone
bb2ea17371 Fix action name 2021-12-30 16:49:55 +11:00
Sacha Weatherstone
b51af8a070 Create update_protobufs 2021-12-30 16:48:20 +11:00
mkinney
aede26694c Merge pull request #188 from mkinney/work_on_serial_issue
revamp the serial connection to avoid Tbeam reboots
2021-12-29 21:41:59 -08:00
Mike Kinney
8e1010e9f2 ignore two checks for Windows smoke1 testing 2021-12-29 21:21:24 -08:00
Mike Kinney
ce2d9f5728 yet another attempt 2021-12-29 21:13:20 -08:00
Mike Kinney
9879e9f2df try yet another way 2021-12-29 21:00:22 -08:00
Mike Kinney
e79faf93d0 try a different way 2021-12-29 20:57:57 -08:00
Mike Kinney
181c04716a accidentally dropped an arg 2021-12-29 20:55:31 -08:00
Mike Kinney
c7d981ec35 fix typo 2021-12-29 20:51:46 -08:00
Mike Kinney
75fe7622a4 deal with windows on the serial issue 2021-12-29 20:49:19 -08:00
Mike Kinney
5dc800f9a3 deal with windows on smoke1 test 2021-12-29 20:39:34 -08:00
Mike Kinney
c7d3f9f787 close seriallog if we have one 2021-12-29 20:35:50 -08:00
Mike Kinney
c70d36d2cd revamp the serial connection to avoid Tbeam reboots 2021-12-29 19:24:26 -08:00
Jm Casler
f239c23d9a Merge branch 'master' of https://github.com/meshtastic/Meshtastic-python 2021-12-29 14:18:35 -08:00
Jm Casler
ac5d729cdf Bumped to 1.2.47. 2021-12-29 14:18:34 -08:00
mkinney
047f43534f Merge pull request #187 from mkinney/bump_version
bump version
2021-12-29 13:29:28 -08:00
Mike Kinney
c26e030f1f bump version 2021-12-29 13:28:08 -08:00
78 changed files with 5266 additions and 4133 deletions

View File

@@ -1,2 +1,6 @@
[run] [run]
omit = meshtastic/*_pb2.py,meshtastic/tests/*.py,meshtastic/test.py omit = meshtastic/*_pb2.py,meshtastic/tests/*.py,meshtastic/test.py
[report]
exclude_lines =
if __name__ == .__main__.:

View File

@@ -10,12 +10,18 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- "3.6"
- "3.7"
- "3.8"
- "3.9"
- "3.10"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install Python 3 - name: Install Python 3
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with:
python-version: 3.9
- name: Uninstall meshtastic - name: Uninstall meshtastic
run: | run: |
pip3 uninstall meshtastic pip3 uninstall meshtastic
@@ -32,14 +38,32 @@ jobs:
run: pylint meshtastic run: pylint meshtastic
- name: Run tests with pytest - name: Run tests with pytest
run: pytest --cov=meshtastic run: pytest --cov=meshtastic
- name: Generate coverage report
run: |
pytest --cov=meshtastic --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
yml: ./codecov.yml
fail_ci_if_error: true
validate: validate:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- "3.6"
- "3.7"
- "3.8"
- "3.9"
- "3.10"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install Python 3 - name: Install Python 3
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with:
python-version: 3.9
- name: Install meshtastic from local - name: Install meshtastic from local
run: | run: |
pip3 install . pip3 install .

179
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,179 @@
name: Make Release
on:
workflow_dispatch:
inputs:
version:
description: "Release version (Example: 1.0.0, must match 'version' in setup.py)"
required: true
default: '1.0.0'
jobs:
release_create:
runs-on: ubuntu-latest
steps:
- name: Create GitHub release
uses: actions/create-release@v1
id: create_release
with:
draft: true
prerelease: true
release_name: ${{ github.event.inputs.version}}
tag_name: ${{ github.event.inputs.version}}
body: |
Autogenerated by github action, developer should edit as required before publishing...
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
publish_to_pypi:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install pypa/build
run: >-
python -m
pip install
build
--user
- name: Build a binary wheel and a source tarball
run: >-
python -m
build
--sdist
--wheel
--outdir dist/
.
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@master
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
build-and-publish-mac:
runs-on: macos-latest
needs: release_create
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Setup code signing
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
MACOS_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
run: |
echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12
security create-keychain -p "$MACOS_KEYCHAIN_PASSWORD" meshtastic.keychain
security default-keychain -s meshtastic.keychain
security unlock-keychain -p "$MACOS_KEYCHAIN_PASSWORD" meshtastic.keychain
security import certificate.p12 -k meshtastic.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_KEYCHAIN_PASSWORD" meshtastic.keychain
- name: Build
env:
MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }}
run: |
pip install pyinstaller
pip install -r requirements.txt
pip install .
pyinstaller -F -n meshtastic --collect-all meshtastic --codesign-identity "$MACOS_SIGNING_IDENTITY" meshtastic/__main__.py
- name: Add mac to release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.release_create.outputs.upload_url }}
asset_path: dist/meshtastic
asset_name: meshtastic_mac
asset_content_type: application/zip
build-and-publish-ubuntu:
runs-on: ubuntu-latest
needs: release_create
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Build
run: |
pip install pyinstaller
pip install -r requirements.txt
pip install .
pyinstaller -F -n meshtastic --collect-all meshtastic meshtastic/__main__.py
- name: Add ubuntu to release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.release_create.outputs.upload_url }}
asset_path: dist/meshtastic
asset_name: meshtastic_ubuntu
asset_content_type: application/zip
- name: Add readme.txt to release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.release_create.outputs.upload_url }}
asset_path: standalone_readme.txt
asset_name: readme.txt
asset_content_type: text/plain
build-and-publish-windows:
runs-on: windows-latest
needs: release_create
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Build
run: |
pip install pyinstaller
pip install -r requirements.txt
pip install .
pyinstaller -F -n meshtastic --collect-all meshtastic meshtastic/__main__.py
- name: Add windows to release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.release_create.outputs.upload_url }}
asset_path: dist/meshtastic.exe
asset_name: meshtastic_windows
asset_content_type: application/zip

35
.github/workflows/update_protobufs.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: "Update protobufs"
on: workflow_dispatch
jobs:
update-protobufs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: true
- name: Update Submodule
run: |
git pull --recurse-submodules
git submodule update --remote --recursive
- name: Download nanopb
run: |
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.4-linux-x86.tar.gz
tar xvzf nanopb-0.4.4-linux-x86.tar.gz
mv nanopb-0.4.4-linux-x86 nanopb-0.4.4
- name: Re-generate protocol buffers
run: |
./bin/regen-protos.sh
- name: Commit update
run: |
git config --global user.name 'github-actions'
git config --global user.email 'bot@noreply.github.com'
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
git add proto
git commit -m "Update protobuf submodule" && git push || echo "No changes to commit"

1
.gitignore vendored
View File

@@ -14,3 +14,4 @@ venv/
.DS_Store .DS_Store
__pycache__ __pycache__
examples/__pycache__ examples/__pycache__
meshtastic.spec

View File

@@ -7,7 +7,7 @@
# Add files or directories matching the regex patterns to the blacklist. The # Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths. # regex matches against base names, not paths.
ignore-patterns=mqtt_pb2.py,channel_pb2.py,environmental_measurement_pb2.py,admin_pb2.py,radioconfig_pb2.py,deviceonly_pb2.py,apponly_pb2.py,remote_hardware_pb2.py,portnums_pb2.py,mesh_pb2.py,storeforward_pb2.py ignore-patterns=mqtt_pb2.py,channel_pb2.py,environmental_measurement_pb2.py,admin_pb2.py,radioconfig_pb2.py,deviceonly_pb2.py,apponly_pb2.py,remote_hardware_pb2.py,portnums_pb2.py,mesh_pb2.py,storeforward_pb2.py,cannedmessages_pb2.py
@@ -23,7 +23,7 @@ ignore-patterns=mqtt_pb2.py,channel_pb2.py,environmental_measurement_pb2.py,admi
# no Warning level messages displayed, use"--disable=all --enable=classes # no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W" # --disable=W"
# #
disable=invalid-name,fixme,logging-fstring-interpolation,too-many-statements,too-many-branches,too-many-locals,no-member,f-string-without-interpolation,protected-access,no-self-use,pointless-string-statement,too-few-public-methods,consider-using-f-string,broad-except,no-else-return,unused-argument,global-statement,global-variable-not-assigned,too-many-boolean-expressions,no-else-raise,bare-except disable=invalid-name,fixme,logging-fstring-interpolation,too-many-statements,too-many-branches,too-many-locals,no-member,f-string-without-interpolation,protected-access,no-self-use,pointless-string-statement,too-few-public-methods,broad-except,no-else-return,no-else-raise,bare-except
[BASIC] [BASIC]

View File

@@ -2,6 +2,10 @@
test: test:
pytest -m unit pytest -m unit
# only run the smoke tests against the virtual device
virt:
pytest -m smokevirt
# local install # local install
install: install:
pip install . pip install .
@@ -16,7 +20,7 @@ lint:
# show the slowest unit tests # show the slowest unit tests
slow: slow:
pytest --durations=0 pytest -m unit --durations=5
# run the coverage report and open results in a browser # run the coverage report and open results in a browser
cov: cov:

View File

@@ -2,6 +2,8 @@
[![Open in Visual Studio Code](https://open.vscode.dev/badges/open-in-vscode.svg)](https://open.vscode.dev/meshtastic/Meshtastic-python) [![Open in Visual Studio Code](https://open.vscode.dev/badges/open-in-vscode.svg)](https://open.vscode.dev/meshtastic/Meshtastic-python)
![Unit Tests](https://github.com/meshtastic/Meshtastic-python/actions/workflows/ci.yml/badge.svg) ![Unit Tests](https://github.com/meshtastic/Meshtastic-python/actions/workflows/ci.yml/badge.svg)
[![codecov](https://codecov.io/gh/meshtastic/Meshtastic-python/branch/master/graph/badge.svg?token=TIWPJL73KV)](https://codecov.io/gh/meshtastic/Meshtastic-python)
![PyPI - Downloads](https://img.shields.io/pypi/dm/meshtastic)
A python client for using [Meshtastic](https://www.meshtastic.org) 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. A python client for using [Meshtastic](https://www.meshtastic.org) 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.

View File

@@ -5,11 +5,11 @@
# workaround for import bug in protoc https://github.com/protocolbuffers/protobuf/issues/1491#issuecomment-690618628 # workaround for import bug in protoc https://github.com/protocolbuffers/protobuf/issues/1491#issuecomment-690618628
if [[ $OSTYPE == 'darwin'* ]]; then if [[ $OSTYPE == 'darwin'* ]]; then
sed -i -E 's/^\(import.*_pb2\)/from . \1/' meshtastic/*.py sed -i '' -E 's/^\(import.*_pb2\)/from . \1/' meshtastic/*.py
# automate the current workaround (may be related to Meshtastic-protobufs issue #27 https://github.com/meshtastic/Meshtastic-protobufs/issues/27)
sed -i '' -E "s/^None = 0/globals()['None'] = 0/" meshtastic/mesh_pb2.py
else else
sed -i -E 's/^import.*_pb2/from . \0/' meshtastic/*.py sed -i -e 's/^import.*_pb2/from . \0/' meshtastic/*.py
# automate the current workaround (may be related to Meshtastic-protobufs issue #27 https://github.com/meshtastic/Meshtastic-protobufs/issues/27)
sed -i -e "s/^None = 0/globals()['None'] = 0/" meshtastic/mesh_pb2.py
fi fi
# automate the current workaround (may be related to Meshtastic-protobufs issue #27 https://github.com/meshtastic/Meshtastic-protobufs/issues/27)
sed -i '' -e "s/^None = 0/globals()['None'] = 0/" meshtastic/mesh_pb2.py

View File

@@ -1,7 +1,5 @@
rm dist/* rm dist/*
set -e set -e
bin/regen-docs.sh
pandoc --from=markdown --to=rst --output=README README.md
python3 setup.py sdist bdist_wheel python3 setup.py sdist bdist_wheel
python3 -m twine upload dist/* python3 -m twine upload dist/*

17
exampleConfig.yaml Normal file
View File

@@ -0,0 +1,17 @@
# example config using camelCase keys
owner: Bob TBeam
ownerShort: BOB
channelUrl: https://www.meshtastic.org/d/#CgUYAyIBAQ
location:
lat: 35.88888
lon: -93.88888
alt: 304
userPrefs:
region: 1
isAlwaysPowered: 'true'
sendOwnerInterval: 2
screenOnSecs: 31536000
waitBluetoothSecs: 31536000

View File

@@ -1,4 +1,6 @@
# example configuration file with snake_case keys
owner: Bob TBeam owner: Bob TBeam
owner_short: BOB
channel_url: https://www.meshtastic.org/d/#CgUYAyIBAQ channel_url: https://www.meshtastic.org/d/#CgUYAyIBAQ
@@ -13,3 +15,4 @@ user_prefs:
send_owner_interval: 2 send_owner_interval: 2
screen_on_secs: 31536000 screen_on_secs: 31536000
wait_bluetooth_secs: 31536000 wait_bluetooth_secs: 31536000
location_share: 'LocEnabled'

20
examples/get_hw.py Normal file
View File

@@ -0,0 +1,20 @@
"""Simple program to demo how to use meshtastic library.
To run: python examples/get_hw.py
"""
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()

View File

@@ -0,0 +1,26 @@
"""Program to scan for hardware
To run: python examples/scan_for_devices.py
"""
import sys
import meshtastic
from meshtastic.supported_device import get_unique_vendor_ids, active_ports_on_supported_devices
from meshtastic.util import detect_supported_devices
# 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}')
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}')
ports = active_ports_on_supported_devices(sds)
print(f'ports:{ports}')

20
examples/set_owner.py Normal file
View File

@@ -0,0 +1,20 @@
"""Simple program to demo how to use meshtastic library.
To run: python examples/set_owner.py Bobby 333
"""
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()

20
info/mac/heltec.txt Normal file
View File

@@ -0,0 +1,20 @@
ioreg -p IOUSB > /tmp/d
> | +-o CP2102 USB to UART Bridge Controller@14400000 <class AppleUSBDevice, id 0x10005c048, registered, matched, active, busy 0 (9 ms), retain 12>
system_profiler SPUSBDataType > /tmp/b
37a38,50
> CP2102 USB to UART Bridge Controller:
>
> Product ID: 0xea60
> Vendor ID: 0x10c4 (Silicon Laboratories, Inc.)
> Version: 1.00
> Serial Number: 0001
> Speed: Up to 12 Mb/s
> Manufacturer: Silicon Labs
> Location ID: 0x14400000 / 53
> Current Available (mA): 500
> Current Required (mA): 100
> Extra Operating Current (mA): 0

View File

@@ -0,0 +1,10 @@
> | +-o WisCore RAK4631 Board@14400000 <class AppleUSBDevice, id 0x10005c158, registered, matched, active, busy 0 (18 ms), retain 14>
/dev/cu.usbmodem14401
% ls -al /dev/*modem*
crw-rw-rw- 1 root wheel 0x9000005 Jan 29 15:32 /dev/cu.usbmodem14401
crw-rw-rw- 1 root wheel 0x9000004 Jan 29 15:31 /dev/tty.usbmodem14401

18
info/mac/rak4631_5005.txt Normal file
View File

@@ -0,0 +1,18 @@
no device plugged in
% ioreg -p IOUSB > /tmp/a
device plugged in, in "boot" mode
% ioreg -p IOUSB > /tmp/b
device plugged in, botted to Meshtastic firmware
% ioreg -p IOUSB > /tmp/c
(venv) sweet Meshtastic-python % diff /tmp/a /tmp/b (with most info removed)
> | +-o Feather nRF52840 Express@14400000 <class AppleUSBDevice, id 0x10005c0ff, registered, matched, active, busy 0 (22 ms), retain 16>
diff /tmp/a /tmp/c (with most info removed)
> | +-o WisCore RAK4631 Board@14400000 <class AppleUSBDevice, id 0x10005c134, registered, matched, active, busy 0 (17 ms), retain 14>
Meshtastic detected port on /dev/cu.usbmodem14401

21
info/mac/tbeam.txt Normal file
View File

@@ -0,0 +1,21 @@
meshtastic detected port: /dev/cu.usbmodem53230050571
ioreg -p IOUSB > /tmp/c
> | +-o USB Single Serial@14400000 <class AppleUSBDevice, id 0x10005bff7, registered, matched, active, busy 0 (15 ms), retain 14>
system_profiler SPUSBDataType > /tmp/a
> USB Single Serial:
>
> Product ID: 0x55d4
> Vendor ID: 0x1a86
> Version: 4.43
> Serial Number: 5323005057
> Speed: Up to 12 Mb/s
> Location ID: 0x14400000 / 50
> Current Available (mA): 500
> Current Required (mA): 134
> Extra Operating Current (mA): 0

16
info/mac/tlora.txt Normal file
View File

@@ -0,0 +1,16 @@
diff of ioreg -p IOUSB
> | +-o USB Single Serial@14300000 <class AppleUSBDevice, id 0x10005bf0f, registered, matched, active, busy 0 (18 ms), retain 14>
Diff of system_profiler SPUSBDataType
< USB Single Serial:
<
< Product ID: 0x55d4
< Vendor ID: 0x1a86
< Version: 4.43
< Speed: Up to 12 Mb/s
< Location ID: 0x14300000 / 46
< Current Available (mA): 500
< Current Required (mA): 134
< Extra Operating Current (mA): 0

3
info/readme.txt Normal file
View File

@@ -0,0 +1,3 @@
Gathering OS-level info for devices.
This info might be helpful for developers detecting info about devices.

85
info/ubuntu/heltec_v2.txt Normal file
View File

@@ -0,0 +1,85 @@
Run on Ubuntu 20
Command:
lsusb -d 10c4: -v
Output:
Bus 001 Device 091: ID 10c4:ea60 Silicon Labs CP210x UART Bridge
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 64
idVendor 0x10c4 Silicon Labs
idProduct 0xea60 CP210x UART Bridge
bcdDevice 1.00
iManufacturer 1
iProduct 2
iSerial 3
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0020
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 100mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 2
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Command:
esptool.py chip_id
Output:
esptool.py v3.2
Found 3 serial ports
Serial port /dev/ttyUSB0
Connecting....
Detecting chip type... Unsupported detection protocol, switching and trying again...
Connecting.....
Detecting chip type... ESP32
Chip is ESP32-D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: 24:0a:c4:fc:be:f0
Uploading stub...
Running stub...
Stub running...
Warning: ESP32 has no Chip ID. Reading MAC instead.
MAC: 24:0a:c4:fc:be:f0
Hard resetting via RTS pin...

View File

@@ -0,0 +1,118 @@
Note: Meshtastic firmware was installed when running these commands
$ ls -al /dev/ttyACM*
crw-rw---- 1 root dialout 166, 0 Jan 29 21:50 /dev/ttyACM0
lsusb -d 239a: -v
Bus 001 Device 097: ID 239a:8029
Couldn't open device, some information will be missing
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 239 Miscellaneous Device
bDeviceSubClass 2
bDeviceProtocol 1 Interface Association
bMaxPacketSize0 64
idVendor 0x239a
idProduct 0x8029
bcdDevice 1.00
iManufacturer 1
iProduct 2
iSerial 3
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x004b
bNumInterfaces 2
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 100mA
Interface Association:
bLength 8
bDescriptorType 11
bFirstInterface 0
bInterfaceCount 2
bFunctionClass 2 Communications
bFunctionSubClass 2 Abstract (modem)
bFunctionProtocol 0
iFunction 0
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 2 Communications
bInterfaceSubClass 2 Abstract (modem)
bInterfaceProtocol 0
iInterface 4
CDC Header:
bcdCDC 1.20
CDC Call Management:
bmCapabilities 0x00
bDataInterface 1
CDC ACM:
bmCapabilities 0x02
line coding and serial state
CDC Union:
bMasterInterface 0
bSlaveInterface 1
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 16
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 10 CDC Data
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
$ lsusb
Bus 002 Device 005: ID 046d:c31c Logitech, Inc. Keyboard K120
Bus 002 Device 002: ID 8087:8000 Intel Corp.
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 097: ID 239a:8029
Bus 001 Device 002: ID 8087:8008 Intel Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Note: esptool.py chip_id does not detect device

View File

@@ -0,0 +1,148 @@
Note: Device has Meshtastic firmware installed.
$ ls -al /dev/ttyACM*
crw-rw---- 1 root dialout 166, 0 Jan 29 21:44 /dev/ttyACM0
$ lsusb -d 239a: -v
Bus 001 Device 098: ID 239a:0029
Couldn't open device, some information will be missing
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 239 Miscellaneous Device
bDeviceSubClass 2
bDeviceProtocol 1 Interface Association
bMaxPacketSize0 64
idVendor 0x239a
idProduct 0x0029
bcdDevice 1.00
iManufacturer 1
iProduct 2
iSerial 3
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0062
bNumInterfaces 3
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 100mA
Interface Association:
bLength 8
bDescriptorType 11
bFirstInterface 0
bInterfaceCount 2
bFunctionClass 2 Communications
bFunctionSubClass 2 Abstract (modem)
bFunctionProtocol 0
iFunction 0
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 2 Communications
bInterfaceSubClass 2 Abstract (modem)
bInterfaceProtocol 0
iInterface 4
CDC Header:
bcdCDC 1.20
CDC Call Management:
bmCapabilities 0x00
bDataInterface 1
CDC ACM:
bmCapabilities 0x02
line coding and serial state
CDC Union:
bMasterInterface 0
bSlaveInterface 1
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 16
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 10 CDC Data
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 2
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 8 Mass Storage
bInterfaceSubClass 6 SCSI
bInterfaceProtocol 80 Bulk-Only
iInterface 5
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x03 EP 3 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x83 EP 3 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
$ lsusb
Bus 002 Device 005: ID 046d:c31c Logitech, Inc. Keyboard K120
Bus 002 Device 002: ID 8087:8000 Intel Corp.
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 098: ID 239a:0029
Bus 001 Device 002: ID 8087:8008 Intel Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Note: esptool.py chip_id does not detect device

1
info/ubuntu/readme.txt Normal file
View File

@@ -0,0 +1 @@
info run on ubuntu

94
info/ubuntu/tbeam.txt Normal file
View File

@@ -0,0 +1,94 @@
Run on Ubuntu 20
Command:
lsusb -d 1a86: -v
Output:
Bus 001 Device 096: ID 1a86:55d4 QinHeng Electronics
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 2 Communications
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x1a86 QinHeng Electronics
idProduct 0x55d4
bcdDevice 4.43
iManufacturer 0
iProduct 2
iSerial 3
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0043
bNumInterfaces 2
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 134mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 2 Communications
bInterfaceSubClass 2 Abstract (modem)
bInterfaceProtocol 1 AT-commands (v.25ter)
iInterface 0
CDC Header:
bcdCDC 1.10
CDC Call Management:
bmCapabilities 0x00
bDataInterface 1
CDC ACM:
bmCapabilities 0x02
line coding and serial state
CDC Union:
bMasterInterface 0
bSlaveInterface 1
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x83 EP 3 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0010 1x 16 bytes
bInterval 1
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 10 CDC Data
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0020 1x 32 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0

106
info/ubuntu/tlora.txt Normal file
View File

@@ -0,0 +1,106 @@
Run on Ubuntu 20
Note: Device has Meshtastic firmware installed
$ lsusb
Bus 002 Device 005: ID 046d:c31c Logitech, Inc. Keyboard K120
Bus 002 Device 002: ID 8087:8000 Intel Corp.
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 099: ID 1a86:55d4 QinHeng Electronics
Bus 001 Device 002: ID 8087:8008 Intel Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
(venv) mikekinney@top:~/Meshtastic-python/info/ubuntu$ lsusb -d 1a86: -v
Bus 001 Device 099: ID 1a86:55d4 QinHeng Electronics
Couldn't open device, some information will be missing
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 2 Communications
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x1a86 QinHeng Electronics
idProduct 0x55d4
bcdDevice 4.43
iManufacturer 0
iProduct 2
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0043
bNumInterfaces 2
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 134mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 2 Communications
bInterfaceSubClass 2 Abstract (modem)
bInterfaceProtocol 1 AT-commands (v.25ter)
iInterface 0
CDC Header:
bcdCDC 1.10
CDC Call Management:
bmCapabilities 0x00
bDataInterface 1
CDC ACM:
bmCapabilities 0x02
line coding and serial state
CDC Union:
bMasterInterface 0
bSlaveInterface 1
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x83 EP 3 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0010 1x 16 bytes
bInterval 1
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 10 CDC Data
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0020 1x 32 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0

106
info/windows/heltec.txt Normal file
View File

@@ -0,0 +1,106 @@
Run from Windows 10
Might work... (nope)
Get-PnpDevice -Class 'USB' -PresentOnly | Format-List
> Get-PnpDevice -PresentOnly | Format-List > b
> Compare-Object (get-content a) (Get-Content b)
InputObject SideIndicator
----------- -------------
Caption : CP2102 USB to UART Bridge Controller <=
Description : CP2102 USB to UART Bridge Controller <=
Name : CP2102 USB to UART Bridge Controller <=
Status : Error <=
ConfigManagerErrorCode : CM_PROB_FAILED_INSTALL <=
DeviceID : USB\VID_10C4&PID_EA60\0001 <=
PNPDeviceID : USB\VID_10C4&PID_EA60\0001 <=
CompatibleID : {USB\Class_FF&SubClass_00&Prot_00, USB\Class_FF&SubClass_00, USB\Class_FF} <=
HardwareID : {USB\VID_10C4&PID_EA60&REV_0100, USB\VID_10C4&PID_EA60} <=
FriendlyName : CP2102 USB to UART Bridge Controller <=
InstanceId : USB\VID_10C4&PID_EA60\0001 <=
Problem : CM_PROB_FAILED_INSTALL <=
ClassGuid : <=
Manufacturer : <=
PNPClass : <=
Class : <=
Service : <=
InstallDate : <=
Availability : <=
ConfigManagerUserConfig : False <=
CreationClassName : Win32_PnPEntity <=
ErrorCleared : <=
ErrorDescription : <=
LastErrorCode : <=
PowerManagementCapabilities : <=
PowerManagementSupported : <=
StatusInfo : <=
SystemCreationClassName : Win32_ComputerSystem <=
SystemName : DESKTOP-FRFQN8H <=
Present : True <=
PSComputerName : <=
ProblemDescription : <=
<=
> Get-PnpDevice -DeviceID 'USB\VID_10C4&PID_EA60\0001'
Status Class FriendlyName InstanceId
------ ----- ------------ ----------
Error CP2102 USB to UART Bridge Controller USB\VID_...
> Get-PnpDevice -PresentOnly -DeviceID 'USB\VID_10C4&PID_EA60\0001'
Get-PnpDevice : No matching Win32_PnPEntity objects found by CIM query for instances of the ROOT\cimv2\Win32_PnPEntity
class on the CIM server: SELECT * FROM Win32_PnPEntity WHERE ((DeviceId LIKE 'USB\\VID[_]10C4&PID[_]EA60\\0001'))
AND ((Present = TRUE)). Verify query parameters and retry.
At line:1 char:1
+ Get-PnpDevice -PresentOnly -DeviceID 'USB\VID_10C4&PID_EA60\0001'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Win32_PnPEntity:String) [Get-PnpDevice], CimJobException
+ FullyQualifiedErrorId : CmdletizationQuery_NotFound,Get-PnpDevice
> Get-PnpDevice -PresentOnly -DeviceID 'USB\VID_10C4&PID_EA60\0001'
Status Class FriendlyName InstanceId
------ ----- ------------ ----------
Error CP2102 USB to UART Bridge Controller USB\VID_...
If need to install driver
Get-PnpDevice -DeviceID 'USB\VID_10C4&PID_EA60\0001' | Format-List
Caption : CP2102 USB to UART Bridge Controller
Description : CP2102 USB to UART Bridge Controller
InstallDate :
Name : CP2102 USB to UART Bridge Controller
Status : Error
Availability :
ConfigManagerErrorCode : CM_PROB_FAILED_INSTALL
ConfigManagerUserConfig : False
CreationClassName : Win32_PnPEntity
DeviceID : USB\VID_10C4&PID_EA60\0001
ErrorCleared :
ErrorDescription :
LastErrorCode :
PNPDeviceID : USB\VID_10C4&PID_EA60\0001
PowerManagementCapabilities :
PowerManagementSupported :
StatusInfo :
SystemCreationClassName : Win32_ComputerSystem
SystemName : DESKTOP-FRFQN8H
ClassGuid :
CompatibleID : {USB\Class_FF&SubClass_00&Prot_00, USB\Class_FF&SubClass_00, USB\Class_FF}
HardwareID : {USB\VID_10C4&PID_EA60&REV_0100, USB\VID_10C4&PID_EA60}
Manufacturer :
PNPClass :
Present : True
Service :
PSComputerName :
Class :
FriendlyName : CP2102 USB to UART Bridge Controller
InstanceId : USB\VID_10C4&PID_EA60\0001
Problem : CM_PROB_FAILED_INSTALL
ProblemDescription :

View File

@@ -0,0 +1,78 @@
Run from Windows 10
> Get-PnpDevice -PresentOnly | Format-List >a
> Get-PnpDevice -PresentOnly | Format-List >b
> Compare-Object (get-content a) (Get-Content b)
InputObject Side
Indi
cato
r
----------- ----
Caption : USB Serial Device (COM4) =>
Description : USB Serial Device =>
Name : USB Serial Device (COM4) =>
DeviceID : USB\VID_239A&PID_8029&MI_00\6&E8876D1&0&0000 =>
PNPDeviceID : USB\VID_239A&PID_8029&MI_00\6&E8876D1&0&0000 =>
ClassGuid : {4d36e978-e325-11ce-bfc1-08002be10318} =>
CompatibleID : {USB\Class_02&SubClass_02&Prot_00, USB\Class_02&SubClass_02, USB\Class_02} =>
HardwareID : {USB\VID_239A&PID_8029&REV_0100&MI_00, USB\VID_239A&PID_8029&MI_00} =>
PNPClass : Ports =>
Service : usbser =>
Class : Ports =>
FriendlyName : USB Serial Device (COM4) =>
InstanceId : USB\VID_239A&PID_8029&MI_00\6&E8876D1&0&0000 =>
Caption : USB Composite Device =>
Description : USB Composite Device =>
Name : USB Composite Device =>
DeviceID : USB\VID_239A&PID_8029\E6CF9502B1D410D8 =>
PNPDeviceID : USB\VID_239A&PID_8029\E6CF9502B1D410D8 =>
ClassGuid : {36fc9e60-c465-11cf-8056-444553540000} =>
CompatibleID : {USB\DevClass_00&SubClass_00&Prot_00, USB\DevClass_00&SubClass_00, USB\DevClass_00, =>
USB\COMPOSITE} =>
HardwareID : {USB\VID_239A&PID_8029&REV_0100, USB\VID_239A&PID_8029} =>
Manufacturer : (Standard USB Host Controller) =>
PNPClass : USB =>
Service : usbccgp =>
Class : USB =>
FriendlyName : USB Composite Device =>
InstanceId : USB\VID_239A&PID_8029\E6CF9502B1D410D8 =>
InstallDate : =>
Status : OK =>
Availability : =>
ConfigManagerErrorCode : CM_PROB_NONE =>
ConfigManagerUserConfig : False =>
CreationClassName : Win32_PnPEntity =>
ErrorCleared : =>
ErrorDescription : =>
LastErrorCode : =>
PowerManagementCapabilities : =>
PowerManagementSupported : =>
StatusInfo : =>
SystemCreationClassName : Win32_ComputerSystem =>
SystemName : DESKTOP-FRFQN8H =>
Present : True =>
PSComputerName : =>
Problem : CM_PROB_NONE =>
ProblemDescription : =>
InstallDate : =>
Status : OK =>
Availability : =>
ConfigManagerErrorCode : CM_PROB_NONE =>
ConfigManagerUserConfig : False =>
CreationClassName : Win32_PnPEntity =>
ErrorCleared : =>
ErrorDescription : =>
LastErrorCode : =>
PowerManagementCapabilities : =>
PowerManagementSupported : =>
StatusInfo : =>
SystemCreationClassName : Win32_ComputerSystem =>
SystemName : DESKTOP-FRFQN8H =>
Manufacturer : Microsoft =>
Present : True =>
PSComputerName : =>
Problem : CM_PROB_NONE =>
ProblemDescription : =>
=>
=>

View File

@@ -0,0 +1,254 @@
Run from Windows 10
> Get-PnpDevice -PresentOnly | Format-List >a
> Get-PnpDevice -PresentOnly | Format-List >b
> Compare-Object (get-content a) (Get-Content b)
In "boot" mode:
InputObject
-----------
Caption : FTHR840BOOT
Description : nRF UF2
Name : FTHR840BOOT
DeviceID : SWD\WPDBUSENUM\_??_USBSTOR#DISK&VEN_ADAFRUIT&PROD_NRF_UF2&REV_1.0#7&27E1626&0&D121BD1C90B
93EA2&0#{53F56307-B6BF-11D0-94F2-00A0C91EFB8B}
PNPDeviceID : SWD\WPDBUSENUM\_??_USBSTOR#DISK&VEN_ADAFRUIT&PROD_NRF_UF2&REV_1.0#7&27E1626&0&D121BD1C90B
93EA2&0#{53F56307-B6BF-11D0-94F2-00A0C91EFB8B}
ClassGuid : {eec5ad98-8080-425f-922a-dabf3de3f69a}
CompatibleID : {wpdbusenum\fs, SWD\Generic}
Manufacturer : Adafruit
PNPClass : WPD
Service : WUDFWpdFs
Class : WPD
FriendlyName : FTHR840BOOT
InstanceId : SWD\WPDBUSENUM\_??_USBSTOR#DISK&VEN_ADAFRUIT&PROD_NRF_UF2&REV_1.0#7&27E1626&0&D121BD1C90B
93EA2&0#{53F56307-B6BF-11D0-94F2-00A0C91EFB8B}
DeviceID : STORAGE\VOLUME\_??_USBSTOR#DISK&VEN_ADAFRUIT&PROD_NRF_UF2&REV_1.0#7&27E1626&0&D121BD1C90B
93EA2&0#{53F56307-B6BF-11D0-94F2-00A0C91EFB8B}
PNPDeviceID : STORAGE\VOLUME\_??_USBSTOR#DISK&VEN_ADAFRUIT&PROD_NRF_UF2&REV_1.0#7&27E1626&0&D121BD1C90B
93EA2&0#{53F56307-B6BF-11D0-94F2-00A0C91EFB8B}
InstanceId : STORAGE\VOLUME\_??_USBSTOR#DISK&VEN_ADAFRUIT&PROD_NRF_UF2&REV_1.0#7&27E1626&0&D121BD1C90B
93EA2&0#{53F56307-B6BF-11D0-94F2-00A0C91EFB8B}
Caption : USB Mass Storage Device
Description : USB Mass Storage Device
Name : USB Mass Storage Device
DeviceID : USB\VID_239A&PID_0029&MI_02\6&175793A&0&0002
PNPDeviceID : USB\VID_239A&PID_0029&MI_02\6&175793A&0&0002
CompatibleID : {USB\Class_08&SubClass_06&Prot_50, USB\Class_08&SubClass_06, USB\Class_08}
HardwareID : {USB\VID_239A&PID_0029&REV_0100&MI_02, USB\VID_239A&PID_0029&MI_02}
Manufacturer : Compatible USB storage device
Service : USBSTOR
FriendlyName : USB Mass Storage Device
InstanceId : USB\VID_239A&PID_0029&MI_02\6&175793A&0&0002
Caption : USB Serial Device (COM5)
Description : USB Serial Device
Name : USB Serial Device (COM5)
DeviceID : USB\VID_239A&PID_0029&MI_00\6&175793A&0&0000
PNPDeviceID : USB\VID_239A&PID_0029&MI_00\6&175793A&0&0000
ClassGuid : {4d36e978-e325-11ce-bfc1-08002be10318}
CompatibleID : {USB\Class_02&SubClass_02&Prot_00, USB\Class_02&SubClass_02, USB\Class_02}
HardwareID : {USB\VID_239A&PID_0029&REV_0100&MI_00, USB\VID_239A&PID_0029&MI_00}
PNPClass : Ports
Service : usbser
Class : Ports
FriendlyName : USB Serial Device (COM5)
InstanceId : USB\VID_239A&PID_0029&MI_00\6&175793A&0&0000
DeviceID : USB\VID_239A&PID_0029\D121BD1C90B93EA2
PNPDeviceID : USB\VID_239A&PID_0029\D121BD1C90B93EA2
ClassGuid : {36fc9e60-c465-11cf-8056-444553540000}
HardwareID : {USB\VID_239A&PID_0029&REV_0100, USB\VID_239A&PID_0029}
PNPClass : USB
Class : USB
InstanceId : USB\VID_239A&PID_0029\D121BD1C90B93EA2
Caption : USB Composite Device
Description : USB Composite Device
Name : USB Composite Device
ClassGuid : {36fc9e60-c465-11cf-8056-444553540000}
CompatibleID : {USB\DevClass_00&SubClass_00&Prot_00, USB\DevClass_00&SubClass_00, USB\DevClass_00,
USB\COMPOSITE}
Manufacturer : (Standard USB Host Controller)
PNPClass : USB
Service : usbccgp
Class : USB
FriendlyName : USB Composite Device
Caption : Volume
Description : Volume
Name : Volume
ClassGuid : {71a27cdd-812a-11d0-bec7-08002be2092f}
HardwareID : {STORAGE\Volume}
PNPClass : Volume
Service : volume
Class : Volume
FriendlyName : Volume
HardwareID :
Caption : Adafruit nRF UF2 USB Device
Name : Adafruit nRF UF2 USB Device
DeviceID : USBSTOR\DISK&VEN_ADAFRUIT&PROD_NRF_UF2&REV_1.0\7&27E1626&0&D121BD1C90B93EA2&0
PNPDeviceID : USBSTOR\DISK&VEN_ADAFRUIT&PROD_NRF_UF2&REV_1.0\7&27E1626&0&D121BD1C90B93EA2&0
CompatibleID : {USBSTOR\Disk, USBSTOR\RAW, GenDisk}
HardwareID : {USBSTOR\DiskAdafruitnRF_UF2_________1.0_, USBSTOR\DiskAdafruitnRF_UF2_________,
USBSTOR\DiskAdafruit, USBSTOR\AdafruitnRF_UF2_________1...}
FriendlyName : Adafruit nRF UF2 USB Device
InstanceId : USBSTOR\DISK&VEN_ADAFRUIT&PROD_NRF_UF2&REV_1.0\7&27E1626&0&D121BD1C90B93EA2&0
Description : Disk drive
ClassGuid : {4d36e967-e325-11ce-bfc1-08002be10318}
Manufacturer : (Standard disk drives)
PNPClass : DiskDrive
Service : disk
Class : DiskDrive
CompatibleID :
InstallDate :
Status : OK
Availability :
ConfigManagerErrorCode : CM_PROB_NONE
ConfigManagerUserConfig : False
CreationClassName : Win32_PnPEntity
ErrorCleared :
ErrorDescription :
LastErrorCode :
PowerManagementCapabilities :
PowerManagementSupported :
StatusInfo :
SystemCreationClassName : Win32_ComputerSystem
SystemName : DESKTOP-FRFQN8H
Present : True
PSComputerName :
Problem : CM_PROB_NONE
ProblemDescription :
InstallDate :
Status : OK
Availability :
ConfigManagerErrorCode : CM_PROB_NONE
ConfigManagerUserConfig : False
CreationClassName : Win32_PnPEntity
ErrorCleared :
ErrorDescription :
LastErrorCode :
PowerManagementCapabilities :
PowerManagementSupported :
StatusInfo :
SystemCreationClassName : Win32_ComputerSystem
SystemName : DESKTOP-FRFQN8H
Present : True
PSComputerName :
Problem : CM_PROB_NONE
ProblemDescription :
InstallDate :
Status : OK
Availability :
ConfigManagerErrorCode : CM_PROB_NONE
ConfigManagerUserConfig : False
CreationClassName : Win32_PnPEntity
ErrorCleared :
ErrorDescription :
LastErrorCode :
PowerManagementCapabilities :
PowerManagementSupported :
StatusInfo :
SystemCreationClassName : Win32_ComputerSystem
SystemName : DESKTOP-FRFQN8H
Present : True
PSComputerName :
Problem : CM_PROB_NONE
ProblemDescription :
InstallDate :
Status : OK
Availability :
ConfigManagerErrorCode : CM_PROB_NONE
ConfigManagerUserConfig : False
CreationClassName : Win32_PnPEntity
ErrorCleared :
ErrorDescription :
LastErrorCode :
PowerManagementCapabilities :
PowerManagementSupported :
StatusInfo :
SystemCreationClassName : Win32_ComputerSystem
SystemName : DESKTOP-FRFQN8H
Manufacturer : Microsoft
Present : True
PSComputerName :
Problem : CM_PROB_NONE
ProblemDescription :
When you press the RST to load Meshtastic:
> Get-PnpDevice -PresentOnly | Format-List >b
> Compare-Object (get-content a) (Get-Content b)
InputObject Side
Indi
cato
r
----------- ----
DeviceID : USB\VID_239A&PID_8029\D121BD1C90B93EA2 =>
PNPDeviceID : USB\VID_239A&PID_8029\D121BD1C90B93EA2 =>
HardwareID : {USB\VID_239A&PID_8029&REV_0100, USB\VID_239A&PID_8029} =>
InstanceId : USB\VID_239A&PID_8029\D121BD1C90B93EA2 =>
Caption : USB Composite Device =>
Description : USB Composite Device =>
Name : USB Composite Device =>
ClassGuid : {36fc9e60-c465-11cf-8056-444553540000} =>
CompatibleID : {USB\DevClass_00&SubClass_00&Prot_00, USB\DevClass_00&SubClass_00, USB\DevClass_00, =>
USB\COMPOSITE} =>
Manufacturer : (Standard USB Host Controller) =>
PNPClass : USB =>
Service : usbccgp =>
Class : USB =>
FriendlyName : USB Composite Device =>
Caption : USB Serial Device (COM6) =>
Description : USB Serial Device =>
Name : USB Serial Device (COM6) =>
DeviceID : USB\VID_239A&PID_8029&MI_00\6&39B279E2&0&0000 =>
PNPDeviceID : USB\VID_239A&PID_8029&MI_00\6&39B279E2&0&0000 =>
ClassGuid : {4d36e978-e325-11ce-bfc1-08002be10318} =>
CompatibleID : {USB\Class_02&SubClass_02&Prot_00, USB\Class_02&SubClass_02, USB\Class_02} =>
HardwareID : {USB\VID_239A&PID_8029&REV_0100&MI_00, USB\VID_239A&PID_8029&MI_00} =>
PNPClass : Ports =>
Service : usbser =>
Class : Ports =>
FriendlyName : USB Serial Device (COM6) =>
InstanceId : USB\VID_239A&PID_8029&MI_00\6&39B279E2&0&0000 =>
InstallDate : =>
Status : OK =>
Availability : =>
ConfigManagerErrorCode : CM_PROB_NONE =>
ConfigManagerUserConfig : False =>
CreationClassName : Win32_PnPEntity =>
ErrorCleared : =>
ErrorDescription : =>
LastErrorCode : =>
PowerManagementCapabilities : =>
PowerManagementSupported : =>
StatusInfo : =>
SystemCreationClassName : Win32_ComputerSystem =>
SystemName : DESKTOP-FRFQN8H =>
Present : True =>
PSComputerName : =>
Problem : CM_PROB_NONE =>
ProblemDescription : =>
InstallDate : =>
Status : OK =>
Availability : =>
ConfigManagerErrorCode : CM_PROB_NONE =>
ConfigManagerUserConfig : False =>
CreationClassName : Win32_PnPEntity =>
ErrorCleared : =>
ErrorDescription : =>
LastErrorCode : =>
PowerManagementCapabilities : =>
PowerManagementSupported : =>
StatusInfo : =>
SystemCreationClassName : Win32_ComputerSystem =>
SystemName : DESKTOP-FRFQN8H =>
Manufacturer : Microsoft =>
Present : True =>
PSComputerName : =>
Problem : CM_PROB_NONE =>
ProblemDescription : =>
=>

42
info/windows/tbeam.txt Normal file
View File

@@ -0,0 +1,42 @@
Run from Windows 10
> Get-PnpDevice -PresentOnly | Format-List >a
> Get-PnpDevice -PresentOnly | Format-List >b
> Compare-Object (get-content a) (Get-Content b)
InputObject SideIndicator
----------- -------------
Caption : USB-Enhanced-SERIAL CH9102 (COM7) =>
Description : USB-Enhanced-SERIAL CH9102 =>
Name : USB-Enhanced-SERIAL CH9102 (COM7) =>
DeviceID : USB\VID_1A86&PID_55D4\5323005057 =>
PNPDeviceID : USB\VID_1A86&PID_55D4\5323005057 =>
ClassGuid : {4d36e978-e325-11ce-bfc1-08002be10318} =>
CompatibleID : {USB\Class_02&SubClass_02&Prot_01, USB\Class_02&SubClass_02, USB\Class_02} =>
HardwareID : {USB\VID_1A86&PID_55D4&REV_0443, USB\VID_1A86&PID_55D4} =>
Manufacturer : wch.cn =>
PNPClass : Ports =>
Service : CH343SER_A64 =>
Class : Ports =>
FriendlyName : USB-Enhanced-SERIAL CH9102 (COM7) =>
InstanceId : USB\VID_1A86&PID_55D4\5323005057 =>
InstallDate : =>
Status : OK =>
Availability : =>
ConfigManagerErrorCode : CM_PROB_NONE =>
ConfigManagerUserConfig : False =>
CreationClassName : Win32_PnPEntity =>
ErrorCleared : =>
ErrorDescription : =>
LastErrorCode : =>
PowerManagementCapabilities : =>
PowerManagementSupported : =>
StatusInfo : =>
SystemCreationClassName : Win32_ComputerSystem =>
SystemName : DESKTOP-FRFQN8H =>
Present : True =>
PSComputerName : =>
Problem : CM_PROB_NONE =>
ProblemDescription : =>
=>

41
info/windows/tlora.txt Normal file
View File

@@ -0,0 +1,41 @@
Run from Windows 10
PS C:\Users\mikekinney> Get-PnpDevice -PresentOnly | Format-List > a
PS C:\Users\mikekinney> Get-PnpDevice -PresentOnly | Format-List > b
PS C:\Users\mikekinney> Compare-Object (get-content a) (Get-Content b)
InputObject SideIndicator
----------- -------------
Caption : USB-Enhanced-SERIAL CH9102 (COM3) <=
Description : USB-Enhanced-SERIAL CH9102 <=
Name : USB-Enhanced-SERIAL CH9102 (COM3) <=
DeviceID : USB\VID_1A86&PID_55D4\5&27435A1F&0&1 <=
PNPDeviceID : USB\VID_1A86&PID_55D4\5&27435A1F&0&1 <=
ClassGuid : {4d36e978-e325-11ce-bfc1-08002be10318} <=
CompatibleID : {USB\Class_02&SubClass_02&Prot_01, USB\Class_02&SubClass_02, USB\Class_02} <=
HardwareID : {USB\VID_1A86&PID_55D4&REV_0443, USB\VID_1A86&PID_55D4} <=
Manufacturer : wch.cn <=
PNPClass : Ports <=
Service : CH343SER_A64 <=
Class : Ports <=
FriendlyName : USB-Enhanced-SERIAL CH9102 (COM3) <=
InstanceId : USB\VID_1A86&PID_55D4\5&27435A1F&0&1 <=
InstallDate : <=
Status : OK <=
Availability : <=
ConfigManagerErrorCode : CM_PROB_NONE <=
ConfigManagerUserConfig : False <=
CreationClassName : Win32_PnPEntity <=
ErrorCleared : <=
ErrorDescription : <=
LastErrorCode : <=
PowerManagementCapabilities : <=
PowerManagementSupported : <=
StatusInfo : <=
SystemCreationClassName : Win32_ComputerSystem <=
SystemName : DESKTOP-FRFQN8H <=
Present : True <=
PSComputerName : <=
Problem : CM_PROB_NONE <=
ProblemDescription : <=
<=

View File

@@ -72,14 +72,16 @@ from typing import *
import serial import serial
import timeago import timeago
import google.protobuf.json_format import google.protobuf.json_format
import pygatt
from pubsub import pub from pubsub import pub
from dotmap import DotMap from dotmap import DotMap
from tabulate import tabulate from tabulate import tabulate
from google.protobuf.json_format import MessageToJson from google.protobuf.json_format import MessageToJson
from .util import fixme, catchAndIgnore, stripnl, DeferredExecution, Timeout from meshtastic.util import fixme, catchAndIgnore, stripnl, DeferredExecution, Timeout
from .node import Node from meshtastic.node import Node
from . import mesh_pb2, portnums_pb2, apponly_pb2, admin_pb2, environmental_measurement_pb2, remote_hardware_pb2, channel_pb2, radioconfig_pb2, util from meshtastic import (mesh_pb2, portnums_pb2, apponly_pb2, admin_pb2,
environmental_measurement_pb2, remote_hardware_pb2,
channel_pb2, radioconfig_pb2, util)
# Note: To follow PEP224, comments should be after the module variable. # Note: To follow PEP224, comments should be after the module variable.
@@ -127,6 +129,7 @@ def _onTextReceive(iface, asDict):
# #
# Usually btw this problem is caused by apps sending binary data but setting the payload type to # Usually btw this problem is caused by apps sending binary data but setting the payload type to
# text. # text.
logging.debug(f'in _onTextReceive() asDict:{asDict}')
try: try:
asBytes = asDict["decoded"]["payload"] asBytes = asDict["decoded"]["payload"]
asDict["decoded"]["text"] = asBytes.decode("utf-8") asDict["decoded"]["text"] = asBytes.decode("utf-8")
@@ -137,22 +140,30 @@ def _onTextReceive(iface, asDict):
def _onPositionReceive(iface, asDict): def _onPositionReceive(iface, asDict):
"""Special auto parsing for received messages""" """Special auto parsing for received messages"""
p = asDict["decoded"]["position"] logging.debug(f'in _onPositionReceive() asDict:{asDict}')
iface._fixupPosition(p) if 'decoded' in asDict:
# update node DB as needed if 'position' in asDict['decoded'] and 'from' in asDict:
iface._getOrCreateByNum(asDict["from"])["position"] = p p = asDict["decoded"]["position"]
logging.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
def _onNodeInfoReceive(iface, asDict): def _onNodeInfoReceive(iface, asDict):
"""Special auto parsing for received messages""" """Special auto parsing for received messages"""
p = asDict["decoded"]["user"] logging.debug(f'in _onNodeInfoReceive() asDict:{asDict}')
# decode user protobufs and update nodedb, provide decoded version as "position" in the published msg if 'decoded' in asDict:
# update node DB as needed if 'user' in asDict['decoded'] and 'from' in asDict:
n = iface._getOrCreateByNum(asDict["from"]) p = asDict["decoded"]["user"]
n["user"] = p # decode user protobufs and update nodedb, provide decoded version as "position" in the published msg
# We now have a node ID, make sure it is uptodate in that table # update node DB as needed
iface.nodes[p["id"]] = n n = iface._getOrCreateByNum(asDict["from"])
_receiveInfoUpdate(iface, asDict) n["user"] = p
# We now have a node ID, make sure it is uptodate in that table
iface.nodes[p["id"]] = n
_receiveInfoUpdate(iface, asDict)
def _receiveInfoUpdate(iface, asDict): def _receiveInfoUpdate(iface, asDict):

View File

@@ -5,6 +5,7 @@
import argparse import argparse
import platform import platform
import logging import logging
import os
import sys import sys
import time import time
import yaml import yaml
@@ -13,13 +14,11 @@ import pyqrcode
import pkg_resources import pkg_resources
import meshtastic.util import meshtastic.util
import meshtastic.test import meshtastic.test
from . import remote_hardware from meshtastic import remote_hardware
from . import portnums_pb2, channel_pb2, radioconfig_pb2 from meshtastic.ble_interface import BLEInterface
from .globals import Globals from meshtastic import portnums_pb2, channel_pb2, radioconfig_pb2
from meshtastic.globals import Globals
from meshtastic.__init__ import BROADCAST_ADDR
have_tunnel = platform.system() == 'Linux'
"""We only import the tunnel code if we are on a platform that can run it. """
def onReceive(packet, interface): def onReceive(packet, interface):
"""Callback invoked when a packet arrives""" """Callback invoked when a packet arrives"""
@@ -40,15 +39,15 @@ def onReceive(packet, interface):
rxSnr = packet['rxSnr'] rxSnr = packet['rxSnr']
hopLimit = packet['hopLimit'] hopLimit = packet['hopLimit']
print(f"message: {msg}") print(f"message: {msg}")
reply = "got msg \'{}\' with rxSnr: {} and hopLimit: {}".format(msg, rxSnr, hopLimit) reply = f"got msg \'{msg}\' with rxSnr: {rxSnr} and hopLimit: {hopLimit}"
print("Sending reply: ", reply) print("Sending reply: ", reply)
interface.sendText(reply) interface.sendText(reply)
except Exception as ex: except Exception as ex:
print(ex) print(f'Warning: There is no field {ex} in the packet.')
def onConnection(interface, topic=pub.AUTO_TOPIC): def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=W0613
"""Callback invoked when we connect/disconnect from a radio""" """Callback invoked when we connect/disconnect from a radio"""
print(f"Connection changed: {topic.getName()}") print(f"Connection changed: {topic.getName()}")
@@ -56,48 +55,69 @@ def onConnection(interface, topic=pub.AUTO_TOPIC):
def getPref(attributes, name): def getPref(attributes, name):
"""Get a channel or preferences value""" """Get a channel or preferences value"""
camel_name = meshtastic.util.snake_to_camel(name)
# Note: protobufs has the keys in snake_case, so snake internally
snake_name = meshtastic.util.camel_to_snake(name)
logging.debug(f'snake_name:{snake_name} camel_name:{camel_name}')
logging.debug(f'use camel:{Globals.getInstance().get_camel_case()}')
objDesc = attributes.DESCRIPTOR objDesc = attributes.DESCRIPTOR
field = objDesc.fields_by_name.get(name) field = objDesc.fields_by_name.get(snake_name)
if not field: if not field:
print(f"{attributes.__class__.__name__} does not have an attribute called {name}, so you can not get it.") if Globals.getInstance().get_camel_case():
print(f"{attributes.__class__.__name__} does not have an attribute called {camel_name}, so you can not get it.")
else:
print(f"{attributes.__class__.__name__} does not have an attribute called {snake_name}, so you can not get it.")
print(f"Choices in sorted order are:") print(f"Choices in sorted order are:")
names = [] names = []
for f in objDesc.fields: for f in objDesc.fields:
names.append(f'{f.name}') tmp_name = f'{f.name}'
if Globals.getInstance().get_camel_case():
tmp_name = meshtastic.util.snake_to_camel(tmp_name)
names.append(tmp_name)
for temp_name in sorted(names): for temp_name in sorted(names):
print(f" {temp_name}") print(f" {temp_name}")
return return
# okay - try to read the value # read the value
try: val = getattr(attributes, snake_name)
try:
val = getattr(attributes, name)
except TypeError:
# The getter didn't like our arg type guess try again as a string
val = getattr(attributes, name)
# succeeded! if Globals.getInstance().get_camel_case():
print(f"{name}: {str(val)}") print(f"{camel_name}: {str(val)}")
except Exception as ex: logging.debug(f"{camel_name}: {str(val)}")
print(f"Can't get {name} due to {ex}") else:
print(f"{snake_name}: {str(val)}")
logging.debug(f"{snake_name}: {str(val)}")
def setPref(attributes, name, valStr): def setPref(attributes, name, valStr):
"""Set a channel or preferences value""" """Set a channel or preferences value"""
snake_name = meshtastic.util.camel_to_snake(name)
camel_name = meshtastic.util.snake_to_camel(name)
logging.debug(f'snake_name:{snake_name}')
logging.debug(f'camel_name:{camel_name}')
objDesc = attributes.DESCRIPTOR objDesc = attributes.DESCRIPTOR
field = objDesc.fields_by_name.get(name) field = objDesc.fields_by_name.get(snake_name)
if not field: if not field:
print(f"{attributes.__class__.__name__} does not have an attribute called {name}, so you can not set it.") if Globals.getInstance().get_camel_case():
print(f"{attributes.__class__.__name__} does not have an attribute called {camel_name}, so you can not set it.")
else:
print(f"{attributes.__class__.__name__} does not have an attribute called {snake_name}, so you can not set it.")
print(f"Choices in sorted order are:") print(f"Choices in sorted order are:")
names = [] names = []
for f in objDesc.fields: for f in objDesc.fields:
names.append(f'{f.name}') tmp_name = f'{f.name}'
if Globals.getInstance().get_camel_case():
tmp_name = meshtastic.util.snake_to_camel(tmp_name)
names.append(tmp_name)
for temp_name in sorted(names): for temp_name in sorted(names):
print(f" {temp_name}") print(f" {temp_name}")
return return
val = meshtastic.util.fromStr(valStr) val = meshtastic.util.fromStr(valStr)
logging.debug(f'valStr:{valStr} val:{val}')
enumType = field.enum_type enumType = field.enum_type
# pylint: disable=C0123 # pylint: disable=C0123
@@ -107,27 +127,28 @@ def setPref(attributes, name, valStr):
if e: if e:
val = e.number val = e.number
else: else:
print(f"{name} does not have an enum called {val}, so you can not set it.") if Globals.getInstance().get_camel_case():
print(f"{camel_name} does not have an enum called {val}, so you can not set it.")
else:
print(f"{snake_name} does not have an enum called {val}, so you can not set it.")
print(f"Choices in sorted order are:") print(f"Choices in sorted order are:")
names = [] names = []
for f in enumType.values: for f in enumType.values:
# Note: We must use the value of the enum (regardless if camel or snake case)
names.append(f'{f.name}') names.append(f'{f.name}')
for temp_name in sorted(names): for temp_name in sorted(names):
print(f" {temp_name}") print(f" {temp_name}")
return return
# okay - try to read the value
try: try:
try: setattr(attributes, snake_name, val)
setattr(attributes, name, val) except TypeError:
except TypeError: # The setter didn't like our arg type guess try again as a string
# The setter didn't like our arg type guess try again as a string setattr(attributes, snake_name, valStr)
setattr(attributes, name, valStr)
# succeeded! if Globals.getInstance().get_camel_case():
print(f"Set {name} to {valStr}") print(f"Set {camel_name} to {valStr}")
except Exception as ex: else:
print(f"Can't set {name} due to {ex}") print(f"Set {snake_name} to {valStr}")
def onConnected(interface): def onConnected(interface):
@@ -137,15 +158,9 @@ def onConnected(interface):
our_globals = Globals.getInstance() our_globals = Globals.getInstance()
args = our_globals.get_args() args = our_globals.get_args()
print("Connected to radio") # do not print this line if we are exporting the config
if not args.export_config:
def getNode(): print("Connected to radio")
"""This operation could be expensive, so we try to cache the results"""
targetNode = our_globals.get_target_node()
if not targetNode:
targetNode = interface.getNode(args.destOrLocal)
our_globals.set_target_node(targetNode)
return targetNode
if args.setlat or args.setlon or args.setalt: if args.setlat or args.setlon or args.setalt:
closeNow = True closeNow = True
@@ -178,12 +193,17 @@ def onConnected(interface):
if args.set_owner: if args.set_owner:
closeNow = True closeNow = True
print(f"Setting device owner to {args.set_owner}") print(f"Setting device owner to {args.set_owner}")
getNode().setOwner(args.set_owner) interface.getNode(args.dest).setOwner(args.set_owner)
if args.set_owner_short:
closeNow = True
print(f"Setting device owner short to {args.set_owner_short}")
interface.getNode(args.dest).setOwner(long_name=None, short_name=args.set_owner_short)
if args.pos_fields: if args.pos_fields:
# If --pos-fields invoked with args, set position fields # If --pos-fields invoked with args, set position fields
closeNow = True closeNow = True
prefs = getNode().radioConfig.preferences prefs = interface.getNode(args.dest).radioConfig.preferences
allFields = 0 allFields = 0
try: try:
@@ -198,14 +218,14 @@ def onConnected(interface):
else: else:
print(f"Setting position fields to {allFields}") print(f"Setting position fields to {allFields}")
setPref(prefs, 'position_flags', ('%d' % allFields)) setPref(prefs, 'position_flags', f'{allFields:d}')
print("Writing modified preferences to device") print("Writing modified preferences to device")
getNode().writeConfig() interface.getNode(args.dest).writeConfig()
elif args.pos_fields is not None: elif args.pos_fields is not None:
# If --pos-fields invoked without args, read and display current value # If --pos-fields invoked without args, read and display current value
closeNow = True closeNow = True
prefs = getNode().radioConfig.preferences prefs = interface.getNode(args.dest).radioConfig.preferences
fieldNames = [] fieldNames = []
for bit in radioconfig_pb2.PositionFlags.values(): for bit in radioconfig_pb2.PositionFlags.values():
@@ -224,81 +244,88 @@ def onConnected(interface):
print(sorted(meshtastic.mesh_pb2.Team.keys())) print(sorted(meshtastic.mesh_pb2.Team.keys()))
else: else:
print(f"Setting team to {meshtastic.mesh_pb2.Team.Name(v_team)}") print(f"Setting team to {meshtastic.mesh_pb2.Team.Name(v_team)}")
getNode().setOwner(team=v_team) interface.getNode(args.dest).setOwner(team=v_team)
if args.set_ham: if args.set_ham:
closeNow = True closeNow = True
print(f"Setting Ham ID to {args.set_ham} and turning off encryption") print(f"Setting Ham ID to {args.set_ham} and turning off encryption")
getNode().setOwner(args.set_ham, is_licensed=True) interface.getNode(args.dest).setOwner(args.set_ham, is_licensed=True)
# Must turn off encryption on primary channel # Must turn off encryption on primary channel
getNode().turnOffEncryptionOnPrimaryChannel() interface.getNode(args.dest).turnOffEncryptionOnPrimaryChannel()
if args.reboot: if args.reboot:
closeNow = True closeNow = True
getNode().reboot() interface.getNode(args.dest).reboot()
if args.shutdown:
closeNow = True
interface.getNode(args.dest).shutdown()
if args.sendtext: if args.sendtext:
closeNow = True closeNow = True
channelIndex = 0 channelIndex = 0
if args.ch_index is not None: if args.ch_index is not None:
channelIndex = int(args.ch_index) channelIndex = int(args.ch_index)
ch = getNode().getChannelByChannelIndex(channelIndex) ch = interface.localNode.getChannelByChannelIndex(channelIndex)
logging.debug(f'ch:{ch}')
if ch and ch.role != channel_pb2.Channel.Role.DISABLED: if ch and ch.role != channel_pb2.Channel.Role.DISABLED:
print(f"Sending text message {args.sendtext} to {args.destOrAll} on channelIndex:{channelIndex}") print(f"Sending text message {args.sendtext} to {args.dest} on channelIndex:{channelIndex}")
interface.sendText(args.sendtext, args.destOrAll, wantAck=True, channelIndex=channelIndex) interface.sendText(args.sendtext, args.dest, wantAck=True, channelIndex=channelIndex)
else: else:
meshtastic.util.our_exit(f"Warning: {channelIndex} is not a valid channel. Channel must not be DISABLED.") meshtastic.util.our_exit(f"Warning: {channelIndex} is not a valid channel. Channel must not be DISABLED.")
if args.sendping: if args.sendping:
payload = str.encode("test string") payload = str.encode("test string")
print(f"Sending ping message to {args.destOrAll}") print(f"Sending ping message to {args.dest}")
interface.sendData(payload, args.destOrAll, portNum=portnums_pb2.PortNum.REPLY_APP, interface.sendData(payload, args.dest, portNum=portnums_pb2.PortNum.REPLY_APP,
wantAck=True, wantResponse=True) wantAck=True, wantResponse=True)
if args.gpio_wrb or args.gpio_rd or args.gpio_watch: if args.gpio_wrb or args.gpio_rd or args.gpio_watch:
rhc = remote_hardware.RemoteHardwareClient(interface) if args.dest == BROADCAST_ADDR:
meshtastic.util.our_exit("Warning: Must use a destination node ID.")
else:
rhc = remote_hardware.RemoteHardwareClient(interface)
if args.gpio_wrb: if args.gpio_wrb:
bitmask = 0 bitmask = 0
bitval = 0 bitval = 0
for wrpair in (args.gpio_wrb or []): for wrpair in (args.gpio_wrb or []):
bitmask |= 1 << int(wrpair[0]) bitmask |= 1 << int(wrpair[0])
bitval |= int(wrpair[1]) << int(wrpair[0]) bitval |= int(wrpair[1]) << int(wrpair[0])
print(f"Writing GPIO mask 0x{bitmask:x} with value 0x{bitval:x} to {args.dest}") print(f"Writing GPIO mask 0x{bitmask:x} with value 0x{bitval:x} to {args.dest}")
rhc.writeGPIOs(args.dest, bitmask, bitval) rhc.writeGPIOs(args.dest, bitmask, bitval)
closeNow = True closeNow = True
if args.gpio_rd: if args.gpio_rd:
bitmask = int(args.gpio_rd, 16) bitmask = int(args.gpio_rd, 16)
print(f"Reading GPIO mask 0x{bitmask:x} from {args.dest}") print(f"Reading GPIO mask 0x{bitmask:x} from {args.dest}")
interface.mask = bitmask interface.mask = bitmask
rhc.readGPIOs(args.dest, bitmask, None) rhc.readGPIOs(args.dest, bitmask, None)
if not interface.noProto:
# wait up to X seconds for a response # wait up to X seconds for a response
for _ in range(10): for _ in range(10):
time.sleep(1) time.sleep(1)
if interface.gotResponse: if interface.gotResponse:
break break
logging.debug(f'end of gpio_rd') logging.debug(f'end of gpio_rd')
if args.gpio_watch: if args.gpio_watch:
bitmask = int(args.gpio_watch, 16) bitmask = int(args.gpio_watch, 16)
print(f"Watching GPIO mask 0x{bitmask:x} from {args.dest}. Press ctrl-c to exit") print(f"Watching GPIO mask 0x{bitmask:x} from {args.dest}. Press ctrl-c to exit")
while True: while True:
rhc.watchGPIOs(args.dest, bitmask) rhc.watchGPIOs(args.dest, bitmask)
time.sleep(1) time.sleep(1)
# handle settings # handle settings
if args.set: if args.set:
closeNow = True closeNow = True
prefs = getNode().radioConfig.preferences prefs = interface.getNode(args.dest).radioConfig.preferences
# Handle the int/float/bool arguments # Handle the int/float/bool arguments
for pref in args.set: for pref in args.set:
setPref(prefs, pref[0], pref[1]) setPref(prefs, pref[0], pref[1])
print("Writing modified preferences to device") print("Writing modified preferences to device")
getNode().writeConfig() interface.getNode(args.dest).writeConfig()
if args.configure: if args.configure:
with open(args.configure[0], encoding='utf8') as file: with open(args.configure[0], encoding='utf8') as file:
@@ -307,11 +334,23 @@ def onConnected(interface):
if 'owner' in configuration: if 'owner' in configuration:
print(f"Setting device owner to {configuration['owner']}") print(f"Setting device owner to {configuration['owner']}")
getNode().setOwner(configuration['owner']) interface.getNode(args.dest).setOwner(configuration['owner'])
if 'owner_short' in configuration:
print(f"Setting device owner short to {configuration['owner_short']}")
interface.getNode(args.dest).setOwner(long_name=None, short_name=configuration['owner_short'])
if 'ownerShort' in configuration:
print(f"Setting device owner short to {configuration['ownerShort']}")
interface.getNode(args.dest).setOwner(long_name=None, short_name=configuration['ownerShort'])
if 'channel_url' in configuration: if 'channel_url' in configuration:
print("Setting channel url to", configuration['channel_url']) print("Setting channel url to", configuration['channel_url'])
getNode().setURL(configuration['channel_url']) interface.getNode(args.dest).setURL(configuration['channel_url'])
if 'channelUrl' in configuration:
print("Setting channel url to", configuration['channelUrl'])
interface.getNode(args.dest).setURL(configuration['channelUrl'])
if 'location' in configuration: if 'location' in configuration:
alt = 0 alt = 0
@@ -336,11 +375,18 @@ def onConnected(interface):
interface.localNode.writeConfig() interface.localNode.writeConfig()
if 'user_prefs' in configuration: if 'user_prefs' in configuration:
prefs = getNode().radioConfig.preferences prefs = interface.getNode(args.dest).radioConfig.preferences
for pref in configuration['user_prefs']: for pref in configuration['user_prefs']:
setPref(prefs, pref, str(configuration['user_prefs'][pref])) setPref(prefs, pref, str(configuration['user_prefs'][pref]))
print("Writing modified preferences to device") print("Writing modified preferences to device")
getNode().writeConfig() interface.getNode(args.dest).writeConfig()
if 'userPrefs' in configuration:
prefs = interface.getNode(args.dest).radioConfig.preferences
for pref in configuration['userPrefs']:
setPref(prefs, pref, str(configuration['userPrefs'][pref]))
print("Writing modified preferences to device")
interface.getNode(args.dest).writeConfig()
if args.export_config: if args.export_config:
# export the configuration (the opposite of '--configure') # export the configuration (the opposite of '--configure')
@@ -349,7 +395,7 @@ def onConnected(interface):
if args.seturl: if args.seturl:
closeNow = True closeNow = True
getNode().setURL(args.seturl) interface.getNode(args.dest).setURL(args.seturl)
# handle changing channels # handle changing channels
@@ -357,7 +403,7 @@ def onConnected(interface):
closeNow = True closeNow = True
if len(args.ch_add) > 10: if len(args.ch_add) > 10:
meshtastic.util.our_exit("Warning: Channel name must be shorter. Channel not added.") meshtastic.util.our_exit("Warning: Channel name must be shorter. Channel not added.")
n = getNode() n = interface.getNode(args.dest)
ch = n.getChannelByName(args.ch_add) ch = n.getChannelByName(args.ch_add)
if ch: if ch:
meshtastic.util.our_exit(f"Warning: This node already has a '{args.ch_add}' channel. No changes were made.") meshtastic.util.our_exit(f"Warning: This node already has a '{args.ch_add}' channel. No changes were made.")
@@ -385,7 +431,7 @@ def onConnected(interface):
meshtastic.util.our_exit("Warning: Cannot delete primary channel.", 1) meshtastic.util.our_exit("Warning: Cannot delete primary channel.", 1)
else: else:
print(f"Deleting channel {channelIndex}") print(f"Deleting channel {channelIndex}")
ch = getNode().deleteChannel(channelIndex) ch = interface.getNode(args.dest).deleteChannel(channelIndex)
ch_changes = [args.ch_longslow, args.ch_longfast, ch_changes = [args.ch_longslow, args.ch_longfast,
args.ch_mediumslow, args.ch_mediumfast, args.ch_mediumslow, args.ch_mediumfast,
@@ -401,7 +447,7 @@ def onConnected(interface):
channelIndex = 0 channelIndex = 0
else: else:
meshtastic.util.our_exit("Warning: Need to specify '--ch-index'.", 1) meshtastic.util.our_exit("Warning: Need to specify '--ch-index'.", 1)
ch = getNode().channels[channelIndex] ch = interface.getNode(args.dest).channels[channelIndex]
if any_primary_channel_changes or args.ch_enable or args.ch_disable: if any_primary_channel_changes or args.ch_enable or args.ch_disable:
@@ -462,21 +508,22 @@ def onConnected(interface):
ch.role = channel_pb2.Channel.Role.DISABLED ch.role = channel_pb2.Channel.Role.DISABLED
print(f"Writing modified channels to device") print(f"Writing modified channels to device")
getNode().writeChannel(channelIndex) interface.getNode(args.dest).writeChannel(channelIndex)
if args.info: if args.info:
print("") print("")
if not args.dest: # If we aren't trying to talk to our local node, don't show it # If we aren't trying to talk to our local node, don't show it
if args.dest == BROADCAST_ADDR:
interface.showInfo() interface.showInfo()
print("") print("")
getNode().showInfo() interface.getNode(args.dest).showInfo()
closeNow = True # FIXME, for now we leave the link up while talking to remote nodes closeNow = True # FIXME, for now we leave the link up while talking to remote nodes
print("") print("")
if args.get: if args.get:
closeNow = True closeNow = True
prefs = getNode().radioConfig.preferences prefs = interface.getNode(args.dest).radioConfig.preferences
# Handle the int/float/bool arguments # Handle the int/float/bool arguments
for pref in args.get: for pref in args.get:
@@ -495,12 +542,16 @@ def onConnected(interface):
qr = pyqrcode.create(url) qr = pyqrcode.create(url)
print(qr.terminal()) print(qr.terminal())
have_tunnel = platform.system() == 'Linux'
if have_tunnel and args.tunnel: if have_tunnel and args.tunnel:
# pylint: disable=C0415 # pylint: disable=C0415
from . import tunnel from . import tunnel
# Even if others said we could close, stay open if the user asked for a tunnel # Even if others said we could close, stay open if the user asked for a tunnel
closeNow = False closeNow = False
tunnel.Tunnel(interface, subnet=args.tunnel_net) if interface.noProto:
logging.warning(f"Not starting Tunnel - disabled by noProto")
else:
tunnel.Tunnel(interface, subnet=args.tunnel_net)
# if the user didn't ask for serial debugging output, we might want to exit after we've done our operation # if the user didn't ask for serial debugging output, we might want to exit after we've done our operation
if (not args.seriallog) and closeNow: if (not args.seriallog) and closeNow:
@@ -530,6 +581,7 @@ def subscribe():
def export_config(interface): def export_config(interface):
"""used in--export-config""" """used in--export-config"""
owner = interface.getLongName() owner = interface.getLongName()
owner_short = interface.getShortName()
channel_url = interface.localNode.getURL() channel_url = interface.localNode.getURL()
myinfo = interface.getMyNodeInfo() myinfo = interface.getMyNodeInfo()
pos = myinfo.get('position') pos = myinfo.get('position')
@@ -544,8 +596,13 @@ def export_config(interface):
config = "# start of Meshtastic configure yaml\n" config = "# start of Meshtastic configure yaml\n"
if owner: if owner:
config += f"owner: {owner}\n\n" config += f"owner: {owner}\n\n"
if owner_short:
config += f"owner_short: {owner_short}\n\n"
if channel_url: if channel_url:
config += f"channel_url: {channel_url}\n\n" if Globals.getInstance().get_camel_case():
config += f"channelUrl: {channel_url}\n\n"
else:
config += f"channel_url: {channel_url}\n\n"
if lat or lon or alt: if lat or lon or alt:
config += "location:\n" config += "location:\n"
if lat: if lat:
@@ -558,15 +615,23 @@ def export_config(interface):
preferences = f'{interface.localNode.radioConfig.preferences}' preferences = f'{interface.localNode.radioConfig.preferences}'
prefs = preferences.splitlines() prefs = preferences.splitlines()
if prefs: if prefs:
config += "user_prefs:\n" if Globals.getInstance().get_camel_case():
config += "userPrefs:\n"
else:
config += "user_prefs:\n"
for pref in prefs: for pref in prefs:
config += f" {meshtastic.util.quoteBooleans(pref)}\n" if Globals.getInstance().get_camel_case():
# Note: This may not work if the value has '_'
config += f" {meshtastic.util.snake_to_camel(meshtastic.util.quoteBooleans(pref))}\n"
else:
config += f" {meshtastic.util.quoteBooleans(pref)}\n"
print(config) print(config)
return config return config
def common(): def common():
"""Shared code for all of our command line wrappers""" """Shared code for all of our command line wrappers"""
logfile = None
our_globals = Globals.getInstance() our_globals = Globals.getInstance()
args = our_globals.get_args() args = our_globals.get_args()
parser = our_globals.get_parser() parser = our_globals.get_parser()
@@ -585,13 +650,8 @@ def common():
channelIndex = int(args.ch_index) channelIndex = int(args.ch_index)
our_globals.set_channel_index(channelIndex) our_globals.set_channel_index(channelIndex)
# Some commands require dest to be set, so we now use destOrAll/destOrLocal for more lenient commands
if not args.dest: if not args.dest:
args.destOrAll = "^all" args.dest = BROADCAST_ADDR
args.destOrLocal = "^local"
else:
args.destOrAll = args.dest
args.destOrLocal = args.dest # FIXME, temp hack for debugging remove
if not args.seriallog: if not args.seriallog:
if args.noproto: if args.noproto:
@@ -621,23 +681,31 @@ def common():
logging.info(f"Logging serial output to {args.seriallog}") logging.info(f"Logging serial output to {args.seriallog}")
# Note: using "line buffering" # Note: using "line buffering"
# pylint: disable=R1732 # pylint: disable=R1732
logfile = open(args.seriallog, 'w+', logfile = open(args.seriallog, 'w+', buffering=1, encoding='utf8')
buffering=1, encoding='utf8') our_globals.set_logfile(logfile)
subscribe() subscribe()
if args.ble: if args.ble:
client = meshtastic.ble_interface.BLEInterface(args.ble, debugOut=logfile, noProto=args.noproto) client = BLEInterface(args.ble, debugOut=logfile, noProto=args.noproto)
elif args.host: elif args.host:
client = meshtastic.tcp_interface.TCPInterface( client = meshtastic.tcp_interface.TCPInterface(args.host, debugOut=logfile, noProto=args.noproto)
args.host, debugOut=logfile, noProto=args.noproto)
else: else:
client = meshtastic.serial_interface.SerialInterface( try:
args.port, debugOut=logfile, noProto=args.noproto) client = meshtastic.serial_interface.SerialInterface(args.port, debugOut=logfile, noProto=args.noproto)
except PermissionError as ex:
username = os.getlogin()
message = "Permission Error:\n"
message += " Need to add yourself to the 'dialout' group by running:\n"
message += f" sudo usermod -a -G dialout {username}\n"
message += " After running that command, log out and re-login for it to take effect.\n"
message += f"Error was:{ex}"
meshtastic.util.our_exit(message)
# We assume client is fully connected now # We assume client is fully connected now
onConnected(client) onConnected(client)
if args.noproto or (have_tunnel and args.tunnel): # loop until someone presses ctrlc have_tunnel = platform.system() == 'Linux'
if args.noproto or args.reply or (have_tunnel and args.tunnel): # loop until someone presses ctrlc
while True: while True:
time.sleep(1000) time.sleep(1000)
@@ -685,10 +753,12 @@ def initParser():
action="store_true") action="store_true")
parser.add_argument( parser.add_argument(
"--get", help="Get a preferences field. Use an invalid field such as '0' to get a list of all fields.", nargs=1, action='append') "--get", help=("Get a preferences field. Use an invalid field such as '0' to get a list of all fields."
" Can use either snake_case or camelCase format. (ex: 'ls_secs' or 'lsSecs')"),
nargs=1, action='append')
parser.add_argument( parser.add_argument(
"--set", help="Set a preferences field", nargs=2, action='append') "--set", help="Set a preferences field. Can use either snake_case or camelCase format. (ex: 'ls_secs' or 'lsSecs')", nargs=2, action='append')
parser.add_argument( parser.add_argument(
"--seturl", help="Set a channel URL", action="store") "--seturl", help="Set a channel URL", action="store")
@@ -710,7 +780,12 @@ def initParser():
"--ch-disable", help="Disable the specified channel", action="store_true", dest="ch_disable", default=False) "--ch-disable", help="Disable the specified channel", action="store_true", dest="ch_disable", default=False)
parser.add_argument( parser.add_argument(
"--ch-set", help="Set a channel parameter", nargs=2, action='append') "--ch-set", help=("Set a channel parameter. To see channel settings available:'--ch-set all all --ch-index 0'. "
"Can set the 'psk' using this command. To disable encryption on primary channel:'--ch-set psk none --ch-index 0'. "
"To set encryption with a new random key on second channel:'--ch-set psk random --ch-index 1'. "
"To set encryption back to the default:'--ch-set default --ch-index 0'. To set encryption with your "
"own key: '--ch-set psk 0x1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b --ch-index 0'."),
nargs=2, action='append')
parser.add_argument( parser.add_argument(
"--ch-longslow", help="Change to the long-range and slow channel", action='store_true') "--ch-longslow", help="Change to the long-range and slow channel", action='store_true')
@@ -730,10 +805,12 @@ def initParser():
parser.add_argument( parser.add_argument(
"--ch-shortfast", help="Change to the short-range and fast channel", action='store_true') "--ch-shortfast", help="Change to the short-range and fast channel", action='store_true')
parser.add_argument( parser.add_argument(
"--set-owner", help="Set device owner name", action="store") "--set-owner", help="Set device owner name", action="store")
parser.add_argument(
"--set-owner-short", help="Set device owner short name", action="store")
parser.add_argument( parser.add_argument(
"--set-team", help="Set team affiliation (an invalid team will list valid values)", action="store") "--set-team", help="Set team affiliation (an invalid team will list valid values)", action="store")
@@ -752,6 +829,9 @@ def initParser():
parser.add_argument( parser.add_argument(
"--reboot", help="Tell the destination node to reboot", action="store_true") "--reboot", help="Tell the destination node to reboot", action="store_true")
parser.add_argument(
"--shutdown", help="Tell the destination node to shutdown", action="store_true")
parser.add_argument( parser.add_argument(
"--reply", help="Reply to received messages", "--reply", help="Reply to received messages",
action="store_true") action="store_true")
@@ -802,16 +882,18 @@ def initParser():
parser.add_argument('--unset-router', dest='deprecated', parser.add_argument('--unset-router', dest='deprecated',
action='store_false', help='Deprecated, use "--set is_router false" instead') action='store_false', help='Deprecated, use "--set is_router false" instead')
have_tunnel = platform.system() == 'Linux'
if have_tunnel: if have_tunnel:
parser.add_argument('--tunnel', parser.add_argument('--tunnel', action='store_true',
action='store_true', help="Create a TUN tunnel device for forwarding IP packets over the mesh") help="Create a TUN tunnel device for forwarding IP packets over the mesh")
parser.add_argument( parser.add_argument("--subnet", dest='tunnel_net',
"--subnet", dest='tunnel_net', help="Sets the local-end subnet address for the TUN IP bridge", default=None) help="Sets the local-end subnet address for the TUN IP bridge. (ex: 10.115' which is the default)",
default=None)
parser.set_defaults(deprecated=None) parser.set_defaults(deprecated=None)
parser.add_argument('--version', action='version', the_version = pkg_resources.get_distribution("meshtastic").version
version=f"{pkg_resources.require('meshtastic')[0].version}") parser.add_argument('--version', action='version', version=f"{the_version}")
parser.add_argument( parser.add_argument(
"--support", action='store_true', help="Show support info (useful when troubleshooting an issue)") "--support", action='store_true', help="Show support info (useful when troubleshooting an issue)")
@@ -828,6 +910,10 @@ def main():
our_globals.set_parser(parser) our_globals.set_parser(parser)
initParser() initParser()
common() common()
logfile = our_globals.get_logfile()
if logfile:
logfile.close()
def tunnelMain(): def tunnelMain():

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: admin.proto # source: admin.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message from google.protobuf import message as _message
from google.protobuf import reflection as _reflection from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database from google.protobuf import symbol_database as _symbol_database
@@ -11,167 +12,17 @@ from google.protobuf import symbol_database as _symbol_database
_sym_db = _symbol_database.Default() _sym_db = _symbol_database.Default()
from . import cannedmessages_pb2 as cannedmessages__pb2
from . import channel_pb2 as channel__pb2 from . import channel_pb2 as channel__pb2
from . import mesh_pb2 as mesh__pb2 from . import mesh_pb2 as mesh__pb2
from . import radioconfig_pb2 as radioconfig__pb2 from . import radioconfig_pb2 as radioconfig__pb2
DESCRIPTOR = _descriptor.FileDescriptor( DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0b\x61\x64min.proto\x1a\x14\x63\x61nnedmessages.proto\x1a\rchannel.proto\x1a\nmesh.proto\x1a\x11radioconfig.proto\"\x87\x0c\n\x0c\x41\x64minMessage\x12!\n\tset_radio\x18\x01 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1a\n\tset_owner\x18\x02 \x01(\x0b\x32\x05.UserH\x00\x12\x1f\n\x0bset_channel\x18\x03 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_radio_request\x18\x04 \x01(\x08H\x00\x12*\n\x12get_radio_response\x18\x05 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1d\n\x13get_channel_request\x18\x06 \x01(\rH\x00\x12(\n\x14get_channel_response\x18\x07 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_owner_request\x18\x08 \x01(\x08H\x00\x12#\n\x12get_owner_response\x18\t \x01(\x0b\x32\x05.UserH\x00\x12\x1d\n\x13\x63onfirm_set_channel\x18 \x01(\x08H\x00\x12\x1b\n\x11\x63onfirm_set_radio\x18! \x01(\x08H\x00\x12\x18\n\x0e\x65xit_simulator\x18\" \x01(\x08H\x00\x12\x18\n\x0ereboot_seconds\x18# \x01(\x05H\x00\x12\x31\n\'get_canned_message_plugin_part1_request\x18$ \x01(\x08H\x00\x12T\n(get_canned_message_plugin_part1_response\x18% \x01(\x0b\x32 .CannedMessagePluginMessagePart1H\x00\x12\x31\n\'get_canned_message_plugin_part2_request\x18& \x01(\x08H\x00\x12T\n(get_canned_message_plugin_part2_response\x18\' \x01(\x0b\x32 .CannedMessagePluginMessagePart2H\x00\x12\x31\n\'get_canned_message_plugin_part3_request\x18( \x01(\x08H\x00\x12T\n(get_canned_message_plugin_part3_response\x18) \x01(\x0b\x32 .CannedMessagePluginMessagePart3H\x00\x12\x31\n\'get_canned_message_plugin_part4_request\x18* \x01(\x08H\x00\x12T\n(get_canned_message_plugin_part4_response\x18+ \x01(\x0b\x32 .CannedMessagePluginMessagePart4H\x00\x12\x31\n\'get_canned_message_plugin_part5_request\x18, \x01(\x08H\x00\x12T\n(get_canned_message_plugin_part5_response\x18- \x01(\x0b\x32 .CannedMessagePluginMessagePart5H\x00\x12K\n\x1fset_canned_message_plugin_part1\x18. \x01(\x0b\x32 .CannedMessagePluginMessagePart1H\x00\x12K\n\x1fset_canned_message_plugin_part2\x18/ \x01(\x0b\x32 .CannedMessagePluginMessagePart2H\x00\x12K\n\x1fset_canned_message_plugin_part3\x18\x30 \x01(\x0b\x32 .CannedMessagePluginMessagePart3H\x00\x12K\n\x1fset_canned_message_plugin_part4\x18\x31 \x01(\x0b\x32 .CannedMessagePluginMessagePart4H\x00\x12K\n\x1fset_canned_message_plugin_part5\x18\x32 \x01(\x0b\x32 .CannedMessagePluginMessagePart5H\x00\x12\x1a\n\x10shutdown_seconds\x18\x33 \x01(\x05H\x00\x42\t\n\x07variantBG\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3')
name='admin.proto',
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\013AdminProtosH\003Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\x0b\x61\x64min.proto\x1a\rchannel.proto\x1a\nmesh.proto\x1a\x11radioconfig.proto\"\xfb\x02\n\x0c\x41\x64minMessage\x12!\n\tset_radio\x18\x01 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1a\n\tset_owner\x18\x02 \x01(\x0b\x32\x05.UserH\x00\x12\x1f\n\x0bset_channel\x18\x03 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_radio_request\x18\x04 \x01(\x08H\x00\x12*\n\x12get_radio_response\x18\x05 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1d\n\x13get_channel_request\x18\x06 \x01(\rH\x00\x12(\n\x14get_channel_response\x18\x07 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1d\n\x13\x63onfirm_set_channel\x18 \x01(\x08H\x00\x12\x1b\n\x11\x63onfirm_set_radio\x18! \x01(\x08H\x00\x12\x18\n\x0e\x65xit_simulator\x18\" \x01(\x08H\x00\x12\x18\n\x0ereboot_seconds\x18# \x01(\x05H\x00\x42\t\n\x07variantBG\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
,
dependencies=[channel__pb2.DESCRIPTOR,mesh__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,])
_ADMINMESSAGE = DESCRIPTOR.message_types_by_name['AdminMessage']
_ADMINMESSAGE = _descriptor.Descriptor(
name='AdminMessage',
full_name='AdminMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='set_radio', full_name='AdminMessage.set_radio', index=0,
number=1, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='set_owner', full_name='AdminMessage.set_owner', index=1,
number=2, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='set_channel', full_name='AdminMessage.set_channel', index=2,
number=3, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='get_radio_request', full_name='AdminMessage.get_radio_request', index=3,
number=4, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='get_radio_response', full_name='AdminMessage.get_radio_response', index=4,
number=5, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='get_channel_request', full_name='AdminMessage.get_channel_request', index=5,
number=6, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='get_channel_response', full_name='AdminMessage.get_channel_response', index=6,
number=7, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='confirm_set_channel', full_name='AdminMessage.confirm_set_channel', index=7,
number=32, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='confirm_set_radio', full_name='AdminMessage.confirm_set_radio', index=8,
number=33, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='exit_simulator', full_name='AdminMessage.exit_simulator', index=9,
number=34, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='reboot_seconds', full_name='AdminMessage.reboot_seconds', index=10,
number=35, type=5, cpp_type=1, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
_descriptor.OneofDescriptor(
name='variant', full_name='AdminMessage.variant',
index=0, containing_type=None, fields=[]),
],
serialized_start=62,
serialized_end=441,
)
_ADMINMESSAGE.fields_by_name['set_radio'].message_type = radioconfig__pb2._RADIOCONFIG
_ADMINMESSAGE.fields_by_name['set_owner'].message_type = mesh__pb2._USER
_ADMINMESSAGE.fields_by_name['set_channel'].message_type = channel__pb2._CHANNEL
_ADMINMESSAGE.fields_by_name['get_radio_response'].message_type = radioconfig__pb2._RADIOCONFIG
_ADMINMESSAGE.fields_by_name['get_channel_response'].message_type = channel__pb2._CHANNEL
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['set_radio'])
_ADMINMESSAGE.fields_by_name['set_radio'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['set_owner'])
_ADMINMESSAGE.fields_by_name['set_owner'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['set_channel'])
_ADMINMESSAGE.fields_by_name['set_channel'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_radio_request'])
_ADMINMESSAGE.fields_by_name['get_radio_request'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_radio_response'])
_ADMINMESSAGE.fields_by_name['get_radio_response'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_channel_request'])
_ADMINMESSAGE.fields_by_name['get_channel_request'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_channel_response'])
_ADMINMESSAGE.fields_by_name['get_channel_response'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['confirm_set_channel'])
_ADMINMESSAGE.fields_by_name['confirm_set_channel'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['confirm_set_radio'])
_ADMINMESSAGE.fields_by_name['confirm_set_radio'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['exit_simulator'])
_ADMINMESSAGE.fields_by_name['exit_simulator'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['reboot_seconds'])
_ADMINMESSAGE.fields_by_name['reboot_seconds'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
DESCRIPTOR.message_types_by_name['AdminMessage'] = _ADMINMESSAGE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
AdminMessage = _reflection.GeneratedProtocolMessageType('AdminMessage', (_message.Message,), { AdminMessage = _reflection.GeneratedProtocolMessageType('AdminMessage', (_message.Message,), {
'DESCRIPTOR' : _ADMINMESSAGE, 'DESCRIPTOR' : _ADMINMESSAGE,
'__module__' : 'admin_pb2' '__module__' : 'admin_pb2'
@@ -179,6 +30,10 @@ AdminMessage = _reflection.GeneratedProtocolMessageType('AdminMessage', (_messag
}) })
_sym_db.RegisterMessage(AdminMessage) _sym_db.RegisterMessage(AdminMessage)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\013AdminProtosH\003Z!github.com/meshtastic/gomeshproto'
_ADMINMESSAGE._serialized_start=84
_ADMINMESSAGE._serialized_end=1627
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: apponly.proto # source: apponly.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message from google.protobuf import message as _message
from google.protobuf import reflection as _reflection from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database from google.protobuf import symbol_database as _symbol_database
@@ -14,52 +15,11 @@ _sym_db = _symbol_database.Default()
from . import channel_pb2 as channel__pb2 from . import channel_pb2 as channel__pb2
DESCRIPTOR = _descriptor.FileDescriptor( DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rapponly.proto\x1a\rchannel.proto\"0\n\nChannelSet\x12\"\n\x08settings\x18\x01 \x03(\x0b\x32\x10.ChannelSettingsBI\n\x13\x63om.geeksville.meshB\rAppOnlyProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3')
name='apponly.proto',
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\rAppOnlyProtosH\003Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\rapponly.proto\x1a\rchannel.proto\"0\n\nChannelSet\x12\"\n\x08settings\x18\x01 \x03(\x0b\x32\x10.ChannelSettingsBI\n\x13\x63om.geeksville.meshB\rAppOnlyProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
,
dependencies=[channel__pb2.DESCRIPTOR,])
_CHANNELSET = DESCRIPTOR.message_types_by_name['ChannelSet']
_CHANNELSET = _descriptor.Descriptor(
name='ChannelSet',
full_name='ChannelSet',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='settings', full_name='ChannelSet.settings', index=0,
number=1, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=32,
serialized_end=80,
)
_CHANNELSET.fields_by_name['settings'].message_type = channel__pb2._CHANNELSETTINGS
DESCRIPTOR.message_types_by_name['ChannelSet'] = _CHANNELSET
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
ChannelSet = _reflection.GeneratedProtocolMessageType('ChannelSet', (_message.Message,), { ChannelSet = _reflection.GeneratedProtocolMessageType('ChannelSet', (_message.Message,), {
'DESCRIPTOR' : _CHANNELSET, 'DESCRIPTOR' : _CHANNELSET,
'__module__' : 'apponly_pb2' '__module__' : 'apponly_pb2'
@@ -67,6 +27,10 @@ ChannelSet = _reflection.GeneratedProtocolMessageType('ChannelSet', (_message.Me
}) })
_sym_db.RegisterMessage(ChannelSet) _sym_db.RegisterMessage(ChannelSet)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\rAppOnlyProtosH\003Z!github.com/meshtastic/gomeshproto'
_CHANNELSET._serialized_start=32
_CHANNELSET._serialized_end=80
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -1,10 +1,16 @@
"""Bluetooth interface """Bluetooth interface
""" """
import logging import logging
import pygatt import platform
from meshtastic.mesh_interface import MeshInterface
from meshtastic.util import our_exit
if platform.system() == 'Linux':
# pylint: disable=E0401
import pygatt
from .mesh_interface import MeshInterface
# Our standard BLE characteristics # Our standard BLE characteristics
TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7" TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7"
@@ -16,6 +22,8 @@ class BLEInterface(MeshInterface):
"""A not quite ready - FIXME - BLE interface to devices""" """A not quite ready - FIXME - BLE interface to devices"""
def __init__(self, address, noProto=False, debugOut=None): def __init__(self, address, noProto=False, debugOut=None):
if platform.system() != 'Linux':
our_exit("Linux is the only platform with experimental BLE support.", 1)
self.address = address self.address = address
if not noProto: if not noProto:
self.adapter = pygatt.GATTToolBackend() # BGAPIBackend() self.adapter = pygatt.GATTToolBackend() # BGAPIBackend()
@@ -31,7 +39,7 @@ class BLEInterface(MeshInterface):
self._readFromRadio() # read the initial responses self._readFromRadio() # read the initial responses
def handle_data(handle, data): def handle_data(handle, data): # pylint: disable=W0613
self._handleFromRadio(data) self._handleFromRadio(data)
if self.device: if self.device:

View File

@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: cannedmessages.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 message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x63\x61nnedmessages.proto\"/\n\x1f\x43\x61nnedMessagePluginMessagePart1\x12\x0c\n\x04text\x18\x01 \x01(\t\"/\n\x1f\x43\x61nnedMessagePluginMessagePart2\x12\x0c\n\x04text\x18\x01 \x01(\t\"/\n\x1f\x43\x61nnedMessagePluginMessagePart3\x12\x0c\n\x04text\x18\x01 \x01(\t\"/\n\x1f\x43\x61nnedMessagePluginMessagePart4\x12\x0c\n\x04text\x18\x01 \x01(\t\"/\n\x1f\x43\x61nnedMessagePluginMessagePart5\x12\x0c\n\x04text\x18\x01 \x01(\tBU\n\x13\x63om.geeksville.meshB\x19\x43\x61nnedMessageConfigProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3')
_CANNEDMESSAGEPLUGINMESSAGEPART1 = DESCRIPTOR.message_types_by_name['CannedMessagePluginMessagePart1']
_CANNEDMESSAGEPLUGINMESSAGEPART2 = DESCRIPTOR.message_types_by_name['CannedMessagePluginMessagePart2']
_CANNEDMESSAGEPLUGINMESSAGEPART3 = DESCRIPTOR.message_types_by_name['CannedMessagePluginMessagePart3']
_CANNEDMESSAGEPLUGINMESSAGEPART4 = DESCRIPTOR.message_types_by_name['CannedMessagePluginMessagePart4']
_CANNEDMESSAGEPLUGINMESSAGEPART5 = DESCRIPTOR.message_types_by_name['CannedMessagePluginMessagePart5']
CannedMessagePluginMessagePart1 = _reflection.GeneratedProtocolMessageType('CannedMessagePluginMessagePart1', (_message.Message,), {
'DESCRIPTOR' : _CANNEDMESSAGEPLUGINMESSAGEPART1,
'__module__' : 'cannedmessages_pb2'
# @@protoc_insertion_point(class_scope:CannedMessagePluginMessagePart1)
})
_sym_db.RegisterMessage(CannedMessagePluginMessagePart1)
CannedMessagePluginMessagePart2 = _reflection.GeneratedProtocolMessageType('CannedMessagePluginMessagePart2', (_message.Message,), {
'DESCRIPTOR' : _CANNEDMESSAGEPLUGINMESSAGEPART2,
'__module__' : 'cannedmessages_pb2'
# @@protoc_insertion_point(class_scope:CannedMessagePluginMessagePart2)
})
_sym_db.RegisterMessage(CannedMessagePluginMessagePart2)
CannedMessagePluginMessagePart3 = _reflection.GeneratedProtocolMessageType('CannedMessagePluginMessagePart3', (_message.Message,), {
'DESCRIPTOR' : _CANNEDMESSAGEPLUGINMESSAGEPART3,
'__module__' : 'cannedmessages_pb2'
# @@protoc_insertion_point(class_scope:CannedMessagePluginMessagePart3)
})
_sym_db.RegisterMessage(CannedMessagePluginMessagePart3)
CannedMessagePluginMessagePart4 = _reflection.GeneratedProtocolMessageType('CannedMessagePluginMessagePart4', (_message.Message,), {
'DESCRIPTOR' : _CANNEDMESSAGEPLUGINMESSAGEPART4,
'__module__' : 'cannedmessages_pb2'
# @@protoc_insertion_point(class_scope:CannedMessagePluginMessagePart4)
})
_sym_db.RegisterMessage(CannedMessagePluginMessagePart4)
CannedMessagePluginMessagePart5 = _reflection.GeneratedProtocolMessageType('CannedMessagePluginMessagePart5', (_message.Message,), {
'DESCRIPTOR' : _CANNEDMESSAGEPLUGINMESSAGEPART5,
'__module__' : 'cannedmessages_pb2'
# @@protoc_insertion_point(class_scope:CannedMessagePluginMessagePart5)
})
_sym_db.RegisterMessage(CannedMessagePluginMessagePart5)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\031CannedMessageConfigProtosH\003Z!github.com/meshtastic/gomeshproto'
_CANNEDMESSAGEPLUGINMESSAGEPART1._serialized_start=24
_CANNEDMESSAGEPLUGINMESSAGEPART1._serialized_end=71
_CANNEDMESSAGEPLUGINMESSAGEPART2._serialized_start=73
_CANNEDMESSAGEPLUGINMESSAGEPART2._serialized_end=120
_CANNEDMESSAGEPLUGINMESSAGEPART3._serialized_start=122
_CANNEDMESSAGEPLUGINMESSAGEPART3._serialized_end=169
_CANNEDMESSAGEPLUGINMESSAGEPART4._serialized_start=171
_CANNEDMESSAGEPLUGINMESSAGEPART4._serialized_end=218
_CANNEDMESSAGEPLUGINMESSAGEPART5._serialized_start=220
_CANNEDMESSAGEPLUGINMESSAGEPART5._serialized_end=267
# @@protoc_insertion_point(module_scope)

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: channel.proto # source: channel.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message from google.protobuf import message as _message
from google.protobuf import reflection as _reflection from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database from google.protobuf import symbol_database as _symbol_database
@@ -13,237 +14,14 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor( DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rchannel.proto\"\x91\x03\n\x0f\x43hannelSettings\x12\x10\n\x08tx_power\x18\x01 \x01(\x05\x12\x32\n\x0cmodem_config\x18\x03 \x01(\x0e\x32\x1c.ChannelSettings.ModemConfig\x12\x11\n\tbandwidth\x18\x06 \x01(\r\x12\x15\n\rspread_factor\x18\x07 \x01(\r\x12\x13\n\x0b\x63oding_rate\x18\x08 \x01(\r\x12\x13\n\x0b\x63hannel_num\x18\t \x01(\r\x12\x0b\n\x03psk\x18\x04 \x01(\x0c\x12\x0c\n\x04name\x18\x05 \x01(\t\x12\n\n\x02id\x18\n \x01(\x07\x12\x16\n\x0euplink_enabled\x18\x10 \x01(\x08\x12\x18\n\x10\x64ownlink_enabled\x18\x11 \x01(\x08\"\x8a\x01\n\x0bModemConfig\x12\x12\n\x0e\x42w125Cr45Sf128\x10\x00\x12\x12\n\x0e\x42w500Cr45Sf128\x10\x01\x12\x14\n\x10\x42w31_25Cr48Sf512\x10\x02\x12\x13\n\x0f\x42w125Cr48Sf4096\x10\x03\x12\x13\n\x0f\x42w250Cr46Sf2048\x10\x04\x12\x13\n\x0f\x42w250Cr47Sf1024\x10\x05\"\x8b\x01\n\x07\x43hannel\x12\r\n\x05index\x18\x01 \x01(\x05\x12\"\n\x08settings\x18\x02 \x01(\x0b\x32\x10.ChannelSettings\x12\x1b\n\x04role\x18\x03 \x01(\x0e\x32\r.Channel.Role\"0\n\x04Role\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07PRIMARY\x10\x01\x12\r\n\tSECONDARY\x10\x02\x42I\n\x13\x63om.geeksville.meshB\rChannelProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3')
name='channel.proto',
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\rChannelProtosH\003Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\rchannel.proto\"\x91\x03\n\x0f\x43hannelSettings\x12\x10\n\x08tx_power\x18\x01 \x01(\x05\x12\x32\n\x0cmodem_config\x18\x03 \x01(\x0e\x32\x1c.ChannelSettings.ModemConfig\x12\x11\n\tbandwidth\x18\x06 \x01(\r\x12\x15\n\rspread_factor\x18\x07 \x01(\r\x12\x13\n\x0b\x63oding_rate\x18\x08 \x01(\r\x12\x13\n\x0b\x63hannel_num\x18\t \x01(\r\x12\x0b\n\x03psk\x18\x04 \x01(\x0c\x12\x0c\n\x04name\x18\x05 \x01(\t\x12\n\n\x02id\x18\n \x01(\x07\x12\x16\n\x0euplink_enabled\x18\x10 \x01(\x08\x12\x18\n\x10\x64ownlink_enabled\x18\x11 \x01(\x08\"\x8a\x01\n\x0bModemConfig\x12\x12\n\x0e\x42w125Cr45Sf128\x10\x00\x12\x12\n\x0e\x42w500Cr45Sf128\x10\x01\x12\x14\n\x10\x42w31_25Cr48Sf512\x10\x02\x12\x13\n\x0f\x42w125Cr48Sf4096\x10\x03\x12\x13\n\x0f\x42w250Cr46Sf2048\x10\x04\x12\x13\n\x0f\x42w250Cr47Sf1024\x10\x05\"\x8b\x01\n\x07\x43hannel\x12\r\n\x05index\x18\x01 \x01(\x05\x12\"\n\x08settings\x18\x02 \x01(\x0b\x32\x10.ChannelSettings\x12\x1b\n\x04role\x18\x03 \x01(\x0e\x32\r.Channel.Role\"0\n\x04Role\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07PRIMARY\x10\x01\x12\r\n\tSECONDARY\x10\x02\x42I\n\x13\x63om.geeksville.meshB\rChannelProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
)
_CHANNELSETTINGS_MODEMCONFIG = _descriptor.EnumDescriptor( _CHANNELSETTINGS = DESCRIPTOR.message_types_by_name['ChannelSettings']
name='ModemConfig', _CHANNEL = DESCRIPTOR.message_types_by_name['Channel']
full_name='ChannelSettings.ModemConfig', _CHANNELSETTINGS_MODEMCONFIG = _CHANNELSETTINGS.enum_types_by_name['ModemConfig']
filename=None, _CHANNEL_ROLE = _CHANNEL.enum_types_by_name['Role']
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='Bw125Cr45Sf128', index=0, number=0,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='Bw500Cr45Sf128', index=1, number=1,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='Bw31_25Cr48Sf512', index=2, number=2,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='Bw125Cr48Sf4096', index=3, number=3,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='Bw250Cr46Sf2048', index=4, number=4,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='Bw250Cr47Sf1024', index=5, number=5,
serialized_options=None,
type=None),
],
containing_type=None,
serialized_options=None,
serialized_start=281,
serialized_end=419,
)
_sym_db.RegisterEnumDescriptor(_CHANNELSETTINGS_MODEMCONFIG)
_CHANNEL_ROLE = _descriptor.EnumDescriptor(
name='Role',
full_name='Channel.Role',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='DISABLED', index=0, number=0,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='PRIMARY', index=1, number=1,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='SECONDARY', index=2, number=2,
serialized_options=None,
type=None),
],
containing_type=None,
serialized_options=None,
serialized_start=513,
serialized_end=561,
)
_sym_db.RegisterEnumDescriptor(_CHANNEL_ROLE)
_CHANNELSETTINGS = _descriptor.Descriptor(
name='ChannelSettings',
full_name='ChannelSettings',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='tx_power', full_name='ChannelSettings.tx_power', index=0,
number=1, type=5, cpp_type=1, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='modem_config', full_name='ChannelSettings.modem_config', index=1,
number=3, type=14, cpp_type=8, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='bandwidth', full_name='ChannelSettings.bandwidth', index=2,
number=6, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='spread_factor', full_name='ChannelSettings.spread_factor', index=3,
number=7, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='coding_rate', full_name='ChannelSettings.coding_rate', index=4,
number=8, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='channel_num', full_name='ChannelSettings.channel_num', index=5,
number=9, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='psk', full_name='ChannelSettings.psk', index=6,
number=4, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='name', full_name='ChannelSettings.name', index=7,
number=5, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='id', full_name='ChannelSettings.id', index=8,
number=10, type=7, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='uplink_enabled', full_name='ChannelSettings.uplink_enabled', index=9,
number=16, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='downlink_enabled', full_name='ChannelSettings.downlink_enabled', index=10,
number=17, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
_CHANNELSETTINGS_MODEMCONFIG,
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=18,
serialized_end=419,
)
_CHANNEL = _descriptor.Descriptor(
name='Channel',
full_name='Channel',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='index', full_name='Channel.index', index=0,
number=1, type=5, cpp_type=1, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='settings', full_name='Channel.settings', index=1,
number=2, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='role', full_name='Channel.role', index=2,
number=3, type=14, cpp_type=8, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
_CHANNEL_ROLE,
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=422,
serialized_end=561,
)
_CHANNELSETTINGS.fields_by_name['modem_config'].enum_type = _CHANNELSETTINGS_MODEMCONFIG
_CHANNELSETTINGS_MODEMCONFIG.containing_type = _CHANNELSETTINGS
_CHANNEL.fields_by_name['settings'].message_type = _CHANNELSETTINGS
_CHANNEL.fields_by_name['role'].enum_type = _CHANNEL_ROLE
_CHANNEL_ROLE.containing_type = _CHANNEL
DESCRIPTOR.message_types_by_name['ChannelSettings'] = _CHANNELSETTINGS
DESCRIPTOR.message_types_by_name['Channel'] = _CHANNEL
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
ChannelSettings = _reflection.GeneratedProtocolMessageType('ChannelSettings', (_message.Message,), { ChannelSettings = _reflection.GeneratedProtocolMessageType('ChannelSettings', (_message.Message,), {
'DESCRIPTOR' : _CHANNELSETTINGS, 'DESCRIPTOR' : _CHANNELSETTINGS,
'__module__' : 'channel_pb2' '__module__' : 'channel_pb2'
@@ -258,6 +36,16 @@ Channel = _reflection.GeneratedProtocolMessageType('Channel', (_message.Message,
}) })
_sym_db.RegisterMessage(Channel) _sym_db.RegisterMessage(Channel)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\rChannelProtosH\003Z!github.com/meshtastic/gomeshproto'
_CHANNELSETTINGS._serialized_start=18
_CHANNELSETTINGS._serialized_end=419
_CHANNELSETTINGS_MODEMCONFIG._serialized_start=281
_CHANNELSETTINGS_MODEMCONFIG._serialized_end=419
_CHANNEL._serialized_start=422
_CHANNEL._serialized_end=561
_CHANNEL_ROLE._serialized_start=513
_CHANNEL_ROLE._serialized_end=561
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: deviceonly.proto # source: deviceonly.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message from google.protobuf import message as _message
from google.protobuf import reflection as _reflection from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database from google.protobuf import symbol_database as _symbol_database
@@ -16,211 +17,14 @@ from . import mesh_pb2 as mesh__pb2
from . import radioconfig_pb2 as radioconfig__pb2 from . import radioconfig_pb2 as radioconfig__pb2
DESCRIPTOR = _descriptor.FileDescriptor( DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x64\x65viceonly.proto\x1a\rchannel.proto\x1a\nmesh.proto\x1a\x11radioconfig.proto\"\x80\x01\n\x11LegacyRadioConfig\x12\x39\n\x0bpreferences\x18\x01 \x01(\x0b\x32$.LegacyRadioConfig.LegacyPreferences\x1a\x30\n\x11LegacyPreferences\x12\x1b\n\x06region\x18\x0f \x01(\x0e\x32\x0b.RegionCode\"\xf0\x03\n\x0b\x44\x65viceState\x12\'\n\x0blegacyRadio\x18\x01 \x01(\x0b\x32\x12.LegacyRadioConfig\x12\x1c\n\x07my_node\x18\x02 \x01(\x0b\x32\x0b.MyNodeInfo\x12\x14\n\x05owner\x18\x03 \x01(\x0b\x32\x05.User\x12\x1a\n\x07node_db\x18\x04 \x03(\x0b\x32\t.NodeInfo\x12\"\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x0b.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12$\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x0b.MeshPacket\x12\x0f\n\x07no_save\x18\t \x01(\x08\x12\x15\n\rdid_gps_reset\x18\x0b \x01(\x08\x12+\n#canned_message_plugin_message_part1\x18\r \x01(\t\x12+\n#canned_message_plugin_message_part2\x18\x0e \x01(\t\x12+\n#canned_message_plugin_message_part3\x18\x0f \x01(\t\x12+\n#canned_message_plugin_message_part4\x18\x10 \x01(\t\x12+\n#canned_message_plugin_message_part5\x18\x11 \x01(\tJ\x04\x08\x0c\x10\r\")\n\x0b\x43hannelFile\x12\x1a\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x08.ChannelBF\n\x13\x63om.geeksville.meshB\nDeviceOnlyH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3')
name='deviceonly.proto',
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\nDeviceOnlyH\003Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\x10\x64\x65viceonly.proto\x1a\rchannel.proto\x1a\nmesh.proto\x1a\x11radioconfig.proto\"\x80\x01\n\x11LegacyRadioConfig\x12\x39\n\x0bpreferences\x18\x01 \x01(\x0b\x32$.LegacyRadioConfig.LegacyPreferences\x1a\x30\n\x11LegacyPreferences\x12\x1b\n\x06region\x18\x0f \x01(\x0e\x32\x0b.RegionCode\"\x8f\x02\n\x0b\x44\x65viceState\x12\'\n\x0blegacyRadio\x18\x01 \x01(\x0b\x32\x12.LegacyRadioConfig\x12\x1c\n\x07my_node\x18\x02 \x01(\x0b\x32\x0b.MyNodeInfo\x12\x14\n\x05owner\x18\x03 \x01(\x0b\x32\x05.User\x12\x1a\n\x07node_db\x18\x04 \x03(\x0b\x32\t.NodeInfo\x12\"\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x0b.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12$\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x0b.MeshPacket\x12\x0f\n\x07no_save\x18\t \x01(\x08\x12\x15\n\rdid_gps_reset\x18\x0b \x01(\x08J\x04\x08\x0c\x10\r\")\n\x0b\x43hannelFile\x12\x1a\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x08.ChannelBF\n\x13\x63om.geeksville.meshB\nDeviceOnlyH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
,
dependencies=[channel__pb2.DESCRIPTOR,mesh__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,])
_LEGACYRADIOCONFIG = DESCRIPTOR.message_types_by_name['LegacyRadioConfig']
_LEGACYRADIOCONFIG_LEGACYPREFERENCES = _descriptor.Descriptor( _LEGACYRADIOCONFIG_LEGACYPREFERENCES = _LEGACYRADIOCONFIG.nested_types_by_name['LegacyPreferences']
name='LegacyPreferences', _DEVICESTATE = DESCRIPTOR.message_types_by_name['DeviceState']
full_name='LegacyRadioConfig.LegacyPreferences', _CHANNELFILE = DESCRIPTOR.message_types_by_name['ChannelFile']
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='region', full_name='LegacyRadioConfig.LegacyPreferences.region', index=0,
number=15, type=14, cpp_type=8, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=147,
serialized_end=195,
)
_LEGACYRADIOCONFIG = _descriptor.Descriptor(
name='LegacyRadioConfig',
full_name='LegacyRadioConfig',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='preferences', full_name='LegacyRadioConfig.preferences', index=0,
number=1, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[_LEGACYRADIOCONFIG_LEGACYPREFERENCES, ],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=67,
serialized_end=195,
)
_DEVICESTATE = _descriptor.Descriptor(
name='DeviceState',
full_name='DeviceState',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='legacyRadio', full_name='DeviceState.legacyRadio', index=0,
number=1, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='my_node', full_name='DeviceState.my_node', index=1,
number=2, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='owner', full_name='DeviceState.owner', index=2,
number=3, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='node_db', full_name='DeviceState.node_db', index=3,
number=4, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='receive_queue', full_name='DeviceState.receive_queue', index=4,
number=5, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='version', full_name='DeviceState.version', index=5,
number=8, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='rx_text_message', full_name='DeviceState.rx_text_message', index=6,
number=7, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='no_save', full_name='DeviceState.no_save', index=7,
number=9, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='did_gps_reset', full_name='DeviceState.did_gps_reset', index=8,
number=11, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=198,
serialized_end=469,
)
_CHANNELFILE = _descriptor.Descriptor(
name='ChannelFile',
full_name='ChannelFile',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='channels', full_name='ChannelFile.channels', index=0,
number=1, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=471,
serialized_end=512,
)
_LEGACYRADIOCONFIG_LEGACYPREFERENCES.fields_by_name['region'].enum_type = radioconfig__pb2._REGIONCODE
_LEGACYRADIOCONFIG_LEGACYPREFERENCES.containing_type = _LEGACYRADIOCONFIG
_LEGACYRADIOCONFIG.fields_by_name['preferences'].message_type = _LEGACYRADIOCONFIG_LEGACYPREFERENCES
_DEVICESTATE.fields_by_name['legacyRadio'].message_type = _LEGACYRADIOCONFIG
_DEVICESTATE.fields_by_name['my_node'].message_type = mesh__pb2._MYNODEINFO
_DEVICESTATE.fields_by_name['owner'].message_type = mesh__pb2._USER
_DEVICESTATE.fields_by_name['node_db'].message_type = mesh__pb2._NODEINFO
_DEVICESTATE.fields_by_name['receive_queue'].message_type = mesh__pb2._MESHPACKET
_DEVICESTATE.fields_by_name['rx_text_message'].message_type = mesh__pb2._MESHPACKET
_CHANNELFILE.fields_by_name['channels'].message_type = channel__pb2._CHANNEL
DESCRIPTOR.message_types_by_name['LegacyRadioConfig'] = _LEGACYRADIOCONFIG
DESCRIPTOR.message_types_by_name['DeviceState'] = _DEVICESTATE
DESCRIPTOR.message_types_by_name['ChannelFile'] = _CHANNELFILE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
LegacyRadioConfig = _reflection.GeneratedProtocolMessageType('LegacyRadioConfig', (_message.Message,), { LegacyRadioConfig = _reflection.GeneratedProtocolMessageType('LegacyRadioConfig', (_message.Message,), {
'LegacyPreferences' : _reflection.GeneratedProtocolMessageType('LegacyPreferences', (_message.Message,), { 'LegacyPreferences' : _reflection.GeneratedProtocolMessageType('LegacyPreferences', (_message.Message,), {
@@ -250,6 +54,16 @@ ChannelFile = _reflection.GeneratedProtocolMessageType('ChannelFile', (_message.
}) })
_sym_db.RegisterMessage(ChannelFile) _sym_db.RegisterMessage(ChannelFile)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nDeviceOnlyH\003Z!github.com/meshtastic/gomeshproto'
_LEGACYRADIOCONFIG._serialized_start=67
_LEGACYRADIOCONFIG._serialized_end=195
_LEGACYRADIOCONFIG_LEGACYPREFERENCES._serialized_start=147
_LEGACYRADIOCONFIG_LEGACYPREFERENCES._serialized_end=195
_DEVICESTATE._serialized_start=198
_DEVICESTATE._serialized_end=694
_CHANNELFILE._serialized_start=696
_CHANNELFILE._serialized_end=737
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: environmental_measurement.proto # source: environmental_measurement.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message from google.protobuf import message as _message
from google.protobuf import reflection as _reflection from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database from google.protobuf import symbol_database as _symbol_database
@@ -13,64 +14,11 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor( DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1f\x65nvironmental_measurement.proto\"g\n\x18\x45nvironmentalMeasurement\x12\x13\n\x0btemperature\x18\x01 \x01(\x02\x12\x19\n\x11relative_humidity\x18\x02 \x01(\x02\x12\x1b\n\x13\x62\x61rometric_pressure\x18\x03 \x01(\x02\x42#Z!github.com/meshtastic/gomeshprotob\x06proto3')
name='environmental_measurement.proto',
package='',
syntax='proto3',
serialized_options=b'Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\x1f\x65nvironmental_measurement.proto\"g\n\x18\x45nvironmentalMeasurement\x12\x13\n\x0btemperature\x18\x01 \x01(\x02\x12\x19\n\x11relative_humidity\x18\x02 \x01(\x02\x12\x1b\n\x13\x62\x61rometric_pressure\x18\x03 \x01(\x02\x42#Z!github.com/meshtastic/gomeshprotob\x06proto3'
)
_ENVIRONMENTALMEASUREMENT = DESCRIPTOR.message_types_by_name['EnvironmentalMeasurement']
_ENVIRONMENTALMEASUREMENT = _descriptor.Descriptor(
name='EnvironmentalMeasurement',
full_name='EnvironmentalMeasurement',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='temperature', full_name='EnvironmentalMeasurement.temperature', index=0,
number=1, type=2, cpp_type=6, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='relative_humidity', full_name='EnvironmentalMeasurement.relative_humidity', index=1,
number=2, type=2, cpp_type=6, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='barometric_pressure', full_name='EnvironmentalMeasurement.barometric_pressure', index=2,
number=3, type=2, cpp_type=6, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=35,
serialized_end=138,
)
DESCRIPTOR.message_types_by_name['EnvironmentalMeasurement'] = _ENVIRONMENTALMEASUREMENT
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
EnvironmentalMeasurement = _reflection.GeneratedProtocolMessageType('EnvironmentalMeasurement', (_message.Message,), { EnvironmentalMeasurement = _reflection.GeneratedProtocolMessageType('EnvironmentalMeasurement', (_message.Message,), {
'DESCRIPTOR' : _ENVIRONMENTALMEASUREMENT, 'DESCRIPTOR' : _ENVIRONMENTALMEASUREMENT,
'__module__' : 'environmental_measurement_pb2' '__module__' : 'environmental_measurement_pb2'
@@ -78,6 +26,10 @@ EnvironmentalMeasurement = _reflection.GeneratedProtocolMessageType('Environment
}) })
_sym_db.RegisterMessage(EnvironmentalMeasurement) _sym_db.RegisterMessage(EnvironmentalMeasurement)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'Z!github.com/meshtastic/gomeshproto'
_ENVIRONMENTALMEASUREMENT._serialized_start=35
_ENVIRONMENTALMEASUREMENT._serialized_end=138
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -27,16 +27,23 @@ class Globals:
Globals.__instance = self Globals.__instance = self
self.args = None self.args = None
self.parser = None self.parser = None
self.target_node = None
self.channel_index = None self.channel_index = None
self.logfile = None
self.tunnelInstance = None
# TODO: to migrate to camel_case for v1.3 change this value to True
self.camel_case = False
def reset(self): def reset(self):
"""Reset all of our globals. If you add a member, add it to this method, too.""" """Reset all of our globals. If you add a member, add it to this method, too."""
self.args = None self.args = None
self.parser = None self.parser = None
self.target_node = None
self.channel_index = None self.channel_index = None
self.logfile = None
self.tunnelInstance = None
# TODO: to migrate to camel_case for v1.3 change this value to True
self.camel_case = False
# setters
def set_args(self, args): def set_args(self, args):
"""Set the args""" """Set the args"""
self.args = args self.args = args
@@ -45,14 +52,23 @@ class Globals:
"""Set the parser""" """Set the parser"""
self.parser = parser self.parser = parser
def set_target_node(self, target_node):
"""Set the target_node"""
self.target_node = target_node
def set_channel_index(self, channel_index): def set_channel_index(self, channel_index):
"""Set the channel_index""" """Set the channel_index"""
self.channel_index = channel_index self.channel_index = channel_index
def set_logfile(self, logfile):
"""Set the logfile"""
self.logfile = logfile
def set_tunnelInstance(self, tunnelInstance):
"""Set the tunnelInstance"""
self.tunnelInstance = tunnelInstance
def set_camel_case(self):
"""Force using camelCase for things like prefs/set/set"""
self.camel_case = True
# getters
def get_args(self): def get_args(self):
"""Get args""" """Get args"""
return self.args return self.args
@@ -61,10 +77,18 @@ class Globals:
"""Get parser""" """Get parser"""
return self.parser return self.parser
def get_target_node(self):
"""Get target_node"""
return self.target_node
def get_channel_index(self): def get_channel_index(self):
"""Get channel_index""" """Get channel_index"""
return self.channel_index return self.channel_index
def get_logfile(self):
"""Get logfile"""
return self.logfile
def get_tunnelInstance(self):
"""Get tunnelInstance"""
return self.tunnelInstance
def get_camel_case(self):
"""Get whether or not to use camelCase"""
return self.camel_case

View File

@@ -17,9 +17,9 @@ from google.protobuf.json_format import MessageToJson
import meshtastic.node import meshtastic.node
from . import portnums_pb2, mesh_pb2 from meshtastic import portnums_pb2, mesh_pb2
from .util import stripnl, Timeout, our_exit from meshtastic.util import stripnl, Timeout, our_exit, remove_keys_from_dict, convert_mac_addr
from .__init__ import LOCAL_ADDR, BROADCAST_NUM, BROADCAST_ADDR, ResponseHandler, publishingThread, OUR_APP_VERSION, protocols from meshtastic.__init__ import LOCAL_ADDR, BROADCAST_NUM, BROADCAST_ADDR, ResponseHandler, publishingThread, OUR_APP_VERSION, protocols
class MeshInterface: class MeshInterface:
"""Interface class for meshtastic devices """Interface class for meshtastic devices
@@ -68,13 +68,12 @@ class MeshInterface:
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, traceback):
if exc_type is not None and exc_value is not None: if exc_type is not None and exc_value is not None:
logging.error( logging.error(f'An exception of type {exc_type} with value {exc_value} has occurred')
f'An exception of type {exc_type} with value {exc_value} has occurred')
if traceback is not None: if traceback is not None:
logging.error(f'Traceback: {traceback}') logging.error(f'Traceback: {traceback}')
self.close() self.close()
def showInfo(self, file=sys.stdout): def showInfo(self, file=sys.stdout): # pylint: disable=W0613
"""Show human readable summary about this object""" """Show human readable summary about this object"""
owner = f"Owner: {self.getLongName()} ({self.getShortName()})" owner = f"Owner: {self.getLongName()} ({self.getShortName()})"
myinfo = '' myinfo = ''
@@ -84,12 +83,24 @@ class MeshInterface:
nodes = "" nodes = ""
if self.nodes: if self.nodes:
for n in self.nodes.values(): for n in self.nodes.values():
nodes = nodes + f" {stripnl(n)}" # when the TBeam is first booted, it sometimes shows the raw data
# so, we will just remove any raw keys
keys_to_remove = ('raw', 'decoded', 'payload')
n2 = remove_keys_from_dict(keys_to_remove, n)
# if we have 'macaddr', re-format it
if 'macaddr' in n2['user']:
val = n2['user']['macaddr']
# decode the base64 value
addr = convert_mac_addr(val)
n2['user']['macaddr'] = addr
nodes = nodes + f" {stripnl(n2)}"
infos = owner + myinfo + mesh + nodes infos = owner + myinfo + mesh + nodes
print(infos) print(infos)
return infos return infos
def showNodes(self, includeSelf=True, file=sys.stdout): def showNodes(self, includeSelf=True, file=sys.stdout): # pylint: disable=W0613
"""Show table summary of nodes in mesh""" """Show table summary of nodes in mesh"""
def formatFloat(value, precision=2, unit=''): def formatFloat(value, precision=2, unit=''):
"""Format a float value with precsion.""" """Format a float value with precsion."""
@@ -148,7 +159,7 @@ class MeshInterface:
def getNode(self, nodeId): def getNode(self, nodeId):
"""Return a node object which contains device settings and channel info""" """Return a node object which contains device settings and channel info"""
if nodeId == LOCAL_ADDR: if nodeId in (LOCAL_ADDR, BROADCAST_ADDR):
return self.localNode return self.localNode
else: else:
n = meshtastic.node.Node(self, nodeId) n = meshtastic.node.Node(self, nodeId)
@@ -382,11 +393,11 @@ class MeshInterface:
return user.get('shortName', None) return user.get('shortName', None)
return None return None
def _waitConnected(self): def _waitConnected(self, timeout=15.0):
"""Block until the initial node db download is complete, or timeout """Block until the initial node db download is complete, or timeout
and raise an exception""" and raise an exception"""
if not self.noProto: if not self.noProto:
if not self.isConnected.wait(15.0): # timeout after x seconds if not self.isConnected.wait(timeout): # timeout after x seconds
raise Exception("Timed out waiting for connection completion") raise Exception("Timed out waiting for connection completion")
# If we failed while connecting, raise the connection to the client # If we failed while connecting, raise the connection to the client
@@ -404,8 +415,7 @@ class MeshInterface:
def _disconnected(self): def _disconnected(self):
"""Called by subclasses to tell clients this interface has disconnected""" """Called by subclasses to tell clients this interface has disconnected"""
self.isConnected.clear() self.isConnected.clear()
publishingThread.queueWork(lambda: pub.sendMessage( publishingThread.queueWork(lambda: pub.sendMessage("meshtastic.connection.lost", interface=self))
"meshtastic.connection.lost", interface=self))
def _startHeartbeat(self): def _startHeartbeat(self):
"""We need to send a heartbeat message to the device every X seconds""" """We need to send a heartbeat message to the device every X seconds"""
@@ -431,8 +441,7 @@ class MeshInterface:
if not self.isConnected.is_set(): if not self.isConnected.is_set():
self.isConnected.set() self.isConnected.set()
self._startHeartbeat() self._startHeartbeat()
publishingThread.queueWork(lambda: pub.sendMessage( publishingThread.queueWork(lambda: pub.sendMessage("meshtastic.connection.established", interface=self))
"meshtastic.connection.established", interface=self))
def _startConfig(self): def _startConfig(self):
"""Start device packets flowing""" """Start device packets flowing"""
@@ -504,7 +513,8 @@ class MeshInterface:
elif fromRadio.HasField("node_info"): elif fromRadio.HasField("node_info"):
node = asDict["nodeInfo"] node = asDict["nodeInfo"]
try: try:
self._fixupPosition(node["position"]) newpos = self._fixupPosition(node["position"])
node["position"] = newpos
except: except:
logging.debug("Node without position") logging.debug("Node without position")
@@ -536,12 +546,14 @@ class MeshInterface:
"""Convert integer lat/lon into floats """Convert integer lat/lon into floats
Arguments: Arguments:
position {Position dictionary} -- object ot fix up position {Position dictionary} -- object to fix up
Returns the position with the updated keys
""" """
if "latitudeI" in position: if "latitudeI" in position:
position["latitude"] = position["latitudeI"] * 1e-7 position["latitude"] = position["latitudeI"] * 1e-7
if "longitudeI" in position: if "longitudeI" in position:
position["longitude"] = position["longitudeI"] * 1e-7 position["longitude"] = position["longitudeI"] * 1e-7
return position
def _nodeNumToId(self, num): def _nodeNumToId(self, num):
"""Map a node node number to a node ID """Map a node node number to a node ID

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: mqtt.proto # source: mqtt.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message from google.protobuf import message as _message
from google.protobuf import reflection as _reflection from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database from google.protobuf import symbol_database as _symbol_database
@@ -14,66 +15,11 @@ _sym_db = _symbol_database.Default()
from . import mesh_pb2 as mesh__pb2 from . import mesh_pb2 as mesh__pb2
DESCRIPTOR = _descriptor.FileDescriptor( DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nmqtt.proto\x1a\nmesh.proto\"V\n\x0fServiceEnvelope\x12\x1b\n\x06packet\x18\x01 \x01(\x0b\x32\x0b.MeshPacket\x12\x12\n\nchannel_id\x18\x02 \x01(\t\x12\x12\n\ngateway_id\x18\x03 \x01(\tBF\n\x13\x63om.geeksville.meshB\nMQTTProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3')
name='mqtt.proto',
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\nMQTTProtosH\003Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\nmqtt.proto\x1a\nmesh.proto\"V\n\x0fServiceEnvelope\x12\x1b\n\x06packet\x18\x01 \x01(\x0b\x32\x0b.MeshPacket\x12\x12\n\nchannel_id\x18\x02 \x01(\t\x12\x12\n\ngateway_id\x18\x03 \x01(\tBF\n\x13\x63om.geeksville.meshB\nMQTTProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
,
dependencies=[mesh__pb2.DESCRIPTOR,])
_SERVICEENVELOPE = DESCRIPTOR.message_types_by_name['ServiceEnvelope']
_SERVICEENVELOPE = _descriptor.Descriptor(
name='ServiceEnvelope',
full_name='ServiceEnvelope',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='packet', full_name='ServiceEnvelope.packet', index=0,
number=1, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='channel_id', full_name='ServiceEnvelope.channel_id', index=1,
number=2, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='gateway_id', full_name='ServiceEnvelope.gateway_id', index=2,
number=3, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=26,
serialized_end=112,
)
_SERVICEENVELOPE.fields_by_name['packet'].message_type = mesh__pb2._MESHPACKET
DESCRIPTOR.message_types_by_name['ServiceEnvelope'] = _SERVICEENVELOPE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
ServiceEnvelope = _reflection.GeneratedProtocolMessageType('ServiceEnvelope', (_message.Message,), { ServiceEnvelope = _reflection.GeneratedProtocolMessageType('ServiceEnvelope', (_message.Message,), {
'DESCRIPTOR' : _SERVICEENVELOPE, 'DESCRIPTOR' : _SERVICEENVELOPE,
'__module__' : 'mqtt_pb2' '__module__' : 'mqtt_pb2'
@@ -81,6 +27,10 @@ ServiceEnvelope = _reflection.GeneratedProtocolMessageType('ServiceEnvelope', (_
}) })
_sym_db.RegisterMessage(ServiceEnvelope) _sym_db.RegisterMessage(ServiceEnvelope)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nMQTTProtosH\003Z!github.com/meshtastic/gomeshproto'
_SERVICEENVELOPE._serialized_start=26
_SERVICEENVELOPE._serialized_end=112
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -4,10 +4,8 @@
import logging import logging
import base64 import base64
from google.protobuf.json_format import MessageToJson from google.protobuf.json_format import MessageToJson
from . import portnums_pb2, apponly_pb2, admin_pb2, channel_pb2 from meshtastic import portnums_pb2, apponly_pb2, admin_pb2, channel_pb2
from .util import pskToString, stripnl, Timeout, our_exit, fromPSK from meshtastic.util import pskToString, stripnl, Timeout, our_exit, fromPSK
class Node: class Node:
@@ -257,6 +255,7 @@ class Node:
p = admin_pb2.AdminMessage() p = admin_pb2.AdminMessage()
p.get_radio_request = True p.get_radio_request = True
# TODO: should we check that localNode has an 'admin' channel?
# Show progress message for super slow operations # Show progress message for super slow operations
if self != self.iface.localNode: if self != self.iface.localNode:
print("Requesting preferences from remote node.") print("Requesting preferences from remote node.")
@@ -286,6 +285,14 @@ class Node:
return self._sendAdmin(p) return self._sendAdmin(p)
def shutdown(self, secs: int = 10):
"""Tell the node to shutdown."""
p = admin_pb2.AdminMessage()
p.shutdown_seconds = secs
logging.info(f"Telling node to shutdown in {secs} seconds")
return self._sendAdmin(p)
def _fixupChannels(self): def _fixupChannels(self):
"""Fixup indexes and add disabled channels as needed""" """Fixup indexes and add disabled channels as needed"""

View File

@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: portnums.proto # source: portnums.proto
"""Generated protocol buffer code."""
from google.protobuf.internal import enum_type_wrapper from google.protobuf.internal import enum_type_wrapper
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message from google.protobuf import message as _message
from google.protobuf import reflection as _reflection from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database from google.protobuf import symbol_database as _symbol_database
@@ -14,96 +15,9 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor( DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0eportnums.proto*\xcb\x02\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\r\n\tREPLY_APP\x10 \x12\x11\n\rIP_TUNNEL_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!\n\x1d\x45NVIRONMENTAL_MEASUREMENT_APP\x10\x43\x12\x0b\n\x07ZPS_APP\x10\x44\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\x44\n\x13\x63om.geeksville.meshB\x08PortnumsH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3')
name='portnums.proto',
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\010PortnumsH\003Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\x0eportnums.proto*\xcb\x02\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\r\n\tREPLY_APP\x10 \x12\x11\n\rIP_TUNNEL_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!\n\x1d\x45NVIRONMENTAL_MEASUREMENT_APP\x10\x43\x12\x0b\n\x07ZPS_APP\x10\x44\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\x44\n\x13\x63om.geeksville.meshB\x08PortnumsH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
)
_PORTNUM = _descriptor.EnumDescriptor(
name='PortNum',
full_name='PortNum',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='UNKNOWN_APP', index=0, number=0,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='TEXT_MESSAGE_APP', index=1, number=1,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='REMOTE_HARDWARE_APP', index=2, number=2,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='POSITION_APP', index=3, number=3,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='NODEINFO_APP', index=4, number=4,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ROUTING_APP', index=5, number=5,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ADMIN_APP', index=6, number=6,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='REPLY_APP', index=7, number=32,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='IP_TUNNEL_APP', index=8, number=33,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='SERIAL_APP', index=9, number=64,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='STORE_FORWARD_APP', index=10, number=65,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='RANGE_TEST_APP', index=11, number=66,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ENVIRONMENTAL_MEASUREMENT_APP', index=12, number=67,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ZPS_APP', index=13, number=68,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='PRIVATE_APP', index=14, number=256,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ATAK_FORWARDER', index=15, number=257,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='MAX', index=16, number=511,
serialized_options=None,
type=None),
],
containing_type=None,
serialized_options=None,
serialized_start=19,
serialized_end=350,
)
_sym_db.RegisterEnumDescriptor(_PORTNUM)
_PORTNUM = DESCRIPTOR.enum_types_by_name['PortNum']
PortNum = enum_type_wrapper.EnumTypeWrapper(_PORTNUM) PortNum = enum_type_wrapper.EnumTypeWrapper(_PORTNUM)
UNKNOWN_APP = 0 UNKNOWN_APP = 0
TEXT_MESSAGE_APP = 1 TEXT_MESSAGE_APP = 1
@@ -124,9 +38,10 @@ ATAK_FORWARDER = 257
MAX = 511 MAX = 511
DESCRIPTOR.enum_types_by_name['PortNum'] = _PORTNUM if _descriptor._USE_C_DESCRIPTORS == False:
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
DESCRIPTOR._options = None
DESCRIPTOR._options = None DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\010PortnumsH\003Z!github.com/meshtastic/gomeshproto'
_PORTNUM._serialized_start=19
_PORTNUM._serialized_end=350
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

File diff suppressed because one or more lines are too long

View File

@@ -2,16 +2,25 @@
""" """
import logging import logging
from pubsub import pub from pubsub import pub
from . import portnums_pb2, remote_hardware_pb2 from meshtastic import portnums_pb2, remote_hardware_pb2
from .util import our_exit from meshtastic.util import our_exit
def onGPIOreceive(packet, interface): def onGPIOreceive(packet, interface):
"""Callback for received GPIO responses """Callback for received GPIO responses
""" """
logging.debug(f"packet:{packet} interface:{interface}") logging.debug(f"packet:{packet} interface:{interface}")
gpioValue = 0
hw = packet["decoded"]["remotehw"] hw = packet["decoded"]["remotehw"]
gpioValue = hw["gpioValue"] if "gpioValue" in hw:
gpioValue = hw["gpioValue"]
else:
if not "gpioMask" in hw:
# we did get a reply, but due to protobufs, 0 for numeric value is not sent
# see https://developers.google.com/protocol-buffers/docs/proto3#default
# so, we set it here
gpioValue = 0
#print(f'mask:{interface.mask}') #print(f'mask:{interface.mask}')
value = int(gpioValue) & int(interface.mask) value = int(gpioValue) & int(interface.mask)
print(f'Received RemoteHardware typ={hw["typ"]}, gpio_value={gpioValue} value={value}') print(f'Received RemoteHardware typ={hw["typ"]}, gpio_value={gpioValue} value={value}')

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: remote_hardware.proto # source: remote_hardware.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message from google.protobuf import message as _message
from google.protobuf import reflection as _reflection from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database from google.protobuf import symbol_database as _symbol_database
@@ -13,105 +14,12 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor( DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15remote_hardware.proto\"\xca\x01\n\x0fHardwareMessage\x12\"\n\x03typ\x18\x01 \x01(\x0e\x32\x15.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\x42J\n\x13\x63om.geeksville.meshB\x0eRemoteHardwareH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3')
name='remote_hardware.proto',
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\016RemoteHardwareH\003Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\x15remote_hardware.proto\"\xca\x01\n\x0fHardwareMessage\x12\"\n\x03typ\x18\x01 \x01(\x0e\x32\x15.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\x42J\n\x13\x63om.geeksville.meshB\x0eRemoteHardwareH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
)
_HARDWAREMESSAGE_TYPE = _descriptor.EnumDescriptor( _HARDWAREMESSAGE = DESCRIPTOR.message_types_by_name['HardwareMessage']
name='Type', _HARDWAREMESSAGE_TYPE = _HARDWAREMESSAGE.enum_types_by_name['Type']
full_name='HardwareMessage.Type',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='UNSET', index=0, number=0,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='WRITE_GPIOS', index=1, number=1,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='WATCH_GPIOS', index=2, number=2,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='GPIOS_CHANGED', index=3, number=3,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='READ_GPIOS', index=4, number=4,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='READ_GPIOS_REPLY', index=5, number=5,
serialized_options=None,
type=None),
],
containing_type=None,
serialized_options=None,
serialized_start=120,
serialized_end=228,
)
_sym_db.RegisterEnumDescriptor(_HARDWAREMESSAGE_TYPE)
_HARDWAREMESSAGE = _descriptor.Descriptor(
name='HardwareMessage',
full_name='HardwareMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='typ', full_name='HardwareMessage.typ', index=0,
number=1, type=14, cpp_type=8, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='gpio_mask', full_name='HardwareMessage.gpio_mask', index=1,
number=2, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='gpio_value', full_name='HardwareMessage.gpio_value', index=2,
number=3, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
_HARDWAREMESSAGE_TYPE,
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=26,
serialized_end=228,
)
_HARDWAREMESSAGE.fields_by_name['typ'].enum_type = _HARDWAREMESSAGE_TYPE
_HARDWAREMESSAGE_TYPE.containing_type = _HARDWAREMESSAGE
DESCRIPTOR.message_types_by_name['HardwareMessage'] = _HARDWAREMESSAGE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
HardwareMessage = _reflection.GeneratedProtocolMessageType('HardwareMessage', (_message.Message,), { HardwareMessage = _reflection.GeneratedProtocolMessageType('HardwareMessage', (_message.Message,), {
'DESCRIPTOR' : _HARDWAREMESSAGE, 'DESCRIPTOR' : _HARDWAREMESSAGE,
'__module__' : 'remote_hardware_pb2' '__module__' : 'remote_hardware_pb2'
@@ -119,6 +27,12 @@ HardwareMessage = _reflection.GeneratedProtocolMessageType('HardwareMessage', (_
}) })
_sym_db.RegisterMessage(HardwareMessage) _sym_db.RegisterMessage(HardwareMessage)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\016RemoteHardwareH\003Z!github.com/meshtastic/gomeshproto'
_HARDWAREMESSAGE._serialized_start=26
_HARDWAREMESSAGE._serialized_end=228
_HARDWAREMESSAGE_TYPE._serialized_start=120
_HARDWAREMESSAGE_TYPE._serialized_end=228
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -1,13 +1,15 @@
""" Serial interface class """ Serial interface class
""" """
import logging import logging
import time
import platform import platform
import os
import stat
import serial import serial
import meshtastic.util import meshtastic.util
from .stream_interface import StreamInterface from meshtastic.stream_interface import StreamInterface
if platform.system() != 'Windows':
import termios
class SerialInterface(StreamInterface): class SerialInterface(StreamInterface):
"""Interface class for meshtastic devices over a serial link""" """Interface class for meshtastic devices over a serial link"""
@@ -20,6 +22,7 @@ class SerialInterface(StreamInterface):
devPath {string} -- A filepath to a device, i.e. /dev/ttyUSB0 (default: {None}) 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}) debugOut {stream} -- If a stream is provided, any debug serial output from the device will be emitted to that stream. (default: {None})
""" """
self.noProto = noProto
if devPath is None: if devPath is None:
ports = meshtastic.util.findPorts() ports = meshtastic.util.findPorts()
@@ -35,43 +38,27 @@ class SerialInterface(StreamInterface):
logging.debug(f"Connecting to {devPath}") logging.debug(f"Connecting to {devPath}")
# Note: we provide None for port here, because we will be opening it later # first we need to set the HUPCL so the device will not reboot based on RTS and/or DTR
self.stream = serial.Serial( # see https://github.com/pyserial/pyserial/issues/124
None, 921600, exclusive=True, timeout=0.5, write_timeout=0) if platform.system() != 'Windows':
with open(devPath, encoding='utf8') as f:
attrs = termios.tcgetattr(f)
attrs[2] = attrs[2] & ~termios.HUPCL
termios.tcsetattr(f, termios.TCSAFLUSH, attrs)
f.close()
time.sleep(0.1)
# rts=False Needed to prevent TBEAMs resetting on OSX, because rts is connected to reset self.stream = serial.Serial(devPath, 921600, exclusive=True, timeout=0.5, write_timeout=0)
self.stream.port = devPath self.stream.flush()
time.sleep(0.1)
# HACK: If the platform driving the serial port is unable to leave the RTS pin in high-impedance StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto, connectNow=connectNow)
# mode, set RTS to false so that the device platform won't be reset spuriously.
# Linux does this properly, so don't apply this hack on Linux (because it makes the reset button not work).
if self._hostPlatformAlwaysDrivesUartRts():
self.stream.rts = False
self.stream.open()
StreamInterface.__init__( def close(self):
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow) """Close a connection to the device"""
self.stream.flush()
"""true if platform driving the serial port is Windows Subsystem for Linux 1.""" time.sleep(0.1)
def _isWsl1(self): self.stream.flush()
# WSL1 identifies itself as Linux, but has a special char device at /dev/lxss for use with session control, time.sleep(0.1)
# e.g. /init. We should treat WSL1 as Windows for the RTS-driving hack because the underlying platfrom logging.debug("Closing Serial stream")
# serial driver for the CP21xx still exhibits the buggy behavior. StreamInterface.close(self)
# WSL2 is not covered here, as it does not (as of 2021-May-25) support the appropriate functionality to
# share or pass-through serial ports.
try:
# Claims to be Linux, but has /dev/lxss; must be WSL 1
return platform.system() == 'Linux' and stat.S_ISCHR(os.stat('/dev/lxss').st_mode)
except:
# Couldn't stat /dev/lxss special device; not WSL1
return False
def _hostPlatformAlwaysDrivesUartRts(self):
# OS-X/Windows seems to have a bug in its CP21xx serial drivers. It ignores that we asked for no RTSCTS
# control and will always drive RTS either high or low (rather than letting the CP102 leave
# it as an open-collector floating pin).
# TODO: When WSL2 supports USB passthrough, this will get messier. If/when WSL2 gets virtual serial
# ports that "share" the Windows serial port (and thus the Windows drivers), this code will need to be
# updated to reflect that as well -- or if T-Beams get made with an alternate USB to UART bridge that has
# a less buggy driver.
return platform.system() != 'Linux' or self._isWsl1()

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: storeforward.proto # source: storeforward.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message from google.protobuf import message as _message
from google.protobuf import reflection as _reflection from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database from google.protobuf import symbol_database as _symbol_database
@@ -13,252 +14,15 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor( DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12storeforward.proto\"\x8a\x06\n\x0fStoreAndForward\x12,\n\x02rr\x18\x01 \x01(\x0e\x32 .StoreAndForward.RequestResponse\x12*\n\x05stats\x18\x02 \x01(\x0b\x32\x1b.StoreAndForward.Statistics\x12)\n\x07history\x18\x03 \x01(\x0b\x32\x18.StoreAndForward.History\x12-\n\theartbeat\x18\x04 \x01(\x0b\x32\x1a.StoreAndForward.Heartbeat\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\"\xf7\x01\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\x0c\x43LIENT_ERROR\x10\x65\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x66\x12\x10\n\x0c\x43LIENT_STATS\x10g\x12\x0f\n\x0b\x43LIENT_PING\x10h\x12\x0f\n\x0b\x43LIENT_PONG\x10i\x12\x10\n\x0c\x43LIENT_ABORT\x10jBQ\n\x13\x63om.geeksville.meshB\x15StoreAndForwardProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3')
name='storeforward.proto',
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\025StoreAndForwardProtosH\003Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\x12storeforward.proto\"\xe7\x04\n\x0fStoreAndForward\x12,\n\x02rr\x18\x01 \x01(\x0e\x32 .StoreAndForward.RequestResponse\x12*\n\x05stats\x18\x02 \x01(\x0b\x32\x1b.StoreAndForward.Statistics\x12)\n\x07history\x18\x03 \x01(\x0b\x32\x18.StoreAndForward.History\x1a\xc6\x01\n\nStatistics\x12\x15\n\rMessagesTotal\x18\x01 \x01(\r\x12\x15\n\rMessagesSaved\x18\x02 \x01(\r\x12\x13\n\x0bMessagesMax\x18\x03 \x01(\r\x12\x0e\n\x06UpTime\x18\x04 \x01(\r\x12\x10\n\x08Requests\x18\x05 \x01(\r\x12\x17\n\x0fRequestsHistory\x18\x06 \x01(\r\x12\x11\n\tHeartbeat\x18\x07 \x01(\x08\x12\x11\n\tReturnMax\x18\x08 \x01(\r\x12\x14\n\x0cReturnWindow\x18\t \x01(\r\x1a\x32\n\x07History\x12\x17\n\x0fHistoryMessages\x18\x01 \x01(\r\x12\x0e\n\x06Window\x18\x02 \x01(\r\"\xd1\x01\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\x10\n\x0c\x43LIENT_ERROR\x10\x65\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x66\x12\x10\n\x0c\x43LIENT_STATS\x10g\x12\x0f\n\x0b\x43LIENT_PING\x10h\x12\x0f\n\x0b\x43LIENT_PONG\x10iBQ\n\x13\x63om.geeksville.meshB\x15StoreAndForwardProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
)
_STOREANDFORWARD_REQUESTRESPONSE = _descriptor.EnumDescriptor( _STOREANDFORWARD = DESCRIPTOR.message_types_by_name['StoreAndForward']
name='RequestResponse', _STOREANDFORWARD_STATISTICS = _STOREANDFORWARD.nested_types_by_name['Statistics']
full_name='StoreAndForward.RequestResponse', _STOREANDFORWARD_HISTORY = _STOREANDFORWARD.nested_types_by_name['History']
filename=None, _STOREANDFORWARD_HEARTBEAT = _STOREANDFORWARD.nested_types_by_name['Heartbeat']
file=DESCRIPTOR, _STOREANDFORWARD_REQUESTRESPONSE = _STOREANDFORWARD.enum_types_by_name['RequestResponse']
values=[
_descriptor.EnumValueDescriptor(
name='UNSET', index=0, number=0,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ROUTER_ERROR', index=1, number=1,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ROUTER_HEARTBEAT', index=2, number=2,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ROUTER_PING', index=3, number=3,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ROUTER_PONG', index=4, number=4,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ROUTER_BUSY', index=5, number=5,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='CLIENT_ERROR', index=6, number=101,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='CLIENT_HISTORY', index=7, number=102,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='CLIENT_STATS', index=8, number=103,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='CLIENT_PING', index=9, number=104,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='CLIENT_PONG', index=10, number=105,
serialized_options=None,
type=None),
],
containing_type=None,
serialized_options=None,
serialized_start=429,
serialized_end=638,
)
_sym_db.RegisterEnumDescriptor(_STOREANDFORWARD_REQUESTRESPONSE)
_STOREANDFORWARD_STATISTICS = _descriptor.Descriptor(
name='Statistics',
full_name='StoreAndForward.Statistics',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='MessagesTotal', full_name='StoreAndForward.Statistics.MessagesTotal', index=0,
number=1, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='MessagesSaved', full_name='StoreAndForward.Statistics.MessagesSaved', index=1,
number=2, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='MessagesMax', full_name='StoreAndForward.Statistics.MessagesMax', index=2,
number=3, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='UpTime', full_name='StoreAndForward.Statistics.UpTime', index=3,
number=4, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='Requests', full_name='StoreAndForward.Statistics.Requests', index=4,
number=5, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='RequestsHistory', full_name='StoreAndForward.Statistics.RequestsHistory', index=5,
number=6, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='Heartbeat', full_name='StoreAndForward.Statistics.Heartbeat', index=6,
number=7, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='ReturnMax', full_name='StoreAndForward.Statistics.ReturnMax', index=7,
number=8, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='ReturnWindow', full_name='StoreAndForward.Statistics.ReturnWindow', index=8,
number=9, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=176,
serialized_end=374,
)
_STOREANDFORWARD_HISTORY = _descriptor.Descriptor(
name='History',
full_name='StoreAndForward.History',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='HistoryMessages', full_name='StoreAndForward.History.HistoryMessages', index=0,
number=1, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='Window', full_name='StoreAndForward.History.Window', index=1,
number=2, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=376,
serialized_end=426,
)
_STOREANDFORWARD = _descriptor.Descriptor(
name='StoreAndForward',
full_name='StoreAndForward',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='rr', full_name='StoreAndForward.rr', index=0,
number=1, type=14, cpp_type=8, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='stats', full_name='StoreAndForward.stats', index=1,
number=2, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='history', full_name='StoreAndForward.history', index=2,
number=3, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[_STOREANDFORWARD_STATISTICS, _STOREANDFORWARD_HISTORY, ],
enum_types=[
_STOREANDFORWARD_REQUESTRESPONSE,
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=23,
serialized_end=638,
)
_STOREANDFORWARD_STATISTICS.containing_type = _STOREANDFORWARD
_STOREANDFORWARD_HISTORY.containing_type = _STOREANDFORWARD
_STOREANDFORWARD.fields_by_name['rr'].enum_type = _STOREANDFORWARD_REQUESTRESPONSE
_STOREANDFORWARD.fields_by_name['stats'].message_type = _STOREANDFORWARD_STATISTICS
_STOREANDFORWARD.fields_by_name['history'].message_type = _STOREANDFORWARD_HISTORY
_STOREANDFORWARD_REQUESTRESPONSE.containing_type = _STOREANDFORWARD
DESCRIPTOR.message_types_by_name['StoreAndForward'] = _STOREANDFORWARD
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
StoreAndForward = _reflection.GeneratedProtocolMessageType('StoreAndForward', (_message.Message,), { StoreAndForward = _reflection.GeneratedProtocolMessageType('StoreAndForward', (_message.Message,), {
'Statistics' : _reflection.GeneratedProtocolMessageType('Statistics', (_message.Message,), { 'Statistics' : _reflection.GeneratedProtocolMessageType('Statistics', (_message.Message,), {
@@ -274,6 +38,13 @@ StoreAndForward = _reflection.GeneratedProtocolMessageType('StoreAndForward', (_
# @@protoc_insertion_point(class_scope:StoreAndForward.History) # @@protoc_insertion_point(class_scope:StoreAndForward.History)
}) })
, ,
'Heartbeat' : _reflection.GeneratedProtocolMessageType('Heartbeat', (_message.Message,), {
'DESCRIPTOR' : _STOREANDFORWARD_HEARTBEAT,
'__module__' : 'storeforward_pb2'
# @@protoc_insertion_point(class_scope:StoreAndForward.Heartbeat)
})
,
'DESCRIPTOR' : _STOREANDFORWARD, 'DESCRIPTOR' : _STOREANDFORWARD,
'__module__' : 'storeforward_pb2' '__module__' : 'storeforward_pb2'
# @@protoc_insertion_point(class_scope:StoreAndForward) # @@protoc_insertion_point(class_scope:StoreAndForward)
@@ -281,7 +52,20 @@ StoreAndForward = _reflection.GeneratedProtocolMessageType('StoreAndForward', (_
_sym_db.RegisterMessage(StoreAndForward) _sym_db.RegisterMessage(StoreAndForward)
_sym_db.RegisterMessage(StoreAndForward.Statistics) _sym_db.RegisterMessage(StoreAndForward.Statistics)
_sym_db.RegisterMessage(StoreAndForward.History) _sym_db.RegisterMessage(StoreAndForward.History)
_sym_db.RegisterMessage(StoreAndForward.Heartbeat)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\025StoreAndForwardProtosH\003Z!github.com/meshtastic/gomeshproto'
_STOREANDFORWARD._serialized_start=23
_STOREANDFORWARD._serialized_end=801
_STOREANDFORWARD_STATISTICS._serialized_start=223
_STOREANDFORWARD_STATISTICS._serialized_end=428
_STOREANDFORWARD_HISTORY._serialized_start=430
_STOREANDFORWARD_HISTORY._serialized_end=503
_STOREANDFORWARD_HEARTBEAT._serialized_start=505
_STOREANDFORWARD_HEARTBEAT._serialized_end=551
_STOREANDFORWARD_REQUESTRESPONSE._serialized_start=554
_STOREANDFORWARD_REQUESTRESPONSE._serialized_end=801
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -7,8 +7,8 @@ import traceback
import serial import serial
from .mesh_interface import MeshInterface from meshtastic.mesh_interface import MeshInterface
from .util import stripnl from meshtastic.util import stripnl
START1 = 0x94 START1 = 0x94
@@ -88,6 +88,8 @@ class StreamInterface(MeshInterface):
if self.stream: # ignore writes when stream is closed if self.stream: # ignore writes when stream is closed
self.stream.write(b) self.stream.write(b)
self.stream.flush() self.stream.flush()
# we sleep here to give the TBeam a chance to work
time.sleep(0.1)
def _readBytes(self, length): def _readBytes(self, length):
"""Read an array of bytes from our stream""" """Read an array of bytes from our stream"""

187
meshtastic/supported_device.py Executable file
View File

@@ -0,0 +1,187 @@
""" 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.
"""
import platform
import subprocess
import re
# 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
class SupportedDevice():
"""Devices supported on Meshtastic"""
def __init__(self, name, version=None, for_firmware=None, device_class="esp32",
baseport_on_linux=None, baseport_on_mac=None, baseport_on_windows="COM",
usb_vendor_id_in_hex=None, usb_product_id_in_hex=None):
""" constructor """
self.name = name
self.version = version
self.for_firmware = for_firmware
self.device_class = device_class # could be "nrf52"
# when you run "lsusb -d xxxx:" in linux
self.usb_vendor_id_in_hex = usb_vendor_id_in_hex # store in lower case
self.usb_product_id_in_hex = usb_product_id_in_hex # store in lower case
self.baseport_on_linux = baseport_on_linux # ex: ttyUSB or ttyACM
self.baseport_on_mac = baseport_on_mac
self.baseport_on_windows = baseport_on_windows
# supported devices
tbeam_v0_7 = SupportedDevice(name="T-Beam", version="0.7", for_firmware="tbeam0.7",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4")
tbeam_v1_1 = SupportedDevice(name="T-Beam", version="1.1", for_firmware="tbeam",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4")
tbeam_M8N = SupportedDevice(name="T-Beam", version="M8N", for_firmware="tbeam",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4")
tbeam_M8N_SX1262 = SupportedDevice(name="T-Beam", version="M8N_SX1262", for_firmware="tbeam",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4")
tlora_v1_1 = SupportedDevice(name="T-Lora", version="1.1", for_firmware="tlora-v1",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4")
tlora_v1_3 = SupportedDevice(name="T-Lora", version="1.3", for_firmware="tlora-v1-3",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4")
tlora_v2_0 = SupportedDevice(name="T-Lora", version="2.0", for_firmware="tlora-v2-1",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4")
tlora_v2_1 = SupportedDevice(name="T-Lora", version="2.1", for_firmware="tlora-v2-1",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4")
tlora_v2_1_1_6 = SupportedDevice(name="T-Lora", version="2.1-1.6", for_firmware="tlora-v2-1-1.6",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4")
heltec_v1 = SupportedDevice(name="Heltec", version="1", for_firmware="heltec-v1",
baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial-",
usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60")
heltec_v2_0 = SupportedDevice(name="Heltec", version="2.0", for_firmware="heltec-v2.0",
baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial-",
usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60")
heltec_v2_1 = SupportedDevice(name="Heltec", version="2.1", for_firmware="heltec-v2.1",
baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial-",
usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60")
# TODO: get info on diy
meshtastic_diy_v1 = SupportedDevice(name="Meshtastic DIY", version="1", for_firmware="meshtastic-diy-v1")
# TODO: get info on TEcho
techo_1 = SupportedDevice(name="T-Echo", version="1", for_firmware="t-echo-1", device_class="nrf52")
rak4631_5005 = SupportedDevice(name="RAK 4631 5005", version="", for_firmware="rak4631_5005",
device_class="nrf52",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="239a", usb_product_id_in_hex="0029")
rak4631_19003 = SupportedDevice(name="RAK 4631 19003", version="", for_firmware="rak4631_19003",
device_class="nrf52",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="239a", usb_product_id_in_hex="8029")
supported_devices = [tbeam_v0_7, tbeam_v1_1, tbeam_M8N, tbeam_M8N_SX1262,
tlora_v1_1, tlora_v1_3, tlora_v2_0, tlora_v2_1, tlora_v2_1_1_6,
heltec_v1, heltec_v2_0, heltec_v2_1,
meshtastic_diy_v1, techo_1, rak4631_5005, rak4631_19003]
def get_unique_vendor_ids():
"""Return a set of unique vendor ids"""
vids = set()
for d in supported_devices:
if d.usb_vendor_id_in_hex:
vids.add(d.usb_vendor_id_in_hex)
return vids
def get_devices_with_vendor_id(vid):
"""Return a set of unique devices with the vendor id"""
sd = set()
for d in supported_devices:
if d.usb_vendor_id_in_hex == vid:
sd.add(d)
return sd
def active_ports_on_supported_devices(sds):
"""Return a set of active ports based on the supplied supported devices"""
ports = set()
baseports = set()
system = platform.system()
# figure out what possible base ports there are
for d in sds:
if system == "Linux":
baseports.add(d.baseport_on_linux)
elif system == "Darwin":
baseports.add(d.baseport_on_mac)
elif system == "Windows":
baseports.add(d.baseport_on_windows)
for bp in baseports:
if system == "Linux":
# see if we have any devices (ignoring any stderr output)
command = f'ls -al /dev/{bp}* 2> /dev/null'
#print(f'command:{command}')
_, ls_output = subprocess.getstatusoutput(command)
#print(f'ls_output:{ls_output}')
# if we got output, there are ports
if len(ls_output) > 0:
#print('got output')
# for each line of output
lines = ls_output.split('\n')
#print(f'lines:{lines}')
for line in lines:
parts = line.split(' ')
#print(f'parts:{parts}')
port = parts[-1]
#print(f'port:{port}')
ports.add(port)
elif system == "Darwin":
# see if we have any devices (ignoring any stderr output)
command = f'ls -al /dev/{bp}* 2> /dev/null'
#print(f'command:{command}')
_, ls_output = subprocess.getstatusoutput(command)
#print(f'ls_output:{ls_output}')
# if we got output, there are ports
if len(ls_output) > 0:
#print('got output')
# for each line of output
lines = ls_output.split('\n')
#print(f'lines:{lines}')
for line in lines:
parts = line.split(' ')
#print(f'parts:{parts}')
port = parts[-1]
#print(f'port:{port}')
ports.add(port)
elif system == "Windows":
# for each device in supported devices found
for d in sds:
# find the port(s)
com_ports = detect_windows_port(d)
#print(f'com_ports:{com_ports}')
# add all ports
for com_port in com_ports:
ports.add(com_port)
return ports
def detect_windows_port(sd):
"""detect if Windows port"""
ports = set()
if sd:
system = platform.system()
if system == "Windows":
command = 'powershell.exe "Get-PnpDevice -PresentOnly | Where-Object{ ($_.DeviceId -like '
command += f"'*{sd.usb_vendor_id_in_hex.upper()}*'"
command += ')} | Format-List"'
#print(f'command:{command}')
_, sp_output = subprocess.getstatusoutput(command)
#print(f'sp_output:{sp_output}')
p = re.compile(r'\(COM(.*)\)')
for x in p.findall(sp_output):
#print(f'x:{x}')
ports.add(f'COM{x}')
return ports

View File

@@ -4,7 +4,7 @@ import logging
import socket import socket
from typing import AnyStr from typing import AnyStr
from .stream_interface import StreamInterface from meshtastic.stream_interface import StreamInterface
class TCPInterface(StreamInterface): class TCPInterface(StreamInterface):
"""Interface class for meshtastic devices over a TCP link""" """Interface class for meshtastic devices over a TCP link"""
@@ -17,8 +17,6 @@ class TCPInterface(StreamInterface):
hostname {string} -- Hostname/IP address of the device to connect to hostname {string} -- Hostname/IP address of the device to connect to
""" """
# Instead of wrapping as a stream, we use the native socket API
# self.stream = sock.makefile('rw')
self.stream = None self.stream = None
self.hostname = hostname self.hostname = hostname
@@ -35,6 +33,12 @@ class TCPInterface(StreamInterface):
StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto, StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto,
connectNow=connectNow) connectNow=connectNow)
def _socket_shutdown(self):
"""Shutdown the socket.
Note: Broke out this line so the exception could be unit tested.
"""
self.socket.shutdown(socket.SHUT_RDWR)
def myConnect(self): def myConnect(self):
"""Connect to socket""" """Connect to socket"""
server_address = (self.hostname, self.portNumber) server_address = (self.hostname, self.portNumber)
@@ -50,7 +54,7 @@ class TCPInterface(StreamInterface):
self._wantExit = True self._wantExit = True
if not self.socket is None: if not self.socket is None:
try: try:
self.socket.shutdown(socket.SHUT_RDWR) self._socket_shutdown()
except: except:
pass # Ignore errors in shutdown, because we might have a race with the server pass # Ignore errors in shutdown, because we might have a race with the server
self.socket.close() self.socket.close()

View File

@@ -8,9 +8,9 @@ import traceback
from dotmap import DotMap from dotmap import DotMap
from pubsub import pub from pubsub import pub
import meshtastic.util import meshtastic.util
from .__init__ import BROADCAST_NUM from meshtastic.__init__ import BROADCAST_NUM
from .serial_interface import SerialInterface from meshtastic.serial_interface import SerialInterface
from .tcp_interface import TCPInterface from meshtastic.tcp_interface import TCPInterface
"""The interfaces we are using for our tests""" """The interfaces we are using for our tests"""
@@ -63,6 +63,7 @@ def testSend(fromInterface, toInterface, isBroadcast=False, asBinary=False, want
Returns: Returns:
boolean -- True for success boolean -- True for success
""" """
# pylint: disable=W0603
global receivedPackets global receivedPackets
receivedPackets = [] receivedPackets = []
fromNode = fromInterface.myInfo.my_node_num fromNode = fromInterface.myInfo.my_node_num
@@ -74,6 +75,7 @@ def testSend(fromInterface, toInterface, isBroadcast=False, asBinary=False, want
logging.debug( logging.debug(
f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}") f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}")
# pylint: disable=W0603
global sendingInterface global sendingInterface
sendingInterface = fromInterface sendingInterface = fromInterface
if not asBinary: if not asBinary:
@@ -94,6 +96,7 @@ def runTests(numTests=50, wantAck=False, maxFailures=0):
numFail = 0 numFail = 0
numSuccess = 0 numSuccess = 0
for _ in range(numTests): for _ in range(numTests):
# pylint: disable=W0603
global testNumber global testNumber
testNumber = testNumber + 1 testNumber = testNumber + 1
isBroadcast = True isBroadcast = True
@@ -152,6 +155,7 @@ def testAll(numTests=5):
pub.subscribe(onConnection, "meshtastic.connection") pub.subscribe(onConnection, "meshtastic.connection")
pub.subscribe(onReceive, "meshtastic.receive") pub.subscribe(onReceive, "meshtastic.receive")
# pylint: disable=W0603
global interfaces global interfaces
interfaces = list(map(lambda port: SerialInterface( interfaces = list(map(lambda port: SerialInterface(
port, debugOut=openDebugLog(port), connectNow=True), ports)) port, debugOut=openDebugLog(port), connectNow=True), ports))

View File

@@ -1,12 +1,15 @@
"""Meshtastic unit tests for ble_interface.py""" """Meshtastic unit tests for ble_interface.py"""
from unittest.mock import patch
import pytest import pytest
from ..ble_interface import BLEInterface from ..ble_interface import BLEInterface
@pytest.mark.unit @pytest.mark.unit
def test_BLEInterface(): @patch('platform.system', return_value='Linux')
def test_BLEInterface(mock_platform):
"""Test that we can instantiate a BLEInterface""" """Test that we can instantiate a BLEInterface"""
iface = BLEInterface('foo', debugOut=True, noProto=True) iface = BLEInterface('foo', debugOut=True, noProto=True)
iface.close() iface.close()
mock_platform.assert_called()

View File

@@ -0,0 +1,61 @@
"""Meshtastic unit tests for __init__.py"""
import re
import logging
from unittest.mock import MagicMock
import pytest
from meshtastic.__init__ import _onTextReceive, _onPositionReceive, _onNodeInfoReceive
from ..serial_interface import SerialInterface
from ..globals import Globals
@pytest.mark.unit
def test_init_onTextReceive_with_exception(caplog):
"""Test _onTextReceive"""
args = MagicMock()
Globals.getInstance().set_args(args)
iface = MagicMock(autospec=SerialInterface)
packet = {}
with caplog.at_level(logging.DEBUG):
_onTextReceive(iface, packet)
assert re.search(r'in _onTextReceive', caplog.text, re.MULTILINE)
assert re.search(r'Malformatted', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_init_onPositionReceive(caplog):
"""Test _onPositionReceive"""
args = MagicMock()
Globals.getInstance().set_args(args)
iface = MagicMock(autospec=SerialInterface)
packet = {
'from': 'foo',
'decoded': {
'position': {}
}
}
with caplog.at_level(logging.DEBUG):
_onPositionReceive(iface, packet)
assert re.search(r'in _onPositionReceive', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_init_onNodeInfoReceive(caplog, iface_with_nodes):
"""Test _onNodeInfoReceive"""
args = MagicMock()
Globals.getInstance().set_args(args)
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
packet = {
'from': 'foo',
'decoded': {
'user': {
'id': 'bar',
},
}
}
with caplog.at_level(logging.DEBUG):
_onNodeInfoReceive(iface, packet)
assert re.search(r'in _onNodeInfoReceive', caplog.text, re.MULTILINE)

View File

@@ -6,13 +6,21 @@ import pytest
@pytest.mark.int @pytest.mark.int
def test_int_no_args(): def test_int_meshtastic_no_args():
"""Test without any args""" """Test meshtastic without any args"""
return_value, out = subprocess.getstatusoutput('meshtastic') return_value, out = subprocess.getstatusoutput('meshtastic')
assert re.match(r'usage: meshtastic', out) assert re.match(r'usage: meshtastic', out)
assert return_value == 1 assert return_value == 1
@pytest.mark.int
def test_int_mesh_tunnel_no_args():
"""Test mesh-tunnel without any args"""
return_value, out = subprocess.getstatusoutput('mesh-tunnel')
assert re.match(r'usage: mesh-tunnel', out)
assert return_value == 1
@pytest.mark.int @pytest.mark.int
def test_int_version(): def test_int_version():
"""Test '--version'.""" """Test '--version'."""

View File

File diff suppressed because it is too large Load Diff

View File

@@ -10,10 +10,13 @@ from ..mesh_interface import MeshInterface
from ..node import Node from ..node import Node
from .. import mesh_pb2 from .. import mesh_pb2
from ..__init__ import LOCAL_ADDR, BROADCAST_ADDR from ..__init__ import LOCAL_ADDR, BROADCAST_ADDR
from ..radioconfig_pb2 import RadioConfig
from ..util import Timeout
@pytest.mark.unit @pytest.mark.unit
def test_MeshInterface(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_MeshInterface(capsys):
"""Test that we can instantiate a MeshInterface""" """Test that we can instantiate a MeshInterface"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
anode = Node('foo', 'bar') anode = Node('foo', 'bar')
@@ -54,19 +57,19 @@ def test_MeshInterface(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_getMyUser(reset_globals, iface_with_nodes): @pytest.mark.usefixtures("reset_globals")
def test_getMyUser(iface_with_nodes):
"""Test getMyUser()""" """Test getMyUser()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
myuser = iface.getMyUser() myuser = iface.getMyUser()
print(f'myuser:{myuser}')
assert myuser is not None assert myuser is not None
assert myuser["id"] == '!9388f81c' assert myuser["id"] == '!9388f81c'
@pytest.mark.unit @pytest.mark.unit
def test_getLongName(reset_globals, iface_with_nodes): @pytest.mark.usefixtures("reset_globals")
def test_getLongName(iface_with_nodes):
"""Test getLongName()""" """Test getLongName()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
@@ -75,7 +78,8 @@ def test_getLongName(reset_globals, iface_with_nodes):
@pytest.mark.unit @pytest.mark.unit
def test_getShortName(reset_globals, iface_with_nodes): @pytest.mark.usefixtures("reset_globals")
def test_getShortName(iface_with_nodes):
"""Test getShortName().""" """Test getShortName()."""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
@@ -84,7 +88,8 @@ def test_getShortName(reset_globals, iface_with_nodes):
@pytest.mark.unit @pytest.mark.unit
def test_handlePacketFromRadio_no_from(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_handlePacketFromRadio_no_from(capsys):
"""Test _handlePacketFromRadio with no 'from' in the mesh packet.""" """Test _handlePacketFromRadio with no 'from' in the mesh packet."""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
meshPacket = mesh_pb2.MeshPacket() meshPacket = mesh_pb2.MeshPacket()
@@ -95,7 +100,8 @@ def test_handlePacketFromRadio_no_from(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_handlePacketFromRadio_with_a_portnum(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_handlePacketFromRadio_with_a_portnum(caplog):
"""Test _handlePacketFromRadio with a portnum """Test _handlePacketFromRadio with a portnum
Since we have an attribute called 'from', we cannot simply 'set' it. Since we have an attribute called 'from', we cannot simply 'set' it.
Had to implement a hack just to be able to test some code. Had to implement a hack just to be able to test some code.
@@ -110,7 +116,8 @@ def test_handlePacketFromRadio_with_a_portnum(caplog, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_handlePacketFromRadio_no_portnum(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_handlePacketFromRadio_no_portnum(caplog):
"""Test _handlePacketFromRadio without a portnum""" """Test _handlePacketFromRadio without a portnum"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
meshPacket = mesh_pb2.MeshPacket() meshPacket = mesh_pb2.MeshPacket()
@@ -121,7 +128,8 @@ def test_handlePacketFromRadio_no_portnum(caplog, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_getNode_with_local(reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_getNode_with_local():
"""Test getNode""" """Test getNode"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
anode = iface.getNode(LOCAL_ADDR) anode = iface.getNode(LOCAL_ADDR)
@@ -129,7 +137,8 @@ def test_getNode_with_local(reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_getNode_not_local(reset_globals, caplog): @pytest.mark.usefixtures("reset_globals")
def test_getNode_not_local(caplog):
"""Test getNode not local""" """Test getNode not local"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
anode = MagicMock(autospec=Node) anode = MagicMock(autospec=Node)
@@ -141,7 +150,8 @@ def test_getNode_not_local(reset_globals, caplog):
@pytest.mark.unit @pytest.mark.unit
def test_getNode_not_local_timeout(reset_globals, capsys): @pytest.mark.usefixtures("reset_globals")
def test_getNode_not_local_timeout(capsys):
"""Test getNode not local, simulate timeout""" """Test getNode not local, simulate timeout"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
anode = MagicMock(autospec=Node) anode = MagicMock(autospec=Node)
@@ -157,7 +167,8 @@ def test_getNode_not_local_timeout(reset_globals, capsys):
@pytest.mark.unit @pytest.mark.unit
def test_sendPosition(reset_globals, caplog): @pytest.mark.usefixtures("reset_globals")
def test_sendPosition(caplog):
"""Test sendPosition""" """Test sendPosition"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
@@ -167,7 +178,25 @@ def test_sendPosition(reset_globals, caplog):
@pytest.mark.unit @pytest.mark.unit
def test_handleFromRadio_empty_payload(reset_globals, caplog): @pytest.mark.usefixtures("reset_globals")
def test_close_with_heartbeatTimer(caplog):
"""Test close() with heartbeatTimer"""
iface = MeshInterface(noProto=True)
anode = Node('foo', 'bar')
radioConfig = RadioConfig()
radioConfig.preferences.phone_timeout_secs = 10
anode.radioConfig = radioConfig
iface.localNode = anode
assert iface.heartbeatTimer is None
with caplog.at_level(logging.DEBUG):
iface._startHeartbeat()
assert iface.heartbeatTimer is not None
iface.close()
@pytest.mark.unit
@pytest.mark.usefixtures("reset_globals")
def test_handleFromRadio_empty_payload(caplog):
"""Test _handleFromRadio""" """Test _handleFromRadio"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
@@ -177,7 +206,8 @@ def test_handleFromRadio_empty_payload(reset_globals, caplog):
@pytest.mark.unit @pytest.mark.unit
def test_handleFromRadio_with_my_info(reset_globals, caplog): @pytest.mark.usefixtures("reset_globals")
def test_handleFromRadio_with_my_info(caplog):
"""Test _handleFromRadio with my_info""" """Test _handleFromRadio with my_info"""
# Note: I captured the '--debug --info' for the bytes below. # Note: I captured the '--debug --info' for the bytes below.
# It "translates" to this: # It "translates" to this:
@@ -202,7 +232,8 @@ def test_handleFromRadio_with_my_info(reset_globals, caplog):
@pytest.mark.unit @pytest.mark.unit
def test_handleFromRadio_with_node_info(reset_globals, caplog, capsys): @pytest.mark.usefixtures("reset_globals")
def test_handleFromRadio_with_node_info(caplog, capsys):
"""Test _handleFromRadio with node_info""" """Test _handleFromRadio with node_info"""
# Note: I captured the '--debug --info' for the bytes below. # Note: I captured the '--debug --info' for the bytes below.
# It "translates" to this: # It "translates" to this:
@@ -238,7 +269,8 @@ def test_handleFromRadio_with_node_info(reset_globals, caplog, capsys):
@pytest.mark.unit @pytest.mark.unit
def test_handleFromRadio_with_node_info_tbeam1(reset_globals, caplog, capsys): @pytest.mark.usefixtures("reset_globals")
def test_handleFromRadio_with_node_info_tbeam1(caplog, capsys):
"""Test _handleFromRadio with node_info""" """Test _handleFromRadio with node_info"""
# Note: Captured the '--debug --info' for the bytes below. # Note: Captured the '--debug --info' for the bytes below.
# pylint: disable=C0301 # pylint: disable=C0301
@@ -261,7 +293,8 @@ def test_handleFromRadio_with_node_info_tbeam1(reset_globals, caplog, capsys):
@pytest.mark.unit @pytest.mark.unit
def test_handleFromRadio_with_node_info_tbeam_with_bad_data(reset_globals, caplog, capsys): @pytest.mark.usefixtures("reset_globals")
def test_handleFromRadio_with_node_info_tbeam_with_bad_data(caplog):
"""Test _handleFromRadio with node_info with some bad data (issue#172) - ensure we do not throw exception""" """Test _handleFromRadio with node_info with some bad data (issue#172) - ensure we do not throw exception"""
# Note: Captured the '--debug --info' for the bytes below. # Note: Captured the '--debug --info' for the bytes below.
from_radio_bytes = b'"\x17\x08\xdc\x8a\x8a\xae\x02\x12\x08"\x06\x00\x00\x00\x00\x00\x00\x1a\x00=\x00\x00\xb8@' from_radio_bytes = b'"\x17\x08\xdc\x8a\x8a\xae\x02\x12\x08"\x06\x00\x00\x00\x00\x00\x00\x1a\x00=\x00\x00\xb8@'
@@ -272,7 +305,8 @@ def test_handleFromRadio_with_node_info_tbeam_with_bad_data(reset_globals, caplo
@pytest.mark.unit @pytest.mark.unit
def test_MeshInterface_sendToRadioImpl(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_MeshInterface_sendToRadioImpl(caplog):
"""Test _sendToRadioImp()""" """Test _sendToRadioImp()"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
@@ -282,7 +316,8 @@ def test_MeshInterface_sendToRadioImpl(caplog, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_MeshInterface_sendToRadio_no_proto(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_MeshInterface_sendToRadio_no_proto(caplog):
"""Test sendToRadio()""" """Test sendToRadio()"""
iface = MeshInterface() iface = MeshInterface()
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
@@ -292,7 +327,8 @@ def test_MeshInterface_sendToRadio_no_proto(caplog, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_sendData_too_long(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendData_too_long(caplog):
"""Test when data payload is too big""" """Test when data payload is too big"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
some_large_text = b'This is a long text that will be too long for send text.' some_large_text = b'This is a long text that will be too long for send text.'
@@ -316,7 +352,8 @@ def test_sendData_too_long(caplog, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_sendData_unknown_app(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendData_unknown_app(capsys):
"""Test sendData when unknown app""" """Test sendData when unknown app"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
@@ -329,7 +366,8 @@ def test_sendData_unknown_app(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_sendPosition_with_a_position(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendPosition_with_a_position(caplog):
"""Test sendPosition when lat/long/alt""" """Test sendPosition when lat/long/alt"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
@@ -340,7 +378,8 @@ def test_sendPosition_with_a_position(caplog, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_sendPacket_with_no_destination(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendPacket_with_no_destination(capsys):
"""Test _sendPacket()""" """Test _sendPacket()"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
@@ -353,7 +392,8 @@ def test_sendPacket_with_no_destination(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_sendPacket_with_destination_as_int(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendPacket_with_destination_as_int(caplog):
"""Test _sendPacket() with int as a destination""" """Test _sendPacket() with int as a destination"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
@@ -363,7 +403,8 @@ def test_sendPacket_with_destination_as_int(caplog, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_sendPacket_with_destination_starting_with_a_bang(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendPacket_with_destination_starting_with_a_bang(caplog):
"""Test _sendPacket() with int as a destination""" """Test _sendPacket() with int as a destination"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
@@ -373,7 +414,8 @@ def test_sendPacket_with_destination_starting_with_a_bang(caplog, reset_globals)
@pytest.mark.unit @pytest.mark.unit
def test_sendPacket_with_destination_as_BROADCAST_ADDR(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendPacket_with_destination_as_BROADCAST_ADDR(caplog):
"""Test _sendPacket() with BROADCAST_ADDR as a destination""" """Test _sendPacket() with BROADCAST_ADDR as a destination"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
@@ -383,7 +425,8 @@ def test_sendPacket_with_destination_as_BROADCAST_ADDR(caplog, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_sendPacket_with_destination_as_LOCAL_ADDR_no_myInfo(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendPacket_with_destination_as_LOCAL_ADDR_no_myInfo(capsys):
"""Test _sendPacket() with LOCAL_ADDR as a destination with no myInfo""" """Test _sendPacket() with LOCAL_ADDR as a destination with no myInfo"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
@@ -397,11 +440,13 @@ def test_sendPacket_with_destination_as_LOCAL_ADDR_no_myInfo(capsys, reset_globa
@pytest.mark.unit @pytest.mark.unit
def test_sendPacket_with_destination_as_LOCAL_ADDR_with_myInfo(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendPacket_with_destination_as_LOCAL_ADDR_with_myInfo(caplog):
"""Test _sendPacket() with LOCAL_ADDR as a destination with myInfo""" """Test _sendPacket() with LOCAL_ADDR as a destination with myInfo"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
myInfo = MagicMock() myInfo = MagicMock()
iface.myInfo = myInfo iface.myInfo = myInfo
iface.myInfo.my_node_num = 1
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
meshPacket = mesh_pb2.MeshPacket() meshPacket = mesh_pb2.MeshPacket()
iface._sendPacket(meshPacket, destinationId=LOCAL_ADDR) iface._sendPacket(meshPacket, destinationId=LOCAL_ADDR)
@@ -409,7 +454,8 @@ def test_sendPacket_with_destination_as_LOCAL_ADDR_with_myInfo(caplog, reset_glo
@pytest.mark.unit @pytest.mark.unit
def test_sendPacket_with_destination_is_blank_with_nodes(capsys, reset_globals, iface_with_nodes): @pytest.mark.usefixtures("reset_globals")
def test_sendPacket_with_destination_is_blank_with_nodes(capsys, iface_with_nodes):
"""Test _sendPacket() with '' as a destination with myInfo""" """Test _sendPacket() with '' as a destination with myInfo"""
iface = iface_with_nodes iface = iface_with_nodes
meshPacket = mesh_pb2.MeshPacket() meshPacket = mesh_pb2.MeshPacket()
@@ -423,7 +469,8 @@ def test_sendPacket_with_destination_is_blank_with_nodes(capsys, reset_globals,
@pytest.mark.unit @pytest.mark.unit
def test_sendPacket_with_destination_is_blank_without_nodes(caplog, reset_globals, iface_with_nodes): @pytest.mark.usefixtures("reset_globals")
def test_sendPacket_with_destination_is_blank_without_nodes(caplog, iface_with_nodes):
"""Test _sendPacket() with '' as a destination with myInfo""" """Test _sendPacket() with '' as a destination with myInfo"""
iface = iface_with_nodes iface = iface_with_nodes
iface.nodes = None iface.nodes = None
@@ -434,7 +481,8 @@ def test_sendPacket_with_destination_is_blank_without_nodes(caplog, reset_global
@pytest.mark.unit @pytest.mark.unit
def test_getMyNodeInfo(reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_getMyNodeInfo():
"""Test getMyNodeInfo()""" """Test getMyNodeInfo()"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
anode = iface.getNode(LOCAL_ADDR) anode = iface.getNode(LOCAL_ADDR)
@@ -448,7 +496,8 @@ def test_getMyNodeInfo(reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_generatePacketId(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_generatePacketId(capsys):
"""Test _generatePacketId() when no currentPacketId (not connected)""" """Test _generatePacketId() when no currentPacketId (not connected)"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
# not sure when this condition would ever happen... but we can simulate it # not sure when this condition would ever happen... but we can simulate it
@@ -460,3 +509,164 @@ def test_generatePacketId(capsys, reset_globals):
assert re.search(r'Not connected yet, can not generate packet', out, re.MULTILINE) assert re.search(r'Not connected yet, can not generate packet', out, re.MULTILINE)
assert err == '' assert err == ''
assert pytest_wrapped_e.type == Exception assert pytest_wrapped_e.type == Exception
@pytest.mark.unit
@pytest.mark.usefixtures("reset_globals")
def test_fixupPosition_empty_pos():
"""Test _fixupPosition()"""
iface = MeshInterface(noProto=True)
pos = {}
newpos = iface._fixupPosition(pos)
assert newpos == pos
@pytest.mark.unit
@pytest.mark.usefixtures("reset_globals")
def test_fixupPosition_no_changes_needed():
"""Test _fixupPosition()"""
iface = MeshInterface(noProto=True)
pos = {"latitude": 101, "longitude": 102}
newpos = iface._fixupPosition(pos)
assert newpos == pos
@pytest.mark.unit
@pytest.mark.usefixtures("reset_globals")
def test_fixupPosition():
"""Test _fixupPosition()"""
iface = MeshInterface(noProto=True)
pos = {"latitudeI": 1010000000, "longitudeI": 1020000000}
newpos = iface._fixupPosition(pos)
assert newpos == {"latitude": 101.0,
"latitudeI": 1010000000,
"longitude": 102.0,
"longitudeI": 1020000000}
@pytest.mark.unit
@pytest.mark.usefixtures("reset_globals")
def test_nodeNumToId(iface_with_nodes):
"""Test _nodeNumToId()"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
someid = iface._nodeNumToId(2475227164)
assert someid == '!9388f81c'
@pytest.mark.unit
@pytest.mark.usefixtures("reset_globals")
def test_nodeNumToId_not_found(iface_with_nodes):
"""Test _nodeNumToId()"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
someid = iface._nodeNumToId(123)
assert someid is None
@pytest.mark.unit
@pytest.mark.usefixtures("reset_globals")
def test_nodeNumToId_to_all(iface_with_nodes):
"""Test _nodeNumToId()"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
someid = iface._nodeNumToId(0xffffffff)
assert someid == '^all'
@pytest.mark.unit
@pytest.mark.usefixtures("reset_globals")
def test_getOrCreateByNum_minimal(iface_with_nodes):
"""Test _getOrCreateByNum()"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
tmp = iface._getOrCreateByNum(123)
assert tmp == {'num': 123}
@pytest.mark.unit
@pytest.mark.usefixtures("reset_globals")
def test_getOrCreateByNum_not_found(iface_with_nodes):
"""Test _getOrCreateByNum()"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
with pytest.raises(Exception) as pytest_wrapped_e:
iface._getOrCreateByNum(0xffffffff)
assert pytest_wrapped_e.type == Exception
@pytest.mark.unit
@pytest.mark.usefixtures("reset_globals")
def test_getOrCreateByNum(iface_with_nodes):
"""Test _getOrCreateByNum()"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
tmp = iface._getOrCreateByNum(2475227164)
assert tmp['num'] == 2475227164
@pytest.mark.unit
def test_enter():
"""Test __enter__()"""
iface = MeshInterface(noProto=True)
assert iface == iface.__enter__()
@pytest.mark.unit
def test_exit_with_exception(caplog):
"""Test __exit__()"""
iface = MeshInterface(noProto=True)
with caplog.at_level(logging.ERROR):
iface.__exit__('foo', 'bar', 'baz')
assert re.search(r'An exception of type foo with value bar has occurred', caplog.text, re.MULTILINE)
assert re.search(r'Traceback: baz', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_showNodes_exclude_self(capsys, caplog, iface_with_nodes):
"""Test that we hit that continue statement"""
with caplog.at_level(logging.DEBUG):
iface = iface_with_nodes
iface.localNode.nodeNum = 2475227164
iface.showNodes()
iface.showNodes(includeSelf=False)
capsys.readouterr()
@pytest.mark.unitslow
def test_waitForConfig(capsys):
"""Test waitForConfig()"""
iface = MeshInterface(noProto=True)
# override how long to wait
iface._timeout = Timeout(0.01)
with pytest.raises(Exception) as pytest_wrapped_e:
iface.waitForConfig()
assert pytest_wrapped_e.type == Exception
out, err = capsys.readouterr()
assert re.search(r'Exception: Timed out waiting for interface config', err, re.MULTILINE)
assert out == ''
@pytest.mark.unit
def test_waitConnected_raises_an_exception(capsys):
"""Test waitConnected()"""
iface = MeshInterface(noProto=True)
with pytest.raises(Exception) as pytest_wrapped_e:
iface.failure = "warn about something"
iface._waitConnected(0.01)
assert pytest_wrapped_e.type == Exception
out, err = capsys.readouterr()
assert re.search(r'warn about something', err, re.MULTILINE)
assert out == ''
@pytest.mark.unit
def test_waitConnected_isConnected_timeout(capsys):
"""Test waitConnected()"""
with pytest.raises(Exception) as pytest_wrapped_e:
iface = MeshInterface()
iface._waitConnected(0.01)
assert pytest_wrapped_e.type == Exception
out, err = capsys.readouterr()
assert re.search(r'warn about something', err, re.MULTILINE)
assert out == ''

View File

@@ -11,6 +11,7 @@ from ..serial_interface import SerialInterface
from ..admin_pb2 import AdminMessage from ..admin_pb2 import AdminMessage
from ..channel_pb2 import Channel from ..channel_pb2 import Channel
from ..radioconfig_pb2 import RadioConfig from ..radioconfig_pb2 import RadioConfig
from ..util import Timeout
@pytest.mark.unit @pytest.mark.unit
@@ -29,7 +30,7 @@ def test_node(capsys):
@pytest.mark.unit @pytest.mark.unit
def test_node_reqquestConfig(): def test_node_requestConfig(capsys):
"""Test run requestConfig""" """Test run requestConfig"""
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
amesg = MagicMock(autospec=AdminMessage) amesg = MagicMock(autospec=AdminMessage)
@@ -37,6 +38,9 @@ def test_node_reqquestConfig():
with patch('meshtastic.admin_pb2.AdminMessage', return_value=amesg): with patch('meshtastic.admin_pb2.AdminMessage', return_value=amesg):
anode = Node(mo, 'bar') anode = Node(mo, 'bar')
anode.requestConfig() anode.requestConfig()
out, err = capsys.readouterr()
assert re.search(r'Requesting preferences from remote node', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit @pytest.mark.unit
@@ -51,6 +55,15 @@ def test_setOwner_and_team(caplog):
assert re.search(r'p.set_owner.team:1', caplog.text, re.MULTILINE) assert re.search(r'p.set_owner.team:1', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_setOwnerShort(caplog):
"""Test setOwner"""
anode = Node('foo', 'bar', noProto=True)
with caplog.at_level(logging.DEBUG):
anode.setOwner(long_name=None, short_name='123')
assert re.search(r'p.set_owner.short_name:123:', caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
def test_setOwner_no_short_name(caplog): def test_setOwner_no_short_name(caplog):
"""Test setOwner""" """Test setOwner"""
@@ -87,6 +100,16 @@ def test_setOwner_no_short_name_and_long_name_has_words(caplog):
assert re.search(r'p.set_owner.team:0', caplog.text, re.MULTILINE) assert re.search(r'p.set_owner.team:0', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_setOwner_long_name_no_short(caplog):
"""Test setOwner"""
anode = Node('foo', 'bar', noProto=True)
with caplog.at_level(logging.DEBUG):
anode.setOwner(long_name ='Aabo', is_licensed=True)
assert re.search(r'p.set_owner.long_name:Aabo:', caplog.text, re.MULTILINE)
assert re.search(r'p.set_owner.short_name:Aab:', caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
def test_exitSimulator(caplog): def test_exitSimulator(caplog):
"""Test exitSimulator""" """Test exitSimulator"""
@@ -106,13 +129,25 @@ def test_reboot(caplog):
@pytest.mark.unit @pytest.mark.unit
def test_setURL_empty_url(): def test_shutdown(caplog):
"""Test shutdown"""
anode = Node('foo', 'bar', noProto=True)
with caplog.at_level(logging.DEBUG):
anode.shutdown()
assert re.search(r'Telling node to shutdown', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_setURL_empty_url(capsys):
"""Test reboot""" """Test reboot"""
anode = Node('foo', 'bar', noProto=True) anode = Node('foo', 'bar', noProto=True)
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
anode.setURL('') anode.setURL('')
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r'Warning: No RadioConfig has been read', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit @pytest.mark.unit
@@ -133,7 +168,7 @@ def test_setURL_valid_URL(caplog):
@pytest.mark.unit @pytest.mark.unit
def test_setURL_valid_URL_but_no_settings(caplog): def test_setURL_valid_URL_but_no_settings(capsys):
"""Test setURL""" """Test setURL"""
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
url = "https://www.meshtastic.org/d/#" url = "https://www.meshtastic.org/d/#"
@@ -143,6 +178,9 @@ def test_setURL_valid_URL_but_no_settings(caplog):
anode.setURL(url) anode.setURL(url)
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r'Warning: There were no settings', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit @pytest.mark.unit
@@ -410,7 +448,7 @@ def test_deleteChannel_secondary_with_admin_channel_before_testing():
@pytest.mark.unit @pytest.mark.unit
def test_getChannelByName(capsys): def test_getChannelByName():
"""Get a channel by the name.""" """Get a channel by the name."""
anode = Node('foo', 'bar') anode = Node('foo', 'bar')
@@ -437,7 +475,7 @@ def test_getChannelByName(capsys):
@pytest.mark.unit @pytest.mark.unit
def test_getChannelByName_invalid_name(capsys): def test_getChannelByName_invalid_name():
"""Get a channel by the name but one that is not present.""" """Get a channel by the name but one that is not present."""
anode = Node('foo', 'bar') anode = Node('foo', 'bar')
@@ -464,7 +502,7 @@ def test_getChannelByName_invalid_name(capsys):
@pytest.mark.unit @pytest.mark.unit
def test_getDisabledChannel(capsys): def test_getDisabledChannel():
"""Get the first disabled channel.""" """Get the first disabled channel."""
anode = Node('foo', 'bar') anode = Node('foo', 'bar')
@@ -494,7 +532,7 @@ def test_getDisabledChannel(capsys):
@pytest.mark.unit @pytest.mark.unit
def test_getDisabledChannel_where_all_channels_are_used(capsys): def test_getDisabledChannel_where_all_channels_are_used():
"""Get the first disabled channel.""" """Get the first disabled channel."""
anode = Node('foo', 'bar') anode = Node('foo', 'bar')
@@ -518,7 +556,7 @@ def test_getDisabledChannel_where_all_channels_are_used(capsys):
@pytest.mark.unit @pytest.mark.unit
def test_getAdminChannelIndex(capsys): def test_getAdminChannelIndex():
"""Get the 'admin' channel index.""" """Get the 'admin' channel index."""
anode = Node('foo', 'bar') anode = Node('foo', 'bar')
@@ -545,7 +583,7 @@ def test_getAdminChannelIndex(capsys):
@pytest.mark.unit @pytest.mark.unit
def test_getAdminChannelIndex_when_no_admin_named_channel(capsys): def test_getAdminChannelIndex_when_no_admin_named_channel():
"""Get the 'admin' channel when there is not one.""" """Get the 'admin' channel when there is not one."""
anode = Node('foo', 'bar') anode = Node('foo', 'bar')
@@ -623,7 +661,7 @@ def test_writeConfig(caplog):
@pytest.mark.unit @pytest.mark.unit
def test_requestChannel_not_localNode(caplog): def test_requestChannel_not_localNode(caplog, capsys):
"""Test _requestChannel()""" """Test _requestChannel()"""
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
@@ -633,6 +671,9 @@ def test_requestChannel_not_localNode(caplog):
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
anode._requestChannel(0) anode._requestChannel(0)
assert re.search(r'Requesting channel 0 info from remote node', caplog.text, re.MULTILINE) assert re.search(r'Requesting channel 0 info from remote node', caplog.text, re.MULTILINE)
out, err = capsys.readouterr()
assert re.search(r'Requesting channel 0 info', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit @pytest.mark.unit
@@ -857,3 +898,14 @@ def test_onResponseRequestSetting_with_error(capsys):
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Error on response', out) assert re.search(r'Error on response', out)
assert err == '' assert err == ''
@pytest.mark.unitslow
def test_waitForConfig():
"""Test waitForConfig()"""
anode = Node('foo', 'bar')
radioConfig = RadioConfig()
anode.radioConfig = radioConfig
anode._timeout = Timeout(0.01)
result = anode.waitForConfig()
assert not result

View File

@@ -79,7 +79,7 @@ def test_watchGPIOs(caplog):
@pytest.mark.unit @pytest.mark.unit
def test_sendHardware_no_nodeid(): def test_sendHardware_no_nodeid(capsys):
"""Test sending no nodeid to _sendHardware()""" """Test sending no nodeid to _sendHardware()"""
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
@@ -87,3 +87,6 @@ def test_sendHardware_no_nodeid():
rhw = RemoteHardwareClient(mo) rhw = RemoteHardwareClient(mo)
rhw._sendHardware(None, None) rhw._sendHardware(None, None)
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
out, err = capsys.readouterr()
assert re.search(r'Warning: Must use a destination node ID', out)
assert err == ''

View File

@@ -3,15 +3,19 @@
import re import re
from unittest.mock import patch from unittest.mock import patch, mock_open
import pytest import pytest
from ..serial_interface import SerialInterface from ..serial_interface import SerialInterface
@pytest.mark.unit @pytest.mark.unit
@patch("time.sleep")
@patch("termios.tcsetattr")
@patch("termios.tcgetattr")
@patch("builtins.open", new_callable=mock_open, read_data="data")
@patch('serial.Serial') @patch('serial.Serial')
@patch('meshtastic.util.findPorts', return_value=['/dev/ttyUSBfake']) @patch('meshtastic.util.findPorts', return_value=['/dev/ttyUSBfake'])
def test_SerialInterface_single_port(mocked_findPorts, mocked_serial): def test_SerialInterface_single_port(mocked_findPorts, mocked_serial, mocked_open, mock_get, mock_set, mock_sleep, capsys):
"""Test that we can instantiate a SerialInterface with a single port""" """Test that we can instantiate a SerialInterface with a single port"""
iface = SerialInterface(noProto=True) iface = SerialInterface(noProto=True)
iface.showInfo() iface.showInfo()
@@ -19,6 +23,16 @@ def test_SerialInterface_single_port(mocked_findPorts, mocked_serial):
iface.close() iface.close()
mocked_findPorts.assert_called() mocked_findPorts.assert_called()
mocked_serial.assert_called() mocked_serial.assert_called()
mocked_open.assert_called()
mock_get.assert_called()
mock_set.assert_called()
mock_sleep.assert_called()
out, err = capsys.readouterr()
assert re.search(r'Nodes in mesh', out, re.MULTILINE)
assert re.search(r'Preferences', out, re.MULTILINE)
assert re.search(r'Channels', out, re.MULTILINE)
assert re.search(r'Primary channel', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit @pytest.mark.unit

View File

@@ -2,6 +2,7 @@
import re import re
import subprocess import subprocess
import time import time
import platform
import os import os
# Do not like using hard coded sleeps, but it probably makes # Do not like using hard coded sleeps, but it probably makes
@@ -140,8 +141,9 @@ def test_smoke1_nodes():
"""Test --nodes""" """Test --nodes"""
return_value, out = subprocess.getstatusoutput('meshtastic --nodes') return_value, out = subprocess.getstatusoutput('meshtastic --nodes')
assert re.match(r'Connected to radio', out) assert re.match(r'Connected to radio', out)
assert re.search(r'^│ N │ User', out, re.MULTILINE) if platform.system() != 'Windows':
assert re.search(r'^│ 1 │', out, re.MULTILINE) assert re.search(r' User ', out, re.MULTILINE)
assert re.search(r' 1 ', out, re.MULTILINE)
assert return_value == 0 assert return_value == 0

View File

@@ -0,0 +1,706 @@
"""Meshtastic smoke tests with a single virtual device via localhost.
During the CI build of the Meshtastic-device, a build.zip file is created.
Inside that build.zip is a standalone executable meshtasticd_linux_amd64.
That linux executable will simulate a Meshtastic device listening on localhost.
This smoke test runs against that localhost.
"""
import re
import subprocess
import time
import platform
import os
# Do not like using hard coded sleeps, but it probably makes
# sense to pause for the radio at apprpriate times
import pytest
from ..util import findPorts
# seconds to pause after running a meshtastic command
PAUSE_AFTER_COMMAND = 0.1
PAUSE_AFTER_REBOOT = 0.1
#TODO: need to fix the virtual device to have a reboot. When you issue the command
# below, you get "FIXME implement reboot for this platform"
#@pytest.mark.smokevirt
#def test_smokevirt_reboot():
# """Test reboot"""
# return_value, _ = subprocess.getstatusoutput('meshtastic --host localhost --reboot')
# assert return_value == 0
# # pause for the radio to reset
# time.sleep(8)
@pytest.mark.smokevirt
def test_smokevirt_info():
"""Test --info"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Owner', out, re.MULTILINE)
assert re.search(r'^My info', out, re.MULTILINE)
assert re.search(r'^Nodes in mesh', out, re.MULTILINE)
assert re.search(r'^Preferences', out, re.MULTILINE)
assert re.search(r'^Channels', out, re.MULTILINE)
assert re.search(r'^ PRIMARY', out, re.MULTILINE)
assert re.search(r'^Primary channel URL', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_sendping():
"""Test --sendping"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --sendping')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Sending ping message', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
def test_get_with_invalid_setting():
"""Test '--get a_bad_setting'."""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --get a_bad_setting')
assert re.search(r'Choices in sorted order', out)
assert return_value == 0
@pytest.mark.smokevirt
def test_set_with_invalid_setting():
"""Test '--set a_bad_setting'."""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set a_bad_setting foo')
assert re.search(r'Choices in sorted order', out)
assert return_value == 0
@pytest.mark.smokevirt
def test_ch_set_with_invalid_settingpatch_find_ports():
"""Test '--ch-set with a_bad_setting'."""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-set invalid_setting foo --ch-index 0')
assert re.search(r'Choices in sorted order', out)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_pos_fields():
"""Test --pos-fields (with some values POS_ALTITUDE POS_ALT_MSL POS_BATTERY)"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --pos-fields POS_ALTITUDE POS_ALT_MSL POS_BATTERY')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Setting position fields to 35', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --pos-fields')
assert re.match(r'Connected to radio', out)
assert re.search(r'POS_ALTITUDE', out, re.MULTILINE)
assert re.search(r'POS_ALT_MSL', out, re.MULTILINE)
assert re.search(r'POS_BATTERY', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_test_with_arg_but_no_hardware():
"""Test --test
Note: Since only one device is connected, it will not do much.
"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --test')
assert re.search(r'^Warning: Must have at least two devices', out, re.MULTILINE)
assert return_value == 1
@pytest.mark.smokevirt
def test_smokevirt_debug():
"""Test --debug"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info --debug')
assert re.search(r'^Owner', out, re.MULTILINE)
assert re.search(r'^DEBUG file', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_seriallog_to_file():
"""Test --seriallog to a file creates a file"""
filename = 'tmpoutput.txt'
if os.path.exists(f"{filename}"):
os.remove(f"{filename}")
return_value, _ = subprocess.getstatusoutput(f'meshtastic --host localhost --info --seriallog {filename}')
assert os.path.exists(f"{filename}")
assert return_value == 0
os.remove(f"{filename}")
@pytest.mark.smokevirt
def test_smokevirt_qr():
"""Test --qr"""
filename = 'tmpqr'
if os.path.exists(f"{filename}"):
os.remove(f"{filename}")
return_value, _ = subprocess.getstatusoutput(f'meshtastic --host localhost --qr > {filename}')
assert os.path.exists(f"{filename}")
# not really testing that a valid qr code is created, just that the file size
# is reasonably big enough for a qr code
assert os.stat(f"{filename}").st_size > 20000
assert return_value == 0
os.remove(f"{filename}")
@pytest.mark.smokevirt
def test_smokevirt_nodes():
"""Test --nodes"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --nodes')
assert re.match(r'Connected to radio', out)
if platform.system() != 'Windows':
assert re.search(r' User ', out, re.MULTILINE)
assert re.search(r' 1 ', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_send_hello():
"""Test --sendtext hello"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --sendtext hello')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Sending text message hello to \^all', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_port():
"""Test --port"""
# first, get the ports
ports = findPorts()
# hopefully there is none
assert len(ports) == 0
@pytest.mark.smokevirt
def test_smokevirt_set_is_router_true():
"""Test --set is_router true"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set is_router true')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set is_router to true', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --get is_router')
assert re.search(r'^is_router: True', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_set_location_info():
"""Test --setlat, --setlon and --setalt """
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --setlat 32.7767 --setlon -96.7970 --setalt 1337')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Fixing altitude', out, re.MULTILINE)
assert re.search(r'^Fixing latitude', out, re.MULTILINE)
assert re.search(r'^Fixing longitude', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out2 = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.search(r'1337', out2, re.MULTILINE)
assert re.search(r'32.7767', out2, re.MULTILINE)
assert re.search(r'-96.797', out2, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_set_is_router_false():
"""Test --set is_router false"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set is_router false')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set is_router to false', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --get is_router')
assert re.search(r'^is_router: False', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_set_owner():
"""Test --set-owner name"""
# make sure the owner is not Joe
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set-owner Bob')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Setting device owner to Bob', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert not re.search(r'Owner: Joe', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set-owner Joe')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Setting device owner to Joe', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.search(r'Owner: Joe', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_set_team():
"""Test --set-team """
# unset the team
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set-team CLEAR')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Setting team to CLEAR', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set-team CYAN')
assert re.search(r'Setting team to CYAN', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.search(r'CYAN', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_ch_values():
"""Test --ch-longslow, --ch-longfast, --ch-mediumslow, --ch-mediumsfast,
--ch-shortslow, and --ch-shortfast arguments
"""
exp = {
'--ch-longslow': 'Bw125Cr48Sf4096',
'--ch-longfast': 'Bw31_25Cr48Sf512',
'--ch-mediumslow': 'Bw250Cr46Sf2048',
'--ch-mediumfast': 'Bw250Cr47Sf1024',
'--ch-shortslow': '{ "psk',
'--ch-shortfast': 'Bw500Cr45Sf128'
}
for key, val in exp.items():
return_value, out = subprocess.getstatusoutput(f'meshtastic --host localhost {key}')
assert re.match(r'Connected to radio', out)
assert re.search(r'Writing modified channels to device', out, re.MULTILINE)
assert return_value == 0
# pause for the radio (might reboot)
time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.search(val, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@pytest.mark.smokevirt
def test_smokevirt_ch_set_name():
"""Test --ch-set name"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert not re.search(r'MyChannel', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-set name MyChannel')
assert re.match(r'Connected to radio', out)
assert re.search(r'Warning: Need to specify', out, re.MULTILINE)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-set name MyChannel --ch-index 0')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set name to MyChannel', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.search(r'MyChannel', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_ch_set_downlink_and_uplink():
"""Test -ch-set downlink_enabled X and --ch-set uplink_enabled X"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-set downlink_enabled false --ch-set uplink_enabled false')
assert re.match(r'Connected to radio', out)
assert re.search(r'Warning: Need to specify', out, re.MULTILINE)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
# pylint: disable=C0301
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-set downlink_enabled false --ch-set uplink_enabled false --ch-index 0')
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert not re.search(r'uplinkEnabled', out, re.MULTILINE)
assert not re.search(r'downlinkEnabled', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
# pylint: disable=C0301
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-set downlink_enabled true --ch-set uplink_enabled true --ch-index 0')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set downlink_enabled to true', out, re.MULTILINE)
assert re.search(r'^Set uplink_enabled to true', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.search(r'uplinkEnabled', out, re.MULTILINE)
assert re.search(r'downlinkEnabled', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_ch_add_and_ch_del():
"""Test --ch-add"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-add testing')
assert re.search(r'Writing modified channels to device', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'SECONDARY', out, re.MULTILINE)
assert re.search(r'testing', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-index 1 --ch-del')
assert re.search(r'Deleting channel 1', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)
# make sure the secondar channel is not there
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.match(r'Connected to radio', out)
assert not re.search(r'SECONDARY', out, re.MULTILINE)
assert not re.search(r'testing', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_ch_enable_and_disable():
"""Test --ch-enable and --ch-disable"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-add testing')
assert re.search(r'Writing modified channels to device', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'SECONDARY', out, re.MULTILINE)
assert re.search(r'testing', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
# ensure they need to specify a --ch-index
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-disable')
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-disable --ch-index 1')
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'DISABLED', out, re.MULTILINE)
assert re.search(r'testing', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-enable --ch-index 1')
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'SECONDARY', out, re.MULTILINE)
assert re.search(r'testing', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-del --ch-index 1')
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@pytest.mark.smokevirt
def test_smokevirt_ch_del_a_disabled_non_primary_channel():
"""Test --ch-del will work on a disabled non-primary channel."""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-add testing')
assert re.search(r'Writing modified channels to device', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'SECONDARY', out, re.MULTILINE)
assert re.search(r'testing', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
# ensure they need to specify a --ch-index
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-disable')
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-del --ch-index 1')
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.match(r'Connected to radio', out)
assert not re.search(r'DISABLED', out, re.MULTILINE)
assert not re.search(r'SECONDARY', out, re.MULTILINE)
assert not re.search(r'testing', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@pytest.mark.smokevirt
def test_smokevirt_attempt_to_delete_primary_channel():
"""Test that we cannot delete the PRIMARY channel."""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-del --ch-index 0')
assert re.search(r'Warning: Cannot delete primary channel', out, re.MULTILINE)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@pytest.mark.smokevirt
def test_smokevirt_attempt_to_disable_primary_channel():
"""Test that we cannot disable the PRIMARY channel."""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-disable --ch-index 0')
assert re.search(r'Warning: Cannot enable', out, re.MULTILINE)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@pytest.mark.smokevirt
def test_smokevirt_attempt_to_enable_primary_channel():
"""Test that we cannot enable the PRIMARY channel."""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-enable --ch-index 0')
assert re.search(r'Warning: Cannot enable', out, re.MULTILINE)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@pytest.mark.smokevirt
def test_smokevirt_ensure_ch_del_second_of_three_channels():
"""Test that when we delete the 2nd of 3 channels, that it deletes the correct channel."""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-add testing1')
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'SECONDARY', out, re.MULTILINE)
assert re.search(r'testing1', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-add testing2')
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'testing2', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-del --ch-index 1')
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'testing2', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-del --ch-index 1')
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@pytest.mark.smokevirt
def test_smokevirt_ensure_ch_del_third_of_three_channels():
"""Test that when we delete the 3rd of 3 channels, that it deletes the correct channel."""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-add testing1')
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'SECONDARY', out, re.MULTILINE)
assert re.search(r'testing1', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-add testing2')
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'testing2', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-del --ch-index 2')
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'testing1', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-del --ch-index 1')
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@pytest.mark.smokevirt
def test_smokevirt_ch_set_modem_config():
"""Test --ch-set modem_config"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-set modem_config Bw31_25Cr48Sf512')
assert re.search(r'Warning: Need to specify', out, re.MULTILINE)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert not re.search(r'Bw31_25Cr48Sf512', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-set modem_config Bw31_25Cr48Sf512 --ch-index 0')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set modem_config to Bw31_25Cr48Sf512', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.search(r'Bw31_25Cr48Sf512', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_seturl_default():
"""Test --seturl with default value"""
# set some channel value so we no longer have a default channel
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-set name foo --ch-index 0')
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
# ensure we no longer have a default primary channel
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert not re.search('CgUYAyIBAQ', out, re.MULTILINE)
assert return_value == 0
url = "https://www.meshtastic.org/d/#CgUYAyIBAQ"
return_value, out = subprocess.getstatusoutput(f"meshtastic --host localhost --seturl {url}")
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.search('CgUYAyIBAQ', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_seturl_invalid_url():
"""Test --seturl with invalid url"""
# Note: This url is no longer a valid url.
url = "https://www.meshtastic.org/c/#GAMiENTxuzogKQdZ8Lz_q89Oab8qB0RlZmF1bHQ="
return_value, out = subprocess.getstatusoutput(f"meshtastic --host localhost --seturl {url}")
assert re.match(r'Connected to radio', out)
assert re.search('Warning: There were no settings', out, re.MULTILINE)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@pytest.mark.smokevirt
def test_smokevirt_configure():
"""Test --configure"""
_ , out = subprocess.getstatusoutput(f"meshtastic --host localhost --configure example_config.yaml")
assert re.match(r'Connected to radio', out)
assert re.search('^Setting device owner to Bob TBeam', out, re.MULTILINE)
assert re.search('^Fixing altitude at 304 meters', out, re.MULTILINE)
assert re.search('^Fixing latitude at 35.8', out, re.MULTILINE)
assert re.search('^Fixing longitude at -93.8', out, re.MULTILINE)
assert re.search('^Setting device position', out, re.MULTILINE)
assert re.search('^Set region to 1', out, re.MULTILINE)
assert re.search('^Set is_always_powered to true', out, re.MULTILINE)
assert re.search('^Set send_owner_interval to 2', out, re.MULTILINE)
assert re.search('^Set screen_on_secs to 31536000', out, re.MULTILINE)
assert re.search('^Set wait_bluetooth_secs to 31536000', out, re.MULTILINE)
assert re.search('^Writing modified preferences to device', out, re.MULTILINE)
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)
@pytest.mark.smokevirt
def test_smokevirt_set_ham():
"""Test --set-ham
Note: Do a factory reset after this setting so it is very short-lived.
"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set-ham KI1234')
assert re.search(r'Setting Ham ID', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.search(r'Owner: KI1234', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_set_wifi_settings():
"""Test --set wifi_ssid and --set wifi_password"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set wifi_ssid "some_ssid" --set wifi_password "temp1234"')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set wifi_ssid to some_ssid', out, re.MULTILINE)
assert re.search(r'^Set wifi_password to temp1234', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --get wifi_ssid --get wifi_password')
assert re.search(r'^wifi_ssid: some_ssid', out, re.MULTILINE)
assert re.search(r'^wifi_password: sekrit', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_factory_reset():
"""Test factory reset"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set factory_reset true')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set factory_reset to true', out, re.MULTILINE)
assert re.search(r'^Writing modified preferences to device', out, re.MULTILINE)
assert return_value == 0
# NOTE: The virtual radio will not respond well after this command. Need to re-start the virtual program at this point.
# TODO: fix?

View File

@@ -19,7 +19,8 @@ def test_StreamInterface():
# Note: This takes a bit, so moving from unit to slow # Note: This takes a bit, so moving from unit to slow
@pytest.mark.unitslow @pytest.mark.unitslow
def test_StreamInterface_with_noProto(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_StreamInterface_with_noProto(caplog):
"""Test that we can instantiate a StreamInterface based on nonProto """Test that we can instantiate a StreamInterface based on nonProto
and we can read/write bytes from a mocked stream and we can read/write bytes from a mocked stream
""" """
@@ -34,11 +35,12 @@ def test_StreamInterface_with_noProto(caplog, reset_globals):
assert data == test_data assert data == test_data
# Note: This takes a bit, so moving from unit to slow ## Note: This takes a bit, so moving from unit to slow
# Tip: If you want to see the print output, run with '-s' flag: ## Tip: If you want to see the print output, run with '-s' flag:
# pytest -s meshtastic/tests/test_stream_interface.py::test_sendToRadioImpl ## pytest -s meshtastic/tests/test_stream_interface.py::test_sendToRadioImpl
@pytest.mark.unitslow @pytest.mark.unitslow
def test_sendToRadioImpl(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendToRadioImpl(caplog):
"""Test _sendToRadioImpl()""" """Test _sendToRadioImpl()"""
# def add_header(b): # def add_header(b):
@@ -78,4 +80,3 @@ def test_sendToRadioImpl(caplog, reset_globals):
assert re.search(r'Sending: ', caplog.text, re.MULTILINE) assert re.search(r'Sending: ', caplog.text, re.MULTILINE)
assert re.search(r'reading character', caplog.text, re.MULTILINE) assert re.search(r'reading character', caplog.text, re.MULTILINE)
assert re.search(r'In reader loop', caplog.text, re.MULTILINE) assert re.search(r'In reader loop', caplog.text, re.MULTILINE)
print(caplog.text)

View File

@@ -13,6 +13,7 @@ def test_TCPInterface(capsys):
"""Test that we can instantiate a TCPInterface""" """Test that we can instantiate a TCPInterface"""
with patch('socket.socket') as mock_socket: with patch('socket.socket') as mock_socket:
iface = TCPInterface(hostname='localhost', noProto=True) iface = TCPInterface(hostname='localhost', noProto=True)
iface.myConnect()
iface.showInfo() iface.showInfo()
iface.localNode.showInfo() iface.localNode.showInfo()
out, err = capsys.readouterr() out, err = capsys.readouterr()
@@ -24,3 +25,28 @@ def test_TCPInterface(capsys):
assert err == '' assert err == ''
assert mock_socket.called assert mock_socket.called
iface.close() iface.close()
@pytest.mark.unit
def test_TCPInterface_exception():
"""Test that we can instantiate a TCPInterface"""
def throw_an_exception():
raise ValueError("Fake exception.")
with patch('meshtastic.tcp_interface.TCPInterface._socket_shutdown') as mock_shutdown:
mock_shutdown.side_effect = throw_an_exception
with patch('socket.socket') as mock_socket:
iface = TCPInterface(hostname='localhost', noProto=True)
iface.myConnect()
iface.close()
assert mock_socket.called
assert mock_shutdown.called
@pytest.mark.unit
def test_TCPInterface_without_connecting():
"""Test that we can instantiate a TCPInterface with connectNow as false"""
with patch('socket.socket'):
iface = TCPInterface(hostname='localhost', noProto=True, connectNow=False)
assert iface.socket is None

View File

@@ -0,0 +1,266 @@
"""Meshtastic unit tests for tunnel.py"""
import re
import sys
import logging
from unittest.mock import patch, MagicMock
import pytest
from ..tcp_interface import TCPInterface
from ..tunnel import Tunnel, onTunnelReceive
from ..globals import Globals
@pytest.mark.unit
@patch('platform.system')
def test_Tunnel_on_non_linux_system(mock_platform_system):
"""Test that we cannot instantiate a Tunnel on a non Linux system"""
a_mock = MagicMock()
a_mock.return_value = 'notLinux'
mock_platform_system.side_effect = a_mock
with patch('socket.socket') as mock_socket:
with pytest.raises(Exception) as pytest_wrapped_e:
iface = TCPInterface(hostname='localhost', noProto=True)
Tunnel(iface)
assert pytest_wrapped_e.type == Exception
assert mock_socket.called
@pytest.mark.unit
@patch('platform.system')
def test_Tunnel_without_interface(mock_platform_system):
"""Test that we can not instantiate a Tunnel without a valid interface"""
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with pytest.raises(Exception) as pytest_wrapped_e:
Tunnel(None)
assert pytest_wrapped_e.type == Exception
@pytest.mark.unitslow
@patch('platform.system')
def test_Tunnel_with_interface(mock_platform_system, caplog, iface_with_nodes):
"""Test that we can not instantiate a Tunnel without a valid interface"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.WARNING):
with patch('socket.socket'):
tun = Tunnel(iface)
assert tun == Globals.getInstance().get_tunnelInstance()
iface.close()
assert re.search(r'Not creating a TapDevice()', caplog.text, re.MULTILINE)
assert re.search(r'Not starting TUN reader', caplog.text, re.MULTILINE)
assert re.search(r'Not sending packet', caplog.text, re.MULTILINE)
@pytest.mark.unitslow
@patch('platform.system')
def test_onTunnelReceive_from_ourselves(mock_platform_system, caplog, iface_with_nodes):
"""Test onTunnelReceive"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
sys.argv = ['']
Globals.getInstance().set_args(sys.argv)
packet = {'decoded': { 'payload': 'foo'}, 'from': 2475227164}
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG):
with patch('socket.socket'):
tun = Tunnel(iface)
Globals.getInstance().set_tunnelInstance(tun)
onTunnelReceive(packet, iface)
assert re.search(r'in onTunnelReceive', caplog.text, re.MULTILINE)
assert re.search(r'Ignoring message we sent', caplog.text, re.MULTILINE)
@pytest.mark.unit
@patch('platform.system')
def test_onTunnelReceive_from_someone_else(mock_platform_system, caplog, iface_with_nodes):
"""Test onTunnelReceive"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
sys.argv = ['']
Globals.getInstance().set_args(sys.argv)
packet = {'decoded': { 'payload': 'foo'}, 'from': 123}
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG):
with patch('socket.socket'):
tun = Tunnel(iface)
Globals.getInstance().set_tunnelInstance(tun)
onTunnelReceive(packet, iface)
assert re.search(r'in onTunnelReceive', caplog.text, re.MULTILINE)
@pytest.mark.unitslow
@patch('platform.system')
def test_shouldFilterPacket_random(mock_platform_system, caplog, iface_with_nodes):
"""Test _shouldFilterPacket()"""
iface = iface_with_nodes
iface.noProto = True
# random packet
packet = b'1234567890123456789012345678901234567890'
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG):
with patch('socket.socket'):
tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet)
assert not ignore
@pytest.mark.unitslow
@patch('platform.system')
def test_shouldFilterPacket_in_blacklist(mock_platform_system, caplog, iface_with_nodes):
"""Test _shouldFilterPacket()"""
iface = iface_with_nodes
iface.noProto = True
# faked IGMP
packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG):
with patch('socket.socket'):
tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet)
assert ignore
@pytest.mark.unitslow
@patch('platform.system')
def test_shouldFilterPacket_icmp(mock_platform_system, caplog, iface_with_nodes):
"""Test _shouldFilterPacket()"""
iface = iface_with_nodes
iface.noProto = True
# faked ICMP
packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG):
with patch('socket.socket'):
tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet)
assert re.search(r'forwarding ICMP message', caplog.text, re.MULTILINE)
assert not ignore
@pytest.mark.unit
@patch('platform.system')
def test_shouldFilterPacket_udp(mock_platform_system, caplog, iface_with_nodes):
"""Test _shouldFilterPacket()"""
iface = iface_with_nodes
iface.noProto = True
# faked UDP
packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG):
with patch('socket.socket'):
tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet)
assert re.search(r'forwarding udp', caplog.text, re.MULTILINE)
assert not ignore
@pytest.mark.unitslow
@patch('platform.system')
def test_shouldFilterPacket_udp_blacklisted(mock_platform_system, caplog, iface_with_nodes):
"""Test _shouldFilterPacket()"""
iface = iface_with_nodes
iface.noProto = True
# faked UDP
packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x6c\x07\x6c\x00\x00\x00'
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
# Note: custom logging level
LOG_TRACE = 5
with caplog.at_level(LOG_TRACE):
with patch('socket.socket'):
tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet)
assert re.search(r'ignoring blacklisted UDP', caplog.text, re.MULTILINE)
assert ignore
@pytest.mark.unit
@patch('platform.system')
def test_shouldFilterPacket_tcp(mock_platform_system, caplog, iface_with_nodes):
"""Test _shouldFilterPacket()"""
iface = iface_with_nodes
iface.noProto = True
# faked TCP
packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG):
with patch('socket.socket'):
tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet)
assert re.search(r'forwarding tcp', caplog.text, re.MULTILINE)
assert not ignore
@pytest.mark.unitslow
@patch('platform.system')
def test_shouldFilterPacket_tcp_blacklisted(mock_platform_system, caplog, iface_with_nodes):
"""Test _shouldFilterPacket()"""
iface = iface_with_nodes
iface.noProto = True
# faked TCP
packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17\x0c\x17\x0c\x00\x00\x00'
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
# Note: custom logging level
LOG_TRACE = 5
with caplog.at_level(LOG_TRACE):
with patch('socket.socket'):
tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet)
assert re.search(r'ignoring blacklisted TCP', caplog.text, re.MULTILINE)
assert ignore
@pytest.mark.unitslow
@patch('platform.system')
def test_ipToNodeId_none(mock_platform_system, caplog, iface_with_nodes):
"""Test _ipToNodeId()"""
iface = iface_with_nodes
iface.noProto = True
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG):
with patch('socket.socket'):
tun = Tunnel(iface)
nodeid = tun._ipToNodeId('something not useful')
assert nodeid is None
@pytest.mark.unitslow
@patch('platform.system')
def test_ipToNodeId_all(mock_platform_system, caplog, iface_with_nodes):
"""Test _ipToNodeId()"""
iface = iface_with_nodes
iface.noProto = True
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG):
with patch('socket.socket'):
tun = Tunnel(iface)
nodeid = tun._ipToNodeId(b'\x00\x00\xff\xff')
assert nodeid == '^all'

View File

@@ -3,11 +3,15 @@
import re import re
import logging import logging
from unittest.mock import patch
import pytest import pytest
from meshtastic.util import (fixme, stripnl, pskToString, our_exit, from meshtastic.util import (fixme, stripnl, pskToString, our_exit,
support_info, genPSK256, fromStr, fromPSK, support_info, genPSK256, fromStr, fromPSK,
quoteBooleans, catchAndIgnore) quoteBooleans, catchAndIgnore,
remove_keys_from_dict, Timeout, hexstr,
ipstr, readnet_u16, findPorts, convert_mac_addr,
snake_to_camel, camel_to_snake)
@pytest.mark.unit @pytest.mark.unit
@@ -36,9 +40,10 @@ def test_fromStr():
assert fromStr('100.01') == 100.01 assert fromStr('100.01') == 100.01
assert fromStr('123') == 123 assert fromStr('123') == 123
assert fromStr('abc') == 'abc' assert fromStr('abc') == 'abc'
assert fromStr('123456789') == 123456789
@pytest.mark.unit @pytest.mark.unitslow
def test_quoteBooleans(): def test_quoteBooleans():
"""Test quoteBooleans""" """Test quoteBooleans"""
assert quoteBooleans('') == '' assert quoteBooleans('') == ''
@@ -85,13 +90,13 @@ def test_pskToString_one_byte_zero_value():
assert pskToString(bytes([0x00])) == 'unencrypted' assert pskToString(bytes([0x00])) == 'unencrypted'
@pytest.mark.unit @pytest.mark.unitslow
def test_pskToString_one_byte_non_zero_value(): def test_pskToString_one_byte_non_zero_value():
"""Test pskToString one byte that is non-zero""" """Test pskToString one byte that is non-zero"""
assert pskToString(bytes([0x01])) == 'default' assert pskToString(bytes([0x01])) == 'default'
@pytest.mark.unit @pytest.mark.unitslow
def test_pskToString_many_bytes(): def test_pskToString_many_bytes():
"""Test pskToString many bytes""" """Test pskToString many bytes"""
assert pskToString(bytes([0x02, 0x01])) == 'secret' assert pskToString(bytes([0x02, 0x01])) == 'secret'
@@ -103,25 +108,31 @@ def test_pskToString_simple():
assert pskToString(bytes([0x03])) == 'simple2' assert pskToString(bytes([0x03])) == 'simple2'
@pytest.mark.unit @pytest.mark.unitslow
def test_our_exit_zero_return_value(): def test_our_exit_zero_return_value(capsys):
"""Test our_exit with a zero return value""" """Test our_exit with a zero return value"""
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
our_exit("Warning: Some message", 0) our_exit("Warning: Some message", 0)
out, err = capsys.readouterr()
assert re.search(r'Warning: Some message', out, re.MULTILINE)
assert err == ''
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 0 assert pytest_wrapped_e.value.code == 0
@pytest.mark.unit @pytest.mark.unitslow
def test_our_exit_non_zero_return_value(): def test_our_exit_non_zero_return_value(capsys):
"""Test our_exit with a non-zero return value""" """Test our_exit with a non-zero return value"""
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
our_exit("Error: Some message", 1) our_exit("Error: Some message", 1)
out, err = capsys.readouterr()
assert re.search(r'Error: Some message', out, re.MULTILINE)
assert err == ''
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
@pytest.mark.unit @pytest.mark.unitslow
def test_fixme(): def test_fixme():
"""Test fixme()""" """Test fixme()"""
with pytest.raises(Exception) as pytest_wrapped_e: with pytest.raises(Exception) as pytest_wrapped_e:
@@ -149,3 +160,115 @@ def test_catchAndIgnore(caplog):
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
catchAndIgnore("something", some_closure) catchAndIgnore("something", some_closure)
assert re.search(r'Exception thrown in something', caplog.text, re.MULTILINE) assert re.search(r'Exception thrown in something', caplog.text, re.MULTILINE)
@pytest.mark.unitslow
def test_remove_keys_from_dict_empty_keys_empty_dict():
"""Test when keys and dict both are empty"""
assert not remove_keys_from_dict((), {})
@pytest.mark.unitslow
def test_remove_keys_from_dict_empty_dict():
"""Test when dict is empty"""
assert not remove_keys_from_dict(('a'), {})
@pytest.mark.unit
def test_remove_keys_from_dict_empty_keys():
"""Test when keys is empty"""
assert remove_keys_from_dict((), {'a':1}) == {'a':1}
@pytest.mark.unitslow
def test_remove_keys_from_dict():
"""Test remove_keys_from_dict()"""
assert remove_keys_from_dict(('b'), {'a':1, 'b':2}) == {'a':1}
@pytest.mark.unitslow
def test_remove_keys_from_dict_multiple_keys():
"""Test remove_keys_from_dict()"""
keys = ('a', 'b')
adict = {'a': 1, 'b': 2, 'c': 3}
assert remove_keys_from_dict(keys, adict) == {'c':3}
@pytest.mark.unit
def test_remove_keys_from_dict_nested():
"""Test remove_keys_from_dict()"""
keys = ('b')
adict = {'a': {'b': 1}, 'b': 2, 'c': 3}
exp = {'a': {}, 'c': 3}
assert remove_keys_from_dict(keys, adict) == exp
@pytest.mark.unitslow
def test_Timeout_not_found():
"""Test Timeout()"""
to = Timeout(0.2)
attrs = ('foo')
to.waitForSet('bar', attrs)
@pytest.mark.unitslow
def test_Timeout_found():
"""Test Timeout()"""
to = Timeout(0.2)
attrs = ()
to.waitForSet('bar', attrs)
@pytest.mark.unitslow
def test_hexstr():
"""Test hexstr()"""
assert hexstr(b'123') == '31:32:33'
assert hexstr(b'') == ''
@pytest.mark.unitslow
def test_ipstr():
"""Test ipstr()"""
assert ipstr(b'1234') == '49.50.51.52'
assert ipstr(b'') == ''
@pytest.mark.unitslow
def test_readnet_u16():
"""Test readnet_u16()"""
assert readnet_u16(b'123456', 2) == 13108
@pytest.mark.unitslow
@patch('serial.tools.list_ports.comports', return_value=[])
def test_findPorts_when_none_found(patch_comports):
"""Test findPorts()"""
assert not findPorts()
patch_comports.assert_called()
@pytest.mark.unitslow
def test_convert_mac_addr():
"""Test convert_mac_addr()"""
assert convert_mac_addr('/c0gFyhb') == 'fd:cd:20:17:28:5b'
assert convert_mac_addr('fd:cd:20:17:28:5b') == 'fd:cd:20:17:28:5b'
assert convert_mac_addr('') == ''
@pytest.mark.unit
def test_snake_to_camel():
"""Test snake_to_camel"""
assert snake_to_camel('') == ''
assert snake_to_camel('foo') == 'foo'
assert snake_to_camel('foo_bar') == 'fooBar'
assert snake_to_camel('fooBar') == 'fooBar'
@pytest.mark.unit
def test_camel_to_snake():
"""Test camel_to_snake"""
assert camel_to_snake('') == ''
assert camel_to_snake('foo') == 'foo'
assert camel_to_snake('Foo') == 'foo'
assert camel_to_snake('fooBar') == 'foo_bar'
assert camel_to_snake('fooBarBaz') == 'foo_bar_baz'

View File

@@ -17,61 +17,28 @@
import logging import logging
import threading import threading
import platform
from pubsub import pub from pubsub import pub
from pytap2 import TapDevice from pytap2 import TapDevice
from . import portnums_pb2 from meshtastic import portnums_pb2
from meshtastic.util import ipstr, readnet_u16
# A new non standard log level that is lower level than DEBUG from meshtastic.globals import Globals
LOG_TRACE = 5
# fixme - find a way to move onTunnelReceive inside of the class
tunnelInstance = None
"""A list of chatty UDP services we should never accidentally
forward to our slow network"""
udpBlacklist = {
1900, # SSDP
5353, # multicast DNS
}
"""A list of TCP services to block"""
tcpBlacklist = {}
"""A list of protocols we ignore"""
protocolBlacklist = {
0x02, # IGMP
0x80, # Service-Specific Connection-Oriented Protocol in a Multilink and Connectionless Environment
}
def hexstr(barray): def onTunnelReceive(packet, interface): # pylint: disable=W0613
"""Print a string of hex digits""" """Callback for received tunneled messages from mesh."""
return ":".join('{:02x}'.format(x) for x in barray) logging.debug(f'in onTunnelReceive()')
our_globals = Globals.getInstance()
tunnelInstance = our_globals.get_tunnelInstance()
def ipstr(barray):
"""Print a string of ip digits"""
return ".".join('{}'.format(x) for x in barray)
def readnet_u16(p, offset):
"""Read big endian u16 (network byte order)"""
return p[offset] * 256 + p[offset + 1]
def onTunnelReceive(packet, interface):
"""Callback for received tunneled messages from mesh
FIXME figure out how to do closures with methods in python"""
tunnelInstance.onReceive(packet) tunnelInstance.onReceive(packet)
class Tunnel: class Tunnel:
"""A TUN based IP tunnel over meshtastic""" """A TUN based IP tunnel over meshtastic"""
def __init__(self, iface, subnet=None, netmask="255.255.0.0"): def __init__(self, iface, subnet='10.115', netmask="255.255.0.0"):
""" """
Constructor Constructor
@@ -79,35 +46,69 @@ class Tunnel:
subnet is used to construct our network number (normally 10.115.x.x) subnet is used to construct our network number (normally 10.115.x.x)
""" """
if subnet is None: if not iface:
subnet = "10.115" raise Exception("Tunnel() must have a interface")
self.iface = iface self.iface = iface
self.subnetPrefix = subnet self.subnetPrefix = subnet
global tunnelInstance if platform.system() != 'Linux':
tunnelInstance = self raise Exception("Tunnel() can only be run instantiated on a Linux system")
our_globals = Globals.getInstance()
our_globals.set_tunnelInstance(self)
"""A list of chatty UDP services we should never accidentally
forward to our slow network"""
self.udpBlacklist = {
1900, # SSDP
5353, # multicast DNS
}
"""A list of TCP services to block"""
self.tcpBlacklist = {
5900, # VNC (Note: Only adding for testing purposes.)
}
"""A list of protocols we ignore"""
self.protocolBlacklist = {
0x02, # IGMP
0x80, # Service-Specific Connection-Oriented Protocol in a Multilink and Connectionless Environment
}
# A new non standard log level that is lower level than DEBUG
self.LOG_TRACE = 5
# TODO: check if root?
logging.info("Starting IP to mesh tunnel (you must be root for this *pre-alpha* "\ logging.info("Starting IP to mesh tunnel (you must be root for this *pre-alpha* "\
"feature to work). Mesh members:") "feature to work). Mesh members:")
pub.subscribe(onTunnelReceive, "meshtastic.receive.data.IP_TUNNEL_APP") pub.subscribe(onTunnelReceive, "meshtastic.receive.data.IP_TUNNEL_APP")
myAddr = self._nodeNumToIp(self.iface.myInfo.my_node_num) myAddr = self._nodeNumToIp(self.iface.myInfo.my_node_num)
for node in self.iface.nodes.values(): if self.iface.nodes:
nodeId = node["user"]["id"] for node in self.iface.nodes.values():
ip = self._nodeNumToIp(node["num"]) nodeId = node["user"]["id"]
logging.info(f"Node { nodeId } has IP address { ip }") ip = self._nodeNumToIp(node["num"])
logging.info(f"Node { nodeId } has IP address { ip }")
logging.debug("creating TUN device with MTU=200") logging.debug("creating TUN device with MTU=200")
# FIXME - figure out real max MTU, it should be 240 - the overhead bytes for SubPacket and Data # FIXME - figure out real max MTU, it should be 240 - the overhead bytes for SubPacket and Data
self.tun = TapDevice(name="mesh") self.tun = None
self.tun.up() if self.iface.noProto:
self.tun.ifconfig(address=myAddr, netmask=netmask, mtu=200) logging.warning(f"Not creating a TapDevice() because it is disabled by noProto")
logging.debug(f"starting TUN reader, our IP address is {myAddr}") else:
self._rxThread = threading.Thread( self.tun = TapDevice(name="mesh")
target=self.__tunReader, args=(), daemon=True) self.tun.up()
self._rxThread.start() self.tun.ifconfig(address=myAddr, netmask=netmask, mtu=200)
self._rxThread = None
if self.iface.noProto:
logging.warning(f"Not starting TUN reader because it is disabled by noProto")
else:
logging.debug(f"starting TUN reader, our IP address is {myAddr}")
self._rxThread = threading.Thread(target=self.__tunReader, args=(), daemon=True)
self._rxThread.start()
def onReceive(self, packet): def onReceive(self, packet):
"""onReceive""" """onReceive"""
@@ -115,12 +116,12 @@ class Tunnel:
if packet["from"] == self.iface.myInfo.my_node_num: if packet["from"] == self.iface.myInfo.my_node_num:
logging.debug("Ignoring message we sent") logging.debug("Ignoring message we sent")
else: else:
logging.debug( logging.debug(f"Received mesh tunnel message type={type(p)} len={len(p)}")
f"Received mesh tunnel message type={type(p)} len={len(p)}")
# we don't really need to check for filtering here (sender should have checked), # we don't really need to check for filtering here (sender should have checked),
# but this provides useful debug printing on types of packets received # but this provides useful debug printing on types of packets received
if not self._shouldFilterPacket(p): if not self.iface.noProto:
self.tun.write(p) if not self._shouldFilterPacket(p):
self.tun.write(p)
def _shouldFilterPacket(self, p): def _shouldFilterPacket(self, p):
"""Given a packet, decode it and return true if it should be ignored""" """Given a packet, decode it and return true if it should be ignored"""
@@ -129,10 +130,9 @@ class Tunnel:
destAddr = p[16:20] destAddr = p[16:20]
subheader = 20 subheader = 20
ignore = False # Assume we will be forwarding the packet ignore = False # Assume we will be forwarding the packet
if protocol in protocolBlacklist: if protocol in self.protocolBlacklist:
ignore = True ignore = True
logging.log( logging.log(self.LOG_TRACE, f"Ignoring blacklisted protocol 0x{protocol:02x}")
LOG_TRACE, f"Ignoring blacklisted protocol 0x{protocol:02x}")
elif protocol == 0x01: # ICMP elif protocol == 0x01: # ICMP
icmpType = p[20] icmpType = p[20]
icmpCode = p[21] icmpCode = p[21]
@@ -145,19 +145,17 @@ class Tunnel:
elif protocol == 0x11: # UDP elif protocol == 0x11: # UDP
srcport = readnet_u16(p, subheader) srcport = readnet_u16(p, subheader)
destport = readnet_u16(p, subheader + 2) destport = readnet_u16(p, subheader + 2)
if destport in udpBlacklist: if destport in self.udpBlacklist:
ignore = True ignore = True
logging.log( logging.log(self.LOG_TRACE, f"ignoring blacklisted UDP port {destport}")
LOG_TRACE, f"ignoring blacklisted UDP port {destport}")
else: else:
logging.debug( logging.debug(f"forwarding udp srcport={srcport}, destport={destport}")
f"forwarding udp srcport={srcport}, destport={destport}")
elif protocol == 0x06: # TCP elif protocol == 0x06: # TCP
srcport = readnet_u16(p, subheader) srcport = readnet_u16(p, subheader)
destport = readnet_u16(p, subheader + 2) destport = readnet_u16(p, subheader + 2)
if destport in tcpBlacklist: if destport in self.tcpBlacklist:
ignore = True ignore = True
logging.log(LOG_TRACE, f"ignoring blacklisted TCP port {destport}") logging.log(self.LOG_TRACE, f"ignoring blacklisted TCP port {destport}")
else: else:
logging.debug(f"forwarding tcp srcport={srcport}, destport={destport}") logging.debug(f"forwarding tcp srcport={srcport}, destport={destport}")
else: else:

View File

@@ -3,14 +3,18 @@
import traceback import traceback
from queue import Queue from queue import Queue
import os import os
import re
import sys import sys
import base64
import time import time
import platform import platform
import logging import logging
import threading import threading
import subprocess
import serial import serial
import serial.tools.list_ports import serial.tools.list_ports
import pkg_resources import pkg_resources
from meshtastic.supported_device import get_unique_vendor_ids, get_devices_with_vendor_id
"""Some devices such as a seger jlink we never want to accidentally open""" """Some devices such as a seger jlink we never want to accidentally open"""
blacklistVids = dict.fromkeys([0x1366]) blacklistVids = dict.fromkeys([0x1366])
@@ -169,8 +173,7 @@ class DeferredExecution():
o = self.queue.get() o = self.queue.get()
o() o()
except: except:
logging.error( logging.error(f"Unexpected error in deferred execution {sys.exc_info()[0]}")
f"Unexpected error in deferred execution {sys.exc_info()[0]}")
print(traceback.format_exc()) print(traceback.format_exc())
@@ -189,15 +192,191 @@ def support_info():
print('or wish to make feature requests, visit:') print('or wish to make feature requests, visit:')
print('https://github.com/meshtastic/Meshtastic-python/issues') print('https://github.com/meshtastic/Meshtastic-python/issues')
print('When adding an issue, be sure to include the following info:') print('When adding an issue, be sure to include the following info:')
print(' System: {0}'.format(platform.system())) print(f' System: {platform.system()}')
print(' Platform: {0}'.format(platform.platform())) print(f' Platform: {platform.platform()}')
print(' Release: {0}'.format(platform.uname().release)) print(f' Release: {platform.uname().release}')
print(' Machine: {0}'.format(platform.uname().machine)) print(f' Machine: {platform.uname().machine}')
print(' Encoding (stdin): {0}'.format(sys.stdin.encoding)) print(f' Encoding (stdin): {sys.stdin.encoding}')
print(' Encoding (stdout): {0}'.format(sys.stdout.encoding)) print(f' Encoding (stdout): {sys.stdout.encoding}')
print(' meshtastic: v{0}'.format(pkg_resources.require('meshtastic')[0].version)) the_version = pkg_resources.get_distribution("meshtastic").version
print(' Executable: {0}'.format(sys.argv[0])) print(f' meshtastic: v{the_version}')
print(' Python: {0} {1} {2}'.format(platform.python_version(), print(f' Executable: {sys.argv[0]}')
platform.python_implementation(), platform.python_compiler())) print(f' Python: {platform.python_version()} {platform.python_implementation()} {platform.python_compiler()}')
print('') print('')
print('Please add the output from the command: meshtastic --info') print('Please add the output from the command: meshtastic --info')
def remove_keys_from_dict(keys, adict):
"""Return a dictionary without some keys in it.
Will removed nested keys.
"""
for key in keys:
try:
del adict[key]
except:
pass
for val in adict.values():
if isinstance(val, dict):
remove_keys_from_dict(keys, val)
return adict
def hexstr(barray):
"""Print a string of hex digits"""
return ":".join(f'{x:02x}' for x in barray)
def ipstr(barray):
"""Print a string of ip digits"""
return ".".join(f'{x}' for x in barray)
def readnet_u16(p, offset):
"""Read big endian u16 (network byte order)"""
return p[offset] * 256 + p[offset + 1]
def convert_mac_addr(val):
"""Convert the base 64 encoded value to a mac address
val - base64 encoded value (ex: '/c0gFyhb'))
returns: a string formatted like a mac address (ex: 'fd:cd:20:17:28:5b')
"""
if not re.match("[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", val):
val_as_bytes = base64.b64decode(val)
return hexstr(val_as_bytes)
return val
def snake_to_camel(a_string):
"""convert snake_case to camelCase"""
# split underscore using split
temp = a_string.split('_')
# joining result
result = temp[0] + ''.join(ele.title() for ele in temp[1:])
return result
def camel_to_snake(a_string):
"""convert camelCase to snake_case"""
return ''.join(['_'+i.lower() if i.isupper() else i for i in a_string]).lstrip('_')
def detect_supported_devices():
"""detect supported devices"""
system = platform.system()
#print(f'system:{system}')
possible_devices = set()
if system == "Linux":
# if linux, run lsusb and list ports
# linux: use lsusb
# Bus 001 Device 091: ID 10c4:ea60 Silicon Labs CP210x UART Bridge
_, lsusb_output = subprocess.getstatusoutput('lsusb')
vids = get_unique_vendor_ids()
for vid in vids:
#print(f'looking for {vid}...')
search = f' {vid}:'
#print(f'search:"{search}"')
if re.search(search, lsusb_output, re.MULTILINE):
#print(f'Found vendor id that matches')
devices = get_devices_with_vendor_id(vid)
# check device id
for device in devices:
#print(f'device:{device} device.usb_product_id_in_hex:{device.usb_product_id_in_hex}')
if device.usb_product_id_in_hex:
search = f' {vid}:{device.usb_product_id_in_hex} '
#print(f'search:"{search}"')
if re.search(search, lsusb_output, re.MULTILINE):
# concatenate the devices with vendor id to possibles
possible_devices.add(device)
else:
# if there is a supported device witout a product id, then it
# might be a match... so, concatenate
possible_devices.add(device)
elif system == "Windows":
# if windows, run Get-PnpDevice
_, sp_output = subprocess.getstatusoutput('powershell.exe "Get-PnpDevice -PresentOnly | Format-List"')
#print(f'sp_output:{sp_output}')
vids = get_unique_vendor_ids()
for vid in vids:
#print(f'looking for {vid.upper()}...')
search = f'DeviceID.*{vid.upper()}&'
#search = f'{vid.upper()}'
#print(f'search:"{search}"')
if re.search(search, sp_output, re.MULTILINE):
#print(f'Found vendor id that matches')
devices = get_devices_with_vendor_id(vid)
# check device id
for device in devices:
#print(f'device:{device} device.usb_product_id_in_hex:{device.usb_product_id_in_hex}')
if device.usb_product_id_in_hex:
search = f'DeviceID.*{vid.upper()}&PID_{device.usb_product_id_in_hex.upper()}'
#print(f'search:"{search}"')
if re.search(search, sp_output, re.MULTILINE):
# concatenate the devices with vendor id to possibles
possible_devices.add(device)
# do a check to see if there is a Windows driver issue
if detect_windows_needs_driver(device, False):
print("WARNING: Need to install driver.")
else:
# if there is a supported device witout a product id, then it
# might be a match... so, concatenate
possible_devices.add(device)
elif system == "Darwin":
# run: system_profiler SPUSBDataType
# if mac air (eg: arm m1) do not know how to get info TODO: research
_, sp_output = subprocess.getstatusoutput('system_profiler SPUSBDataType')
vids = get_unique_vendor_ids()
for vid in vids:
#print(f'looking for {vid}...')
search = f'Vendor ID: 0x{vid}'
#print(f'search:"{search}"')
if re.search(search, sp_output, re.MULTILINE):
#print(f'Found vendor id that matches')
devices = get_devices_with_vendor_id(vid)
# check device id
for device in devices:
#print(f'device:{device} device.usb_product_id_in_hex:{device.usb_product_id_in_hex}')
if device.usb_product_id_in_hex:
search = f'Product ID: 0x{device.usb_product_id_in_hex}'
#print(f'search:"{search}"')
if re.search(search, sp_output, re.MULTILINE):
# concatenate the devices with vendor id to possibles
possible_devices.add(device)
else:
# if there is a supported device witout a product id, then it
# might be a match... so, concatenate
possible_devices.add(device)
return possible_devices
def detect_windows_needs_driver(sd, print_reason=False):
"""detect if Windows user needs to install driver for a supported device"""
need_to_install_driver = False
if sd:
system = platform.system()
#print(f'in detect_windows_needs_driver system:{system}')
if system == "Windows":
# if windows, see if we can find a DeviceId with the vendor id
# Get-PnpDevice | Where-Object{ ($_.DeviceId -like '*10C4*')} | Format-List
command = 'powershell.exe "Get-PnpDevice | Where-Object{ ($_.DeviceId -like '
command += f"'*{sd.usb_vendor_id_in_hex.upper()}*'"
command += ')} | Format-List"'
#print(f'command:{command}')
_, sp_output = subprocess.getstatusoutput(command)
#print(f'sp_output:{sp_output}')
search = f'CM_PROB_FAILED_INSTALL'
#print(f'search:"{search}"')
if re.search(search, sp_output, re.MULTILINE):
need_to_install_driver = True
# if the want to see the reason
if print_reason:
print(sp_output)
return need_to_install_driver

2
proto

Submodule proto updated: 1d3b4806ab...07ed86d8b4

View File

@@ -1,11 +1,15 @@
[pytest] [pytest]
addopts = -m "not int and not smoke1 and not smoke2 and not smokewifi and not examples" addopts = -m "not int and not smoke1 and not smoke2 and not smokewifi and not examples and not smokevirt"
filterwarnings =
ignore::DeprecationWarning
markers = markers =
unit: marks tests as unit tests unit: marks tests as unit tests
unitslow: marks slow unit tests unitslow: marks slow unit tests
int: marks tests as integration tests int: marks tests as integration tests
smokevirt: marks tests as smoke tests against virtual device
smoke1: runs smoke tests on a single device connected via USB smoke1: runs smoke tests on a single device connected via USB
smoke2: runs smoke tests on a two devices connected via USB smoke2: runs smoke tests on a two devices connected via USB
smokewifi: runs smoke test on an esp32 device setup with wifi smokewifi: runs smoke test on an esp32 device setup with wifi

View File

@@ -4,7 +4,6 @@ protobuf
dotmap dotmap
pexpect pexpect
pyqrcode pyqrcode
pygatt
tabulate tabulate
timeago timeago
webencodings webencodings
@@ -18,3 +17,4 @@ pyyaml
pytap2 pytap2
pdoc3 pdoc3
pypubsub pypubsub
pygatt; platform_system == "Linux"

View File

@@ -12,7 +12,7 @@ with open("README.md", "r") as fh:
# This call to setup() does all the work # This call to setup() does all the work
setup( setup(
name="meshtastic", name="meshtastic",
version="1.2.46", version="1.2.78",
description="Python API & client shell for talking to Meshtastic devices", description="Python API & client shell for talking to Meshtastic devices",
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
@@ -23,13 +23,18 @@ setup(
classifiers=[ classifiers=[
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
], ],
packages=["meshtastic"], packages=["meshtastic"],
include_package_data=True, include_package_data=True,
install_requires=["pyserial>=3.4", "protobuf>=3.13.0", install_requires=["pyserial>=3.4", "protobuf>=3.13.0",
"pypubsub>=4.0.3", "dotmap>=1.3.14", "pexpect>=4.6.0", "pyqrcode>=1.2.1", "pypubsub>=4.0.3", "dotmap>=1.3.14", "pexpect>=4.6.0", "pyqrcode>=1.2.1",
"pygatt>=4.0.5", "tabulate>=0.8.9", "timeago>=1.0.15", "pyyaml"], "tabulate>=0.8.9", "timeago>=1.0.15", "pyyaml",
"pygatt>=4.0.5 ; platform_system=='Linux'"],
extras_require={ extras_require={
'tunnel': ["pytap2>=2.0.0"] 'tunnel': ["pytap2>=2.0.0"]
}, },

7
standalone_readme.txt Normal file
View File

@@ -0,0 +1,7 @@
readme.txt for single standalone executable zip files that can be
downloaded from https://github.com/meshtastic/Meshtastic-python/releases
If you do not want to install python and/or the python libraries, you can download one of these
zip files to run the Meshtastic command line interface (CLI) as a standalone executable.
See https://meshtastic.org/docs/software/python/python-standalone for more info.

5
vercel.json Normal file
View File

@@ -0,0 +1,5 @@
{
"github": {
"silent": true
}
}