Compare commits

...

176 Commits

Author SHA1 Message Date
Jm Casler
28a72218fc bump to 1.2.46 2021-12-16 15:00:46 -05:00
mkinney
03374eb62b Merge pull request #165 from mkinney/fix_pyyaml_import
adding pyyaml to setup
2021-12-16 11:57:17 -08:00
Mike Kinney
95cd5f92c7 adding pyyaml to setup 2021-12-16 11:54:50 -08:00
Jm Casler
56dec2c52a Bump version to 1.2.45 2021-12-16 13:57:42 -05:00
mkinney
876a0a13dd Merge pull request #164 from mkinney/unit_tests
Unit tests
2021-12-16 09:23:50 -08:00
Mike Kinney
2fcfdeb04f minor formatting; added unit tests for Node() 2021-12-16 09:10:45 -08:00
Mike Kinney
34789899d8 add main unit test for --ble and --host; add test for --sendtext with --dest 2021-12-15 21:59:41 -08:00
Mike Kinney
6239585bf2 add main unit test for onReceive() and onConnection() 2021-12-15 21:46:40 -08:00
Mike Kinney
ef9a97d1f7 initial test_ble_interface; add another unit test 2021-12-15 16:15:46 -08:00
mkinney
182a9b89f3 Merge pull request #163 from mkinney/main_unit_tests2
remove print statements that were helpful during dev
2021-12-15 14:49:15 -08:00
Mike Kinney
712f1f5288 add main unit test --noproto, --seriallog, and a deprecated arg 2021-12-15 14:44:28 -08:00
Mike Kinney
5e1e968ccd add main unit test for --get 2021-12-15 14:21:37 -08:00
Mike Kinney
dae1f08632 remove lines that are not needed 2021-12-15 13:46:36 -08:00
Mike Kinney
d44e06420a add couple more tests for --pos-fields 2021-12-15 13:41:28 -08:00
Mike Kinney
8927fdcd1e figured out how to test --pos-fields 2021-12-15 12:54:59 -08:00
Mike Kinney
a248807165 remove print statements that were helpful during dev 2021-12-13 00:00:04 -08:00
mkinney
c7ef5828a5 Merge pull request #162 from mkinney/main_unit_tests
Main unit tests
2021-12-12 21:20:41 -08:00
Mike Kinney
0475fc9895 add main unit tests for all of the range settings like --ch-longslow 2021-12-12 21:06:50 -08:00
Mike Kinney
26b82303f8 refactor global_reset() to fixture 2021-12-12 20:30:40 -08:00
Mike Kinney
b4a4fed8d6 add unit tests for --ch-del 2021-12-12 20:03:43 -08:00
Mike Kinney
ed0bf6df4a add main unit tests for --ch-add 2021-12-12 19:52:36 -08:00
Mike Kinney
2f8f928465 add main unit test for --configure 2021-12-12 19:14:51 -08:00
Mike Kinney
a6dbdc29c1 figured out how to unit test --set with invalid field 2021-12-12 18:14:30 -08:00
Mike Kinney
4e3dcb6531 figured out how to unit test --set 2021-12-12 18:01:49 -08:00
Mike Kinney
d2a9b87968 add main unit test for --seturl 2021-12-12 17:05:38 -08:00
Mike Kinney
59e14d3741 add main unit tests for --set-team 2021-12-12 14:37:41 -08:00
Mike Kinney
f3490f80fa add tests for --setlat, --setlon, and --setalt 2021-12-12 14:08:09 -08:00
mkinney
0d367104f2 Merge pull request #161 from mkinney/work_on_channel
sort possible options/tests, figured out how to mock so --info can be…
2021-12-12 13:33:13 -08:00
Mike Kinney
a4b0278900 get rid of the --settime example 2021-12-12 13:22:41 -08:00
Mike Kinney
121f38a1b7 add unit test for --sendtext, --reboot, and --sendping 2021-12-12 11:03:05 -08:00
Mike Kinney
d2d7c9fb0e add unit test for --set-owner and --set-ham; refactor setting encryption after 2021-12-12 10:30:28 -08:00
Mike Kinney
34d31711ad add --qr and --nodes unit test 2021-12-12 09:27:51 -08:00
Mike Kinney
6d1121a751 so unit test will pass 2021-12-12 00:31:18 -08:00
Mike Kinney
0943ecab5b sort possible options/tests, figured out how to mock so --info can be tested 2021-12-12 00:22:17 -08:00
mkinney
c21d64be64 Merge pull request #158 from mkinney/max_retransmit_fix
add change as suggested by OverByThere
2021-12-11 17:56:34 -08:00
Mike Kinney
3cff4e2856 add change as suggested by OverByThere 2021-12-11 17:49:09 -08:00
Jm Casler
75db013df4 Merge pull request #157 from mkinney/ch_enable_work
Ch enable work
2021-12-10 22:41:44 -08:00
Mike Kinney
691801fca0 add more tests; refactor due to pylint suggestion 2021-12-10 20:02:01 -08:00
Mike Kinney
ce794ee035 re-worked the code for ch-set and ch-enable/ch-disable; updated tests 2021-12-10 19:10:09 -08:00
mkinney
8d45c60811 Merge pull request #156 from mkinney/testing_more_stuffs
Testing more stuffs
2021-12-10 10:40:17 -08:00
Mike Kinney
ada45ca860 make unit tests pass even if hardware is connected serially 2021-12-10 10:20:16 -08:00
Mike Kinney
b53521c029 add two more smoke1 tests 2021-12-10 10:17:37 -08:00
mkinney
0554872424 Merge pull request #154 from mkinney/work_on_unit_tests2
Work on unit tests2
2021-12-10 09:37:32 -08:00
Mike Kinney
e414e7fb35 temp removed import 2021-12-10 09:30:50 -08:00
Mike Kinney
10e97ee88c added --test2 unit test; wip on --info 2021-12-10 09:24:57 -08:00
Mike Kinney
80d13eac85 had to move test dir to tests because of pytest looking for __init__.py 2021-12-09 17:20:13 -08:00
mkinney
7ea6a85c31 Merge pull request #153 from mkinney/even_more_unit_tests
Even more unit tests
2021-12-09 16:33:30 -08:00
Mike Kinney
9f7a42b059 clean up --test tests 2021-12-09 16:30:23 -08:00
Mike Kinney
89058799c5 revert --test change; move tunnel import back; add encoding info to support 2021-12-09 16:21:13 -08:00
Mike Kinney
96a5f316ea refactor two more globals into Globals: channel_index and target_node 2021-12-09 13:41:24 -08:00
mkinney
6b85611067 Merge pull request #152 from mkinney/more_unit_testing
More unit testing
2021-12-09 12:39:08 -08:00
Mike Kinney
f587698d2a move functions from main to util and add unit tests 2021-12-09 12:26:17 -08:00
Mike Kinney
54fc1a92e7 refactor Settings() to Globals() 2021-12-09 11:46:33 -08:00
Mike Kinney
e7991cfc00 refactor globals to singleton 2021-12-09 11:26:24 -08:00
Jm Casler
c6409a699e updating proto submodule to latest 2021-12-09 11:23:26 -08:00
mkinney
8028e596bb Merge pull request #151 from mkinney/work_on_unit_tests
Work on unit tests
2021-12-09 09:18:43 -08:00
Mike Kinney
089d64105e fail build if pylint has issues 2021-12-09 09:14:38 -08:00
Mike Kinney
ddd34bfb6a ignore the test dir from code coverage 2021-12-09 01:08:54 -08:00
Mike Kinney
7b0fed0987 add test with no serial ports detected and with 2 serial ports detected 2021-12-09 00:56:26 -08:00
Mike Kinney
04534bb90e add initial unit test for SerialInterface 2021-12-09 00:44:26 -08:00
Mike Kinney
af6c54db08 change to relative importing 2021-12-09 00:26:28 -08:00
Mike Kinney
55e374c89b add some tests 2021-12-08 23:20:20 -08:00
Jm Casler
66d0dc48e0 updating proto submodule to latest 2021-12-08 23:15:10 -08:00
Jm Casler
e8fd40e145 updating proto submodule to latest 2021-12-08 18:36:44 -08:00
Jm Casler
a910499ae3 updating proto submodule to latest 2021-12-08 17:36:05 -08:00
mkinney
6c4bb4f7d2 Merge pull request #150 from mkinney/more_work_on_tests
More work on tests
2021-12-08 15:44:29 -08:00
Mike Kinney
4140d20f49 smoke test found an error that pylint suggested a change on; so reverted the change and disabled warning 2021-12-08 15:23:00 -08:00
Mike Kinney
5f653f266c fix broken tests 2021-12-08 15:13:14 -08:00
Mike Kinney
624c933dbf fix (or disable) pylint warnings 2021-12-08 15:00:57 -08:00
Mike Kinney
994b31c688 add more tests 2021-12-08 14:33:12 -08:00
mkinney
d80212cafe Merge pull request #149 from mkinney/more_testing
More testing
2021-12-08 13:13:23 -08:00
Mike Kinney
32cc2bb16d add more info about running smoke tests 2021-12-08 13:09:13 -08:00
Mike Kinney
f548d99776 add set-ham and pos-fields tests 2021-12-08 13:02:29 -08:00
Mike Kinney
6ef6c293e4 add more testing 2021-12-08 12:17:26 -08:00
Jm Casler
3a583f2697 Updated docs & protobufs 2021-12-07 15:12:21 -08:00
mkinney
6fffdbbc27 Merge pull request #147 from mkinney/improve_warnings_and_exits
improve the error messages; change from exceptions where it makes sense
2021-12-07 14:32:28 -08:00
Mike Kinney
c6e5c6a047 set team can occasionally reboot; seturl no longer fails silently 2021-12-07 14:28:51 -08:00
Mike Kinney
592be9a2a1 improve the error messages; change from exceptions where it makes sense 2021-12-07 14:19:54 -08:00
mkinney
2ee59c1e21 Merge pull request #145 from mkinney/add_support_option
Add support option
2021-12-07 13:37:35 -08:00
Jm Casler
c9d0b06e3c Merge branch 'master' of https://github.com/meshtastic/Meshtastic-python 2021-12-07 13:06:51 -08:00
Jm Casler
1d12d21e19 updating proto submodule to latest 2021-12-07 13:06:33 -08:00
Mike Kinney
65826d5cc2 add int test for --support 2021-12-07 09:45:01 -08:00
Mike Kinney
237b750497 fix pylint warnings; remove old doc 2021-12-07 09:42:27 -08:00
Mike Kinney
70099cf82d add --support option 2021-12-07 09:30:10 -08:00
Jm Casler
068b902f12 Merge pull request #144 from mkinney/minor_fixes
Minor fixes
2021-12-07 08:53:26 -08:00
Mike Kinney
bd037299b2 remove extra print statements 2021-12-07 08:45:33 -08:00
Mike Kinney
ec311216db fix formatting for running tests 2021-12-07 08:44:36 -08:00
Jm Casler
9fb83cec7c Merge pull request #140 from mkinney/work_on_unit_tests
Work on unit tests
2021-12-07 00:49:36 -08:00
Mike Kinney
51cbc307e5 got smoke tests passing 2021-12-07 00:36:45 -08:00
Mike Kinney
8ee4362bd8 add more command line args 2021-12-06 23:51:05 -08:00
Mike Kinney
c5500291ce minor fixes 2021-12-06 23:36:03 -08:00
Mike Kinney
0223b00226 Merge remote-tracking branch 'upstream/master' into work_on_unit_tests 2021-12-06 23:31:38 -08:00
Mike Kinney
4e79a1b3c4 fix empty lines 2021-12-06 23:31:26 -08:00
Mike Kinney
da4326e0cc uninstall system meshtastic 2021-12-06 23:27:08 -08:00
Mike Kinney
cb61a40767 more tweaks for ci 2021-12-06 23:24:13 -08:00
Mike Kinney
914c0fab8c tweak ci 2021-12-06 23:15:29 -08:00
Mike Kinney
da37d77d67 add pytap2 and a quick int check after installing on ci 2021-12-06 23:11:41 -08:00
Mike Kinney
6d2a187d38 fix pylint warnings 2021-12-06 23:01:34 -08:00
Mike Kinney
b7c155e710 pause just a little more after each command in case a reboot 2021-12-06 22:47:59 -08:00
Mike Kinney
6b66ce97c5 revert a recent change 2021-12-06 22:40:19 -08:00
Mike Kinney
b40eb08c5c refactor classes into respective files 2021-12-06 22:19:26 -08:00
Mike Kinney
26907107b3 move pskToString into util 2021-12-06 21:09:24 -08:00
Mike Kinney
5883a28690 migrate to a test__init__ file 2021-12-06 21:07:40 -08:00
Mike Kinney
cc9b6cd7b1 test MeshInterface 2021-12-06 21:01:21 -08:00
Jm Casler
6896e70c31 Updated docs for mt py 2021-12-06 17:48:18 -08:00
Jm Casler
163021de2a Add new channel configurations
Create new default channel configurations.

https://github.com/meshtastic/Meshtastic-device/issues/965
2021-12-06 16:33:56 -08:00
Jm Casler
b881ce4af5 updating proto submodule to latest 2021-12-06 16:09:03 -08:00
Ben Meadors
a07d3063da Added link to example_config.yml 2021-12-06 16:48:08 -06:00
mkinney
34553d2a25 Merge pull request #138 from mkinney/add_tests_for_yaml
add smoke test for configure via yaml file
2021-12-06 14:33:07 -08:00
Mike Kinney
516082e2d3 add smoke test for configure via yaml file 2021-12-06 14:23:02 -08:00
mkinney
291e235e34 Merge pull request #137 from mkinney/loosen_requirements
do not specify versions
2021-12-06 13:06:27 -08:00
Mike Kinney
547e38d248 do not specify versions 2021-12-06 12:57:00 -08:00
Jm Casler
b1e250ed26 Merge pull request #134 from mkinney/add_smoke1
added smoke tests on one device
2021-12-04 16:23:06 -08:00
Jm Casler
e25071a775 Merge pull request #135 from Hydra-Designs/master
Added very basic implementation of configure command for provisioning / setting preferences via yaml
2021-12-04 16:22:37 -08:00
thebentern
59ea299ad8 Added very basic implementation of configure yaml 2021-12-04 17:18:30 -06:00
Mike Kinney
f8ab4ffcfe add more smoke1 tests: debug, team, ch-longslow, ch-shortfast, ch-add, ch-del, ch-index 2021-12-03 11:38:50 -08:00
Mike Kinney
7dd67bb4b4 add qr, port, nodes and seriallog testing 2021-12-03 10:34:00 -08:00
Mike Kinney
82ae2c8e43 added smoke tests on one device 2021-12-03 09:50:48 -08:00
Jm Casler
dc66f514fd Bump to 1.2.44 2021-12-02 22:45:11 -08:00
Jm Casler
bda7386f48 updating proto submodule to latest 2021-12-02 22:42:37 -08:00
Jm Casler
17e8c35ed5 Merge pull request #132 from mkinney/add_requirements
add requirements.txt; update ci
2021-12-01 11:41:40 -08:00
Mike Kinney
2225ba35d7 oops. need to add test dir back for coverage 2021-12-01 11:00:10 -08:00
Mike Kinney
1874b0ce34 ignore some files during code coverage 2021-12-01 10:57:43 -08:00
Mike Kinney
ef811b5f7b add coverage 2021-12-01 10:34:48 -08:00
Mike Kinney
e7e094a14c add requirements.txt; update ci 2021-12-01 10:17:39 -08:00
Jm Casler
c824ddae1a Merge pull request #131 from mkinney/add_github_action_for_test
Add GitHub action for test
2021-12-01 10:01:24 -08:00
Mike Kinney
5deca40925 add pylint but do not stop on issues 2021-12-01 09:51:51 -08:00
Mike Kinney
c0a834b015 update badge 2021-12-01 09:42:24 -08:00
Mike Kinney
b270894751 try to add badge 2021-12-01 09:40:50 -08:00
Mike Kinney
a811a9dee6 add pytest to github action 2021-12-01 09:36:46 -08:00
Mike Kinney
e53fe421e8 ignore vi swap files 2021-12-01 09:34:10 -08:00
Jm Casler
1f150325d1 Merge pull request #129 from mkinney/add_pytest
initial pytest
2021-12-01 08:51:08 -08:00
Jm Casler
deb8b70184 Merge pull request #130 from a-f-G-U-C/team-pos-cli-202112
add cli support for team and position (fixed)
2021-12-01 08:50:49 -08:00
a-f-G-U-C
3c8cc08677 deconflict PR117 2021-12-01 13:00:52 +00:00
Mike Kinney
e5f0dc9c4b fix typo and add more info to readme about adding other types of tests 2021-12-01 00:26:57 -08:00
Mike Kinney
4def643803 break out unit tests so there is one assert per test 2021-12-01 00:24:53 -08:00
Mike Kinney
0473820ef4 add simple unit test 2021-12-01 00:13:57 -08:00
Mike Kinney
ee8c124651 initial pytest 2021-11-30 23:45:27 -08:00
Jm Casler
8fc7c2a0fe Merge branch 'master' of https://github.com/meshtastic/Meshtastic-python 2021-11-30 16:09:46 -08:00
Jm Casler
7735b4b16c Remove the -E files 2021-11-30 16:09:07 -08:00
Jm Casler
78043aa39c Merge pull request #121 from mkinney/channel_name_limit
do not allow channel names longer than 10 characters
2021-11-30 14:36:39 -08:00
Jm Casler
04c5cb1f47 Merge pull request #126 from mkinney/add_pylint
Add pylint
2021-11-30 14:33:57 -08:00
Mike Kinney
035ec09b20 fix quick and easy pylint fixes 2021-11-30 14:16:33 -08:00
Mike Kinney
ee01f735e3 initial pylintrc settings for a reasonable first pass 2021-11-30 13:20:12 -08:00
Jm Casler
dfe3989cd8 Added ignore of nanopb-0.4.4 2021-11-29 21:31:58 -08:00
Jm Casler
a04bd32b12 Bump version to 1.2.43 2021-11-29 21:22:59 -08:00
Jm Casler
86dede390a Update regen-protos.sh for #45 and #46 2021-11-29 21:18:41 -08:00
Jm Casler
75c347a8dc Bumped version to 1.2.41 2021-11-29 19:36:25 -08:00
Jm Casler
703b237367 updating proto submodule to latest 2021-11-29 18:59:27 -08:00
Mike Kinney
ccd6e7f4ed do not allow channel names longer than 10 characters 2021-11-21 22:52:16 -08:00
Jm Casler
eb13589336 Merge pull request #119 from mkinney/prerelease_fixes
Prerelease fixes
2021-11-21 21:55:17 -08:00
Mike Kinney
e738382924 fix --set-owner; comment out --settime 2021-11-21 21:13:15 -08:00
Mike Kinney
8f7b874e3d fix the set and unset rauter deprecated commands 2021-11-21 21:03:07 -08:00
Kevin Hester
55b9ab8bf1 Merge pull request #113 from rxt1077/master
Fixed order of have_tunnel check
2021-11-04 11:26:20 -07:00
Kevin Hester
69f53ff27b Merge pull request #115 from a-f-G-U-C/pb-61bc1d0
update protobufs to 61bc1d0
2021-11-04 11:25:48 -07:00
Kevin Hester
729e93668c Merge pull request #116 from rxt1077/proto-fix
reserved word fix in regen-protos.sh
2021-11-04 11:25:22 -07:00
Ryan Tolboom
271d4cb47a reserved word fix in regen-protos.sh 2021-11-04 13:32:46 -04:00
Ryan Tolboom
0f7bc8d5f6 Merge branch 'master' of github.com:rxt1077/Meshtastic-python 2021-11-03 22:57:10 -04:00
Ryan Tolboom
8ce025ed5e Added workaround for reserved word 'None' 2021-11-03 22:55:32 -04:00
a-f-G-U-C
866b633b02 update protobufs to 61bc1d0 2021-10-29 13:49:04 +00:00
Ryan Tolboom
a6d2ce11cc Fixed order of have_tunnel check 2021-10-20 21:38:19 -04:00
Kevin Hester
19613ff53b Merge pull request #108 from ChuckNorrison/master
return and print infos
2021-09-18 16:34:54 -07:00
Kevin Hester
8f266ce8ad Merge pull request #111 from jdstroy/master
Add optional channelIndex parameter to sendText convenience method
2021-09-18 16:34:17 -07:00
jdstroy
ea24bbcfaf Fix a typo in the README 2021-09-16 18:37:13 -04:00
jdstroy
bfdb260d3a Add channelIndex parameter to sendText 2021-09-16 18:37:12 -04:00
Kevin Hester
b009aa0bdf Merge pull request #106 from jdstroy/master
Wait indefinitely for packets when --tunnel is specified.
2021-09-06 17:48:42 -07:00
Kevin Hester
c37f41ad80 Merge pull request #107 from jdstroy/patch-1
Update to FAQ
2021-09-06 17:47:59 -07:00
ChuckNorrison
e117901964 return and print infos
return generated informations to the function and not just print out
2021-09-06 12:28:33 +02:00
jdstroy
907903d17f Update to FAQ
Clarify some things in README.md FAQ
2021-09-05 15:12:32 -07:00
jdstroy
65d767ba93 Wait indefinitely for packets when --tunnel is specified. 2021-09-05 07:59:05 -04:00
Kevin Hester
8380e57fa0 update protobufs 2021-09-01 10:26:37 -07:00
Kevin Hester
a65d502f14 Merge pull request #101 from srichs/correct-readme
Updated README and python-cmd-guide
2021-08-15 14:11:00 -07:00
srichs
223c67d2b4 Update python-cmd-guide.md 2021-08-13 00:49:21 -06:00
srichs
47a1b73834 Updated README to remove deprecated arguments 2021-08-13 00:44:23 -06:00
Kevin Hester
8649bc3655 Merge pull request #100 from sachaw/patch-1
Update README.md
2021-07-16 05:21:42 +08:00
Sacha Weatherstone
6f6f6e3a87 Update README.md 2021-07-09 14:28:26 +10:00
96 changed files with 35750 additions and 5672 deletions

2
.coveragerc Normal file
View File

@@ -0,0 +1,2 @@
[run]
omit = meshtastic/*_pb2.py,meshtastic/tests/*.py

34
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Linting and Tests
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Python 3
uses: actions/setup-python@v1
with:
python-version: 3.9
- name: Uninstall meshtastic
run: |
pip3 uninstall meshtastic
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip3 install -r requirements.txt
- name: Install meshtastic from local
run: |
pip3 install .
which meshtastic
meshtastic --version
- name: Run pylint
run: pylint meshtastic
- name: Run tests with pytest
run: pytest --cov=meshtastic

5
.gitignore vendored
View File

@@ -4,3 +4,8 @@ dist
*.egg-info
log_*
.eggs
nanopb-0.4.4
.*swp
.coverage
*.py-E
venv/

83
.pylintrc Normal file
View File

@@ -0,0 +1,83 @@
# pylint configuration file
#
# Note: "pylint --generate-rcfile" is helpful to see what values to add to this file
[MASTER]
# Add files or directories matching the regex patterns to the blacklist. The
# 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
[MESSAGES CONTROL]
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --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
[BASIC]
# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,ex,Run,_
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=150
# Maximum number of lines in a module
max-module-lines=1200
[REFACTORING]
# Maximum number of nested blocks for function / method body
max-nested-blocks=10
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,fixme,XXX,TODO
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=30
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=yes
[DESIGN]
# Maximum number of arguments for function / method.
max-args=10
# Maximum number of attributes for a class (see R0902).
max-attributes=20

129
README.md
View File

@@ -1,4 +1,7 @@
# 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)
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.
@@ -10,15 +13,19 @@ Installation is easily done through the Python package installer pip (note, you
- check that your computer has Python 3 installed.
- check that your computer has "pip3" installed, if not follow [this guide](https://www.makeuseof.com/tag/install-pip-for-python/).
- check that pytap2 is installed by pip3. If not, install it:
```
sudo pip3 install --upgrade pytap2
```
- install meshtastic:
```
sudo pip3 install --upgrade meshtastic
```
An example using Python 3 code to send a message to the mesh:
```
import meshtastic
interface = meshtastic.SerialInterface() # By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0
@@ -32,9 +39,10 @@ For the rough notes/implementation plan see [TODO](https://github.com/meshtastic
This pip package will also install a "meshtastic" command line executable, which displays packets sent over the network as JSON and lets you see serial debugging information from the meshtastic devices. The source code for this tool is also a good [example](https://github.com/meshtastic/Meshtastic-python/blob/master/meshtastic/__main__.py) of a 'complete' application that uses the meshtastic python API.
NOTE: This command is not run inside of python, you run it from your operating system shell prompt directly. If when you type "meshtastic" it doesn't find the command and you are using Windows: Check that the python "scripts" directory [is in your path](https://datatofish.com/add-python-to-windows-path/).
NOTE: This command is not run inside of python; you run it from your operating system shell prompt directly. If when you type "meshtastic" it doesn't find the command and you are using Windows: Check that the python "scripts" directory [is in your path](https://datatofish.com/add-python-to-windows-path/).
To display a (partial) list of the available commands:
```
meshtastic -h
```
@@ -69,6 +77,12 @@ Or to configure an ESP32 to run as a Wifi access point:
meshtastic --set wifi_ap_mode true --set wifi_ssid mywifissid --set wifi_password mywifipsw
```
Once an a node is connected to a Wi-Fi network, you can use the `--host` option to access it instead of using the USB serial bridge:
```
meshtastic --host Meshtastic-0123.lan --info --nodes
```
For a full list of preferences which can be set (and their documentation) see [here](https://meshtastic.org/docs/developers/protobufs/api#radioconfiguserpreferences).
### Changing channel settings
@@ -78,7 +92,7 @@ The channel settings can be changed similiarly. Either by using a standard (sha
The URL is constructed automatically based off of the current channel settings. So if you want to customize a channel you could do something like:
```
meshtastic --setchan name mychan --setchan channel_num 4 --info
meshtastic --ch-set name mychan --ch-set channel_num 4 --info
```
This will change some channel params and then show device info (which will include the current channel URL)
@@ -86,14 +100,20 @@ This will change some channel params and then show device info (which will inclu
You can even set the channel preshared key to a particular AES128 or AES256 sequence.
```
meshtastic --setchan psk 0x1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b --info
meshtastic --ch-set psk 0x1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b --info
```
Use "--setchan psk none" to turn off encryption.
Use "--ch-set psk none" to turn off encryption.
Use "--setchan psk random" will assign a new (high quality) random AES256 key to the primary channel (similar to what the Android app does when making new channels).
Use "--ch-set psk random" will assign a new (high quality) random AES256 key to the primary channel (similar to what the Android app does when making new channels).
Use "--setchan psk default" to restore the standard 'default' (minimally secure, because it is in the source code for anyone to read) AES128 key.
Use "--ch-set psk default" to restore the standard 'default' (minimally secure, because it is in the source code for anyone to read) AES128 key.
All "ch-set" commands will default to the primary channel at index 0, but can be applied to other channels with the "ch-index" parameter:
```
meshtastic --ch-index 1 --ch-set name mychan --ch-set channel_num 4 --info
```
## Ham radio support
@@ -106,6 +126,15 @@ Setting HAM ID to KI1345 and turning off encryption
Writing modified channels to device
```
## Changing multiple settings from a yaml file
You can put parameters into a yaml file to update multiple values. See the [example_config.yaml](example_config.yaml).
This is how you might call it:
```
meshtastic --configure example_config.yaml
```
## FAQ/common problems
This is a collection of common questions and answers from our friendly forum.
@@ -113,10 +142,13 @@ This is a collection of common questions and answers from our friendly forum.
### [Permission denied: /dev/ttyUSB0](https://meshtastic.discourse.group/t/question-on-permission-denied-dev-ttyusb0/590/3?u=geeksville)
This indicates an OS permission problem for access by your user to the USB serial port. Typically this is fixed by the following.
```
sudo usermod -a -G dialout <username>
sudo usermod -a -G dialout $USER
```
This adds the "dialout" group to your user. You'll need to obtain a new login session (for example, by logging out and logging back in) for the group change (and thus permission change) to take effect.
## Mac OS Big Sur
There is a problem with Big Sur and pyserial. The workaround is to install a newer version of pyserial:
@@ -125,14 +157,93 @@ There is a problem with Big Sur and pyserial. The workaround is to install a new
pip3 install -U --pre pyserial
```
Afterwards you can use the meshatstic python client again on MacOS.
Afterwards you can use the Meshtastic python client again on MacOS.
## A note to developers of this lib
We use the visual-studio-code default python formatting conventions (autopep8). So if you use that IDE you should be able to use "Format Document" and not generate unrelated diffs. If you use some other editor, please don't change formatting on lines you haven't changed.
If you need to build a new release you'll need:
```
apt install pandoc
sudo pip3 install markdown pdoc3 webencodings pyparsing twine autopep8
sudo pip3 install markdown pdoc3 webencodings pyparsing twine autopep8 pylint pytest pytest-cov
```
For development, you will probably want to run:
```
pip3 install -r requirements.txt
```
To lint, run:
```
pylint meshtastic
```
To test, first install this code locally, then run pytest:
```
pip3 install .
pytest
```
Possible options for testing:
* For more verbosity, add "-v" or even "-vv" like this:
```
pytest -vv
```
* To run just unit tests:
```
pytest
# or (more verbosely)
pytest -m unit
```
* To run just integration tests:
```
pytest -m int
```
* To run the smoke test with only one device connected serially (aka smoke1):
```
pytest -m smoke1
```
CAUTION: Running smoke1 will reset values on the device, including the region to 1 (US).
Be sure to hit the reset button on the device after the test is completed.
* To run the smoke test with only two device connected serially (aka smoke2):
```
pytest -m smoke2
```
* To run the wifi smoke test:
```
pytest -m smokewifi
```
* To run a specific test:
```
pytest -msmoke1 meshtastic/tests/test_smoke1.py::test_smoke1_info
# or to run a specific smoke2 test
pytest -m smoke2 meshtastic/tests/test_smoke2.py::test_smoke2_info
# or to run a specific smoke_wifi test
pytest -m smokewifi meshtastic/tests/test_smoke_wifi.py::test_smokewifi_info
```
* To add another classification of tests such as "unit" or "smoke1", see [pytest.ini](pytest.ini).
* To see the unit test code coverage:
```
pytest --cov=meshtastic
# or if want html coverage report
pytest --cov-report html --cov=meshtastic
```

View File

@@ -1,21 +1,22 @@
set -e
# You may consider running: "pytest -m smoke1" instead of this test.
echo "Running (crude) prerelease tests to verify sanity"
echo running hello
python3 tests/hello_world.py
# bin/run.sh --help
echo toggling router
bin/run.sh --set-router
bin/run.sh --unset-router
bin/run.sh --set is_router true
bin/run.sh --set is_router false
# TODO: This does not seem to work.
echo setting channel
bin/run.sh --seturl "https://www.meshtastic.org/c/#GAMiENTxuzogKQdZ8Lz_q89Oab8qB0RlZmF1bHQ="
echo setting time
bin/run.sh --settime
echo setting owner
bin/run.sh --setowner "Test Build"
bin/run.sh --set-owner "Test Build"
echo setting position
bin/run.sh --setlat 32.7767 --setlon -96.7970 --setalt 1337
echo dumping info
bin/run.sh --info
echo sending closing message
bin/run.sh --sendtext "Sanity complete"
bin/run.sh --sendtext "Sanity complete"

View File

@@ -1,8 +1,15 @@
#!/bin/bash
protoc -I=proto --python_out meshtastic `ls proto/*.proto`
./nanopb-0.4.4/generator-bin/protoc -I=proto --python_out meshtastic `ls proto/*.proto`
# workaround for import bug in protoc https://github.com/protocolbuffers/protobuf/issues/1491#issuecomment-690618628
sed -i -E 's/^import.*_pb2/from . \0/' meshtastic/*.py
if [[ $OSTYPE == 'darwin'* ]]; then
sed -i -E 's/^\(import.*_pb2\)/from . \1/' meshtastic/*.py
else
sed -i -E 's/^import.*_pb2/from . \0/' meshtastic/*.py
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

@@ -3,9 +3,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.9.2" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.admin_pb2 API documentation</title>
<meta name="description" content="Generated protocol buffer code." />
<meta name="description" content="" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
@@ -22,7 +22,6 @@
<h1 class="title">Module <code>meshtastic.admin_pb2</code></h1>
</header>
<section id="section-intro">
<p>Generated protocol buffer code.</p>
<details class="source">
<summary>
<span>Expand source code</span>
@@ -30,7 +29,7 @@
<pre><code class="python"># -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: admin.proto
&#34;&#34;&#34;Generated protocol buffer code.&#34;&#34;&#34;
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
@@ -40,9 +39,9 @@ from google.protobuf import symbol_database as _symbol_database
_sym_db = _symbol_database.Default()
from . import channel_pb2 as channel__pb2
from . import mesh_pb2 as mesh__pb2
from . import radioconfig_pb2 as radioconfig__pb2
from . import channel_pb2 as channel__pb2
DESCRIPTOR = _descriptor.FileDescriptor(
@@ -50,10 +49,9 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package=&#39;&#39;,
syntax=&#39;proto3&#39;,
serialized_options=b&#39;\n\023com.geeksville.meshB\013AdminProtosH\003Z!github.com/meshtastic/gomeshproto&#39;,
create_key=_descriptor._internal_create_key,
serialized_pb=b&#39;\n\x0b\x61\x64min.proto\x1a\nmesh.proto\x1a\x11radioconfig.proto\x1a\rchannel.proto\&#34;\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\&#34; \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&#39;
serialized_pb=b&#39;\n\x0b\x61\x64min.proto\x1a\rchannel.proto\x1a\nmesh.proto\x1a\x11radioconfig.proto\&#34;\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\&#34; \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&#39;
,
dependencies=[mesh__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,channel__pb2.DESCRIPTOR,])
dependencies=[channel__pb2.DESCRIPTOR,mesh__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,])
@@ -64,7 +62,6 @@ _ADMINMESSAGE = _descriptor.Descriptor(
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name=&#39;set_radio&#39;, full_name=&#39;AdminMessage.set_radio&#39;, index=0,
@@ -72,77 +69,77 @@ _ADMINMESSAGE = _descriptor.Descriptor(
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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;set_owner&#39;, full_name=&#39;AdminMessage.set_owner&#39;, 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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;set_channel&#39;, full_name=&#39;AdminMessage.set_channel&#39;, 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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;get_radio_request&#39;, full_name=&#39;AdminMessage.get_radio_request&#39;, 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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;get_radio_response&#39;, full_name=&#39;AdminMessage.get_radio_response&#39;, 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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;get_channel_request&#39;, full_name=&#39;AdminMessage.get_channel_request&#39;, 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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;get_channel_response&#39;, full_name=&#39;AdminMessage.get_channel_response&#39;, 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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;confirm_set_channel&#39;, full_name=&#39;AdminMessage.confirm_set_channel&#39;, 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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;confirm_set_radio&#39;, full_name=&#39;AdminMessage.confirm_set_radio&#39;, 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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;exit_simulator&#39;, full_name=&#39;AdminMessage.exit_simulator&#39;, 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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;reboot_seconds&#39;, full_name=&#39;AdminMessage.reboot_seconds&#39;, 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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
@@ -156,9 +153,7 @@ _ADMINMESSAGE = _descriptor.Descriptor(
oneofs=[
_descriptor.OneofDescriptor(
name=&#39;variant&#39;, full_name=&#39;AdminMessage.variant&#39;,
index=0, containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[]),
index=0, containing_type=None, fields=[]),
],
serialized_start=62,
serialized_end=441,
@@ -228,67 +223,756 @@ DESCRIPTOR._options = None
<dl>
<dt id="meshtastic.admin_pb2.AdminMessage"><code class="flex name class">
<span>class <span class="ident">AdminMessage</span></span>
<span>(</span><span>*args, **kwargs)</span>
<span>(</span><span>**kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<div class="desc"><p>Abstract base class for protocol messages.</p>
<p>Protocol message classes are almost always generated by the protocol
compiler.
These generated types subclass Message and implement the methods
shown below.</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google.protobuf.pyext._message.CMessage</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="meshtastic.admin_pb2.AdminMessage.CONFIRM_SET_CHANNEL_FIELD_NUMBER"><code class="name">var <span class="ident">CONFIRM_SET_CHANNEL_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.CONFIRM_SET_RADIO_FIELD_NUMBER"><code class="name">var <span class="ident">CONFIRM_SET_RADIO_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.EXIT_SIMULATOR_FIELD_NUMBER"><code class="name">var <span class="ident">EXIT_SIMULATOR_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.GET_CHANNEL_REQUEST_FIELD_NUMBER"><code class="name">var <span class="ident">GET_CHANNEL_REQUEST_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.GET_CHANNEL_RESPONSE_FIELD_NUMBER"><code class="name">var <span class="ident">GET_CHANNEL_RESPONSE_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.GET_RADIO_REQUEST_FIELD_NUMBER"><code class="name">var <span class="ident">GET_RADIO_REQUEST_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.GET_RADIO_RESPONSE_FIELD_NUMBER"><code class="name">var <span class="ident">GET_RADIO_RESPONSE_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.REBOOT_SECONDS_FIELD_NUMBER"><code class="name">var <span class="ident">REBOOT_SECONDS_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.SET_CHANNEL_FIELD_NUMBER"><code class="name">var <span class="ident">SET_CHANNEL_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.SET_OWNER_FIELD_NUMBER"><code class="name">var <span class="ident">SET_OWNER_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.SET_RADIO_FIELD_NUMBER"><code class="name">var <span class="ident">SET_RADIO_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
<h3>Static methods</h3>
<dl>
<dt id="meshtastic.admin_pb2.AdminMessage.FromString"><code class="name flex">
<span>def <span class="ident">FromString</span></span>(<span>s)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def FromString(s):
message = cls()
message.MergeFromString(s)
return message</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.RegisterExtension"><code class="name flex">
<span>def <span class="ident">RegisterExtension</span></span>(<span>extension_handle)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def RegisterExtension(extension_handle):
extension_handle.containing_type = cls.DESCRIPTOR
# TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
# pylint: disable=protected-access
cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
_AttachFieldHelpers(cls, extension_handle)</code></pre>
</details>
</dd>
</dl>
<h3>Instance variables</h3>
<dl>
<dt id="meshtastic.admin_pb2.AdminMessage.confirm_set_channel"><code class="name">var <span class="ident">confirm_set_channel</span></code></dt>
<dd>
<div class="desc"><p>Field AdminMessage.confirm_set_channel</p></div>
<div class="desc"><p>Getter for confirm_set_channel.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.confirm_set_radio"><code class="name">var <span class="ident">confirm_set_radio</span></code></dt>
<dd>
<div class="desc"><p>Field AdminMessage.confirm_set_radio</p></div>
<div class="desc"><p>Getter for confirm_set_radio.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.exit_simulator"><code class="name">var <span class="ident">exit_simulator</span></code></dt>
<dd>
<div class="desc"><p>Field AdminMessage.exit_simulator</p></div>
<div class="desc"><p>Getter for exit_simulator.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.get_channel_request"><code class="name">var <span class="ident">get_channel_request</span></code></dt>
<dd>
<div class="desc"><p>Field AdminMessage.get_channel_request</p></div>
<div class="desc"><p>Getter for get_channel_request.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.get_channel_response"><code class="name">var <span class="ident">get_channel_response</span></code></dt>
<dd>
<div class="desc"><p>Field AdminMessage.get_channel_response</p></div>
<div class="desc"><p>Getter for get_channel_response.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
field_value = self._fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
# Atomically check if another thread has preempted us and, if not, swap
# in the new object we just created. If someone has preempted us, we
# take that object and discard ours.
# WARNING: We are relying on setdefault() being atomic. This is true
# in CPython but we haven&#39;t investigated others. This warning appears
# in several other locations in this file.
field_value = self._fields.setdefault(field, field_value)
return field_value</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.get_radio_request"><code class="name">var <span class="ident">get_radio_request</span></code></dt>
<dd>
<div class="desc"><p>Field AdminMessage.get_radio_request</p></div>
<div class="desc"><p>Getter for get_radio_request.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.get_radio_response"><code class="name">var <span class="ident">get_radio_response</span></code></dt>
<dd>
<div class="desc"><p>Field AdminMessage.get_radio_response</p></div>
<div class="desc"><p>Getter for get_radio_response.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
field_value = self._fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
# Atomically check if another thread has preempted us and, if not, swap
# in the new object we just created. If someone has preempted us, we
# take that object and discard ours.
# WARNING: We are relying on setdefault() being atomic. This is true
# in CPython but we haven&#39;t investigated others. This warning appears
# in several other locations in this file.
field_value = self._fields.setdefault(field, field_value)
return field_value</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.reboot_seconds"><code class="name">var <span class="ident">reboot_seconds</span></code></dt>
<dd>
<div class="desc"><p>Field AdminMessage.reboot_seconds</p></div>
<div class="desc"><p>Getter for reboot_seconds.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.set_channel"><code class="name">var <span class="ident">set_channel</span></code></dt>
<dd>
<div class="desc"><p>Field AdminMessage.set_channel</p></div>
<div class="desc"><p>Getter for set_channel.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
field_value = self._fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
# Atomically check if another thread has preempted us and, if not, swap
# in the new object we just created. If someone has preempted us, we
# take that object and discard ours.
# WARNING: We are relying on setdefault() being atomic. This is true
# in CPython but we haven&#39;t investigated others. This warning appears
# in several other locations in this file.
field_value = self._fields.setdefault(field, field_value)
return field_value</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.set_owner"><code class="name">var <span class="ident">set_owner</span></code></dt>
<dd>
<div class="desc"><p>Field AdminMessage.set_owner</p></div>
<div class="desc"><p>Getter for set_owner.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
field_value = self._fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
# Atomically check if another thread has preempted us and, if not, swap
# in the new object we just created. If someone has preempted us, we
# take that object and discard ours.
# WARNING: We are relying on setdefault() being atomic. This is true
# in CPython but we haven&#39;t investigated others. This warning appears
# in several other locations in this file.
field_value = self._fields.setdefault(field, field_value)
return field_value</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.set_radio"><code class="name">var <span class="ident">set_radio</span></code></dt>
<dd>
<div class="desc"><p>Field AdminMessage.set_radio</p></div>
<div class="desc"><p>Getter for set_radio.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
field_value = self._fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
# Atomically check if another thread has preempted us and, if not, swap
# in the new object we just created. If someone has preempted us, we
# take that object and discard ours.
# WARNING: We are relying on setdefault() being atomic. This is true
# in CPython but we haven&#39;t investigated others. This warning appears
# in several other locations in this file.
field_value = self._fields.setdefault(field, field_value)
return field_value</code></pre>
</details>
</dd>
</dl>
<h3>Methods</h3>
<dl>
<dt id="meshtastic.admin_pb2.AdminMessage.ByteSize"><code class="name flex">
<span>def <span class="ident">ByteSize</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ByteSize(self):
if not self._cached_byte_size_dirty:
return self._cached_byte_size
size = 0
descriptor = self.DESCRIPTOR
if descriptor.GetOptions().map_entry:
# Fields of map entry should always be serialized.
size = descriptor.fields_by_name[&#39;key&#39;]._sizer(self.key)
size += descriptor.fields_by_name[&#39;value&#39;]._sizer(self.value)
else:
for field_descriptor, field_value in self.ListFields():
size += field_descriptor._sizer(field_value)
for tag_bytes, value_bytes in self._unknown_fields:
size += len(tag_bytes) + len(value_bytes)
self._cached_byte_size = size
self._cached_byte_size_dirty = False
self._listener_for_children.dirty = False
return size</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.Clear"><code class="name flex">
<span>def <span class="ident">Clear</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def _Clear(self):
# Clear fields.
self._fields = {}
self._unknown_fields = ()
# pylint: disable=protected-access
if self._unknown_field_set is not None:
self._unknown_field_set._clear()
self._unknown_field_set = None
self._oneofs = {}
self._Modified()</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.ClearField"><code class="name flex">
<span>def <span class="ident">ClearField</span></span>(<span>self, field_name)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ClearField(self, field_name):
try:
field = message_descriptor.fields_by_name[field_name]
except KeyError:
try:
field = message_descriptor.oneofs_by_name[field_name]
if field in self._oneofs:
field = self._oneofs[field]
else:
return
except KeyError:
raise ValueError(&#39;Protocol message %s has no &#34;%s&#34; field.&#39; %
(message_descriptor.name, field_name))
if field in self._fields:
# To match the C++ implementation, we need to invalidate iterators
# for map fields when ClearField() happens.
if hasattr(self._fields[field], &#39;InvalidateIterators&#39;):
self._fields[field].InvalidateIterators()
# Note: If the field is a sub-message, its listener will still point
# at us. That&#39;s fine, because the worst than can happen is that it
# will call _Modified() and invalidate our byte size. Big deal.
del self._fields[field]
if self._oneofs.get(field.containing_oneof, None) is field:
del self._oneofs[field.containing_oneof]
# Always call _Modified() -- even if nothing was changed, this is
# a mutating method, and thus calling it should cause the field to become
# present in the parent message.
self._Modified()</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.DiscardUnknownFields"><code class="name flex">
<span>def <span class="ident">DiscardUnknownFields</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def _DiscardUnknownFields(self):
self._unknown_fields = []
self._unknown_field_set = None # pylint: disable=protected-access
for field, value in self.ListFields():
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
if _IsMapField(field):
if _IsMessageMapField(field):
for key in value:
value[key].DiscardUnknownFields()
elif field.label == _FieldDescriptor.LABEL_REPEATED:
for sub_message in value:
sub_message.DiscardUnknownFields()
else:
value.DiscardUnknownFields()</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.FindInitializationErrors"><code class="name flex">
<span>def <span class="ident">FindInitializationErrors</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Finds required fields which are not initialized.</p>
<h2 id="returns">Returns</h2>
<p>A list of strings.
Each string is a path to an uninitialized field from
the top-level message, e.g. "foo.bar[5].baz".</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def FindInitializationErrors(self):
&#34;&#34;&#34;Finds required fields which are not initialized.
Returns:
A list of strings. Each string is a path to an uninitialized field from
the top-level message, e.g. &#34;foo.bar[5].baz&#34;.
&#34;&#34;&#34;
errors = [] # simplify things
for field in required_fields:
if not self.HasField(field.name):
errors.append(field.name)
for field, value in self.ListFields():
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
if field.is_extension:
name = &#39;(%s)&#39; % field.full_name
else:
name = field.name
if _IsMapField(field):
if _IsMessageMapField(field):
for key in value:
element = value[key]
prefix = &#39;%s[%s].&#39; % (name, key)
sub_errors = element.FindInitializationErrors()
errors += [prefix + error for error in sub_errors]
else:
# ScalarMaps can&#39;t have any initialization errors.
pass
elif field.label == _FieldDescriptor.LABEL_REPEATED:
for i in range(len(value)):
element = value[i]
prefix = &#39;%s[%d].&#39; % (name, i)
sub_errors = element.FindInitializationErrors()
errors += [prefix + error for error in sub_errors]
else:
prefix = name + &#39;.&#39;
sub_errors = value.FindInitializationErrors()
errors += [prefix + error for error in sub_errors]
return errors</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.HasField"><code class="name flex">
<span>def <span class="ident">HasField</span></span>(<span>self, field_name)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def HasField(self, field_name):
try:
field = hassable_fields[field_name]
except KeyError:
raise ValueError(error_msg % (message_descriptor.full_name, field_name))
if isinstance(field, descriptor_mod.OneofDescriptor):
try:
return HasField(self, self._oneofs[field].name)
except KeyError:
return False
else:
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
value = self._fields.get(field)
return value is not None and value._is_present_in_parent
else:
return field in self._fields</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.IsInitialized"><code class="name flex">
<span>def <span class="ident">IsInitialized</span></span>(<span>self, errors=None)</span>
</code></dt>
<dd>
<div class="desc"><p>Checks if all required fields of a message are set.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>errors</code></strong></dt>
<dd>A list which, if provided, will be populated with the field
paths of all missing required fields.</dd>
</dl>
<h2 id="returns">Returns</h2>
<p>True iff the specified message has all required fields set.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def IsInitialized(self, errors=None):
&#34;&#34;&#34;Checks if all required fields of a message are set.
Args:
errors: A list which, if provided, will be populated with the field
paths of all missing required fields.
Returns:
True iff the specified message has all required fields set.
&#34;&#34;&#34;
# Performance is critical so we avoid HasField() and ListFields().
for field in required_fields:
if (field not in self._fields or
(field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
not self._fields[field]._is_present_in_parent)):
if errors is not None:
errors.extend(self.FindInitializationErrors())
return False
for field, value in list(self._fields.items()): # dict can change size!
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
if field.label == _FieldDescriptor.LABEL_REPEATED:
if (field.message_type.has_options and
field.message_type.GetOptions().map_entry):
continue
for element in value:
if not element.IsInitialized():
if errors is not None:
errors.extend(self.FindInitializationErrors())
return False
elif value._is_present_in_parent and not value.IsInitialized():
if errors is not None:
errors.extend(self.FindInitializationErrors())
return False
return True</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.ListFields"><code class="name flex">
<span>def <span class="ident">ListFields</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ListFields(self):
all_fields = [item for item in self._fields.items() if _IsPresent(item)]
all_fields.sort(key = lambda item: item[0].number)
return all_fields</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.MergeFrom"><code class="name flex">
<span>def <span class="ident">MergeFrom</span></span>(<span>self, msg)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def MergeFrom(self, msg):
if not isinstance(msg, cls):
raise TypeError(
&#39;Parameter to MergeFrom() must be instance of same class: &#39;
&#39;expected %s got %s.&#39; % (_FullyQualifiedClassName(cls),
_FullyQualifiedClassName(msg.__class__)))
assert msg is not self
self._Modified()
fields = self._fields
for field, value in msg._fields.items():
if field.label == LABEL_REPEATED:
field_value = fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
fields[field] = field_value
field_value.MergeFrom(value)
elif field.cpp_type == CPPTYPE_MESSAGE:
if value._is_present_in_parent:
field_value = fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
fields[field] = field_value
field_value.MergeFrom(value)
else:
self._fields[field] = value
if field.containing_oneof:
self._UpdateOneofState(field)
if msg._unknown_fields:
if not self._unknown_fields:
self._unknown_fields = []
self._unknown_fields.extend(msg._unknown_fields)
# pylint: disable=protected-access
if self._unknown_field_set is None:
self._unknown_field_set = containers.UnknownFieldSet()
self._unknown_field_set._extend(msg._unknown_field_set)</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.MergeFromString"><code class="name flex">
<span>def <span class="ident">MergeFromString</span></span>(<span>self, serialized)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def MergeFromString(self, serialized):
serialized = memoryview(serialized)
length = len(serialized)
try:
if self._InternalParse(serialized, 0, length) != length:
# The only reason _InternalParse would return early is if it
# encountered an end-group tag.
raise message_mod.DecodeError(&#39;Unexpected end-group tag.&#39;)
except (IndexError, TypeError):
# Now ord(buf[p:p+1]) == ord(&#39;&#39;) gets TypeError.
raise message_mod.DecodeError(&#39;Truncated message.&#39;)
except struct.error as e:
raise message_mod.DecodeError(e)
return length # Return this for legacy reasons.</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.SerializePartialToString"><code class="name flex">
<span>def <span class="ident">SerializePartialToString</span></span>(<span>self, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def SerializePartialToString(self, **kwargs):
out = BytesIO()
self._InternalSerialize(out.write, **kwargs)
return out.getvalue()</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.SerializeToString"><code class="name flex">
<span>def <span class="ident">SerializeToString</span></span>(<span>self, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def SerializeToString(self, **kwargs):
# Check if the message has all of its required fields set.
if not self.IsInitialized():
raise message_mod.EncodeError(
&#39;Message %s is missing required fields: %s&#39; % (
self.DESCRIPTOR.full_name, &#39;,&#39;.join(self.FindInitializationErrors())))
return self.SerializePartialToString(**kwargs)</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.SetInParent"><code class="name flex">
<span>def <span class="ident">SetInParent</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Sets the _cached_byte_size_dirty bit to true,
and propagates this to our listener iff this was a state change.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def Modified(self):
&#34;&#34;&#34;Sets the _cached_byte_size_dirty bit to true,
and propagates this to our listener iff this was a state change.
&#34;&#34;&#34;
# Note: Some callers check _cached_byte_size_dirty before calling
# _Modified() as an extra optimization. So, if this method is ever
# changed such that it does stuff even when _cached_byte_size_dirty is
# already true, the callers need to be updated.
if not self._cached_byte_size_dirty:
self._cached_byte_size_dirty = True
self._listener_for_children.dirty = True
self._is_present_in_parent = True
self._listener.Modified()</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.UnknownFields"><code class="name flex">
<span>def <span class="ident">UnknownFields</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def _UnknownFields(self):
if self._unknown_field_set is None: # pylint: disable=protected-access
# pylint: disable=protected-access
self._unknown_field_set = containers.UnknownFieldSet()
return self._unknown_field_set # pylint: disable=protected-access</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.WhichOneof"><code class="name flex">
<span>def <span class="ident">WhichOneof</span></span>(<span>self, oneof_name)</span>
</code></dt>
<dd>
<div class="desc"><p>Returns the name of the currently set field inside a oneof, or None.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def WhichOneof(self, oneof_name):
&#34;&#34;&#34;Returns the name of the currently set field inside a oneof, or None.&#34;&#34;&#34;
try:
field = message_descriptor.oneofs_by_name[oneof_name]
except KeyError:
raise ValueError(
&#39;Protocol message has no oneof &#34;%s&#34; field.&#39; % oneof_name)
nested_field = self._oneofs.get(field, None)
if nested_field is not None and self.HasField(nested_field.name):
return nested_field.name
else:
return None</code></pre>
</details>
</dd>
</dl>
</dd>
@@ -311,7 +995,35 @@ DESCRIPTOR._options = None
<li>
<h4><code><a title="meshtastic.admin_pb2.AdminMessage" href="#meshtastic.admin_pb2.AdminMessage">AdminMessage</a></code></h4>
<ul class="">
<li><code><a title="meshtastic.admin_pb2.AdminMessage.ByteSize" href="#meshtastic.admin_pb2.AdminMessage.ByteSize">ByteSize</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.CONFIRM_SET_CHANNEL_FIELD_NUMBER" href="#meshtastic.admin_pb2.AdminMessage.CONFIRM_SET_CHANNEL_FIELD_NUMBER">CONFIRM_SET_CHANNEL_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.CONFIRM_SET_RADIO_FIELD_NUMBER" href="#meshtastic.admin_pb2.AdminMessage.CONFIRM_SET_RADIO_FIELD_NUMBER">CONFIRM_SET_RADIO_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.Clear" href="#meshtastic.admin_pb2.AdminMessage.Clear">Clear</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.ClearField" href="#meshtastic.admin_pb2.AdminMessage.ClearField">ClearField</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.DESCRIPTOR" href="#meshtastic.admin_pb2.AdminMessage.DESCRIPTOR">DESCRIPTOR</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.DiscardUnknownFields" href="#meshtastic.admin_pb2.AdminMessage.DiscardUnknownFields">DiscardUnknownFields</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.EXIT_SIMULATOR_FIELD_NUMBER" href="#meshtastic.admin_pb2.AdminMessage.EXIT_SIMULATOR_FIELD_NUMBER">EXIT_SIMULATOR_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.FindInitializationErrors" href="#meshtastic.admin_pb2.AdminMessage.FindInitializationErrors">FindInitializationErrors</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.FromString" href="#meshtastic.admin_pb2.AdminMessage.FromString">FromString</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.GET_CHANNEL_REQUEST_FIELD_NUMBER" href="#meshtastic.admin_pb2.AdminMessage.GET_CHANNEL_REQUEST_FIELD_NUMBER">GET_CHANNEL_REQUEST_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.GET_CHANNEL_RESPONSE_FIELD_NUMBER" href="#meshtastic.admin_pb2.AdminMessage.GET_CHANNEL_RESPONSE_FIELD_NUMBER">GET_CHANNEL_RESPONSE_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.GET_RADIO_REQUEST_FIELD_NUMBER" href="#meshtastic.admin_pb2.AdminMessage.GET_RADIO_REQUEST_FIELD_NUMBER">GET_RADIO_REQUEST_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.GET_RADIO_RESPONSE_FIELD_NUMBER" href="#meshtastic.admin_pb2.AdminMessage.GET_RADIO_RESPONSE_FIELD_NUMBER">GET_RADIO_RESPONSE_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.HasField" href="#meshtastic.admin_pb2.AdminMessage.HasField">HasField</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.IsInitialized" href="#meshtastic.admin_pb2.AdminMessage.IsInitialized">IsInitialized</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.ListFields" href="#meshtastic.admin_pb2.AdminMessage.ListFields">ListFields</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.MergeFrom" href="#meshtastic.admin_pb2.AdminMessage.MergeFrom">MergeFrom</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.MergeFromString" href="#meshtastic.admin_pb2.AdminMessage.MergeFromString">MergeFromString</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.REBOOT_SECONDS_FIELD_NUMBER" href="#meshtastic.admin_pb2.AdminMessage.REBOOT_SECONDS_FIELD_NUMBER">REBOOT_SECONDS_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.RegisterExtension" href="#meshtastic.admin_pb2.AdminMessage.RegisterExtension">RegisterExtension</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.SET_CHANNEL_FIELD_NUMBER" href="#meshtastic.admin_pb2.AdminMessage.SET_CHANNEL_FIELD_NUMBER">SET_CHANNEL_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.SET_OWNER_FIELD_NUMBER" href="#meshtastic.admin_pb2.AdminMessage.SET_OWNER_FIELD_NUMBER">SET_OWNER_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.SET_RADIO_FIELD_NUMBER" href="#meshtastic.admin_pb2.AdminMessage.SET_RADIO_FIELD_NUMBER">SET_RADIO_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.SerializePartialToString" href="#meshtastic.admin_pb2.AdminMessage.SerializePartialToString">SerializePartialToString</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.SerializeToString" href="#meshtastic.admin_pb2.AdminMessage.SerializeToString">SerializeToString</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.SetInParent" href="#meshtastic.admin_pb2.AdminMessage.SetInParent">SetInParent</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.UnknownFields" href="#meshtastic.admin_pb2.AdminMessage.UnknownFields">UnknownFields</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.WhichOneof" href="#meshtastic.admin_pb2.AdminMessage.WhichOneof">WhichOneof</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.confirm_set_channel" href="#meshtastic.admin_pb2.AdminMessage.confirm_set_channel">confirm_set_channel</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.confirm_set_radio" href="#meshtastic.admin_pb2.AdminMessage.confirm_set_radio">confirm_set_radio</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.exit_simulator" href="#meshtastic.admin_pb2.AdminMessage.exit_simulator">exit_simulator</a></code></li>
@@ -331,7 +1043,7 @@ DESCRIPTOR._options = None
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc"><cite>pdoc</cite> 0.9.2</a>.</p>
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -3,9 +3,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.9.2" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.apponly_pb2 API documentation</title>
<meta name="description" content="Generated protocol buffer code." />
<meta name="description" content="" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
@@ -22,7 +22,6 @@
<h1 class="title">Module <code>meshtastic.apponly_pb2</code></h1>
</header>
<section id="section-intro">
<p>Generated protocol buffer code.</p>
<details class="source">
<summary>
<span>Expand source code</span>
@@ -30,7 +29,7 @@
<pre><code class="python"># -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: apponly.proto
&#34;&#34;&#34;Generated protocol buffer code.&#34;&#34;&#34;
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
@@ -48,7 +47,6 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package=&#39;&#39;,
syntax=&#39;proto3&#39;,
serialized_options=b&#39;\n\023com.geeksville.meshB\rAppOnlyProtosH\003Z!github.com/meshtastic/gomeshproto&#39;,
create_key=_descriptor._internal_create_key,
serialized_pb=b&#39;\n\rapponly.proto\x1a\rchannel.proto\&#34;0\n\nChannelSet\x12\&#34;\n\x08settings\x18\x01 \x03(\x0b\x32\x10.ChannelSettingsBI\n\x13\x63om.geeksville.meshB\rAppOnlyProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3&#39;
,
dependencies=[channel__pb2.DESCRIPTOR,])
@@ -62,7 +60,6 @@ _CHANNELSET = _descriptor.Descriptor(
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name=&#39;settings&#39;, full_name=&#39;ChannelSet.settings&#39;, index=0,
@@ -70,7 +67,7 @@ _CHANNELSET = _descriptor.Descriptor(
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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
@@ -114,13 +111,16 @@ DESCRIPTOR._options = None
<dl>
<dt id="meshtastic.apponly_pb2.ChannelSet"><code class="flex name class">
<span>class <span class="ident">ChannelSet</span></span>
<span>(</span><span>*args, **kwargs)</span>
<span>(</span><span>**kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<div class="desc"><p>Abstract base class for protocol messages.</p>
<p>Protocol message classes are almost always generated by the protocol
compiler.
These generated types subclass Message and implement the methods
shown below.</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google.protobuf.pyext._message.CMessage</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
@@ -129,12 +129,528 @@ DESCRIPTOR._options = None
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.apponly_pb2.ChannelSet.SETTINGS_FIELD_NUMBER"><code class="name">var <span class="ident">SETTINGS_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
<h3>Static methods</h3>
<dl>
<dt id="meshtastic.apponly_pb2.ChannelSet.FromString"><code class="name flex">
<span>def <span class="ident">FromString</span></span>(<span>s)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def FromString(s):
message = cls()
message.MergeFromString(s)
return message</code></pre>
</details>
</dd>
<dt id="meshtastic.apponly_pb2.ChannelSet.RegisterExtension"><code class="name flex">
<span>def <span class="ident">RegisterExtension</span></span>(<span>extension_handle)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def RegisterExtension(extension_handle):
extension_handle.containing_type = cls.DESCRIPTOR
# TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
# pylint: disable=protected-access
cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
_AttachFieldHelpers(cls, extension_handle)</code></pre>
</details>
</dd>
</dl>
<h3>Instance variables</h3>
<dl>
<dt id="meshtastic.apponly_pb2.ChannelSet.settings"><code class="name">var <span class="ident">settings</span></code></dt>
<dd>
<div class="desc"><p>Field ChannelSet.settings</p></div>
<div class="desc"><p>Getter for settings.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
field_value = self._fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
# Atomically check if another thread has preempted us and, if not, swap
# in the new object we just created. If someone has preempted us, we
# take that object and discard ours.
# WARNING: We are relying on setdefault() being atomic. This is true
# in CPython but we haven&#39;t investigated others. This warning appears
# in several other locations in this file.
field_value = self._fields.setdefault(field, field_value)
return field_value</code></pre>
</details>
</dd>
</dl>
<h3>Methods</h3>
<dl>
<dt id="meshtastic.apponly_pb2.ChannelSet.ByteSize"><code class="name flex">
<span>def <span class="ident">ByteSize</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ByteSize(self):
if not self._cached_byte_size_dirty:
return self._cached_byte_size
size = 0
descriptor = self.DESCRIPTOR
if descriptor.GetOptions().map_entry:
# Fields of map entry should always be serialized.
size = descriptor.fields_by_name[&#39;key&#39;]._sizer(self.key)
size += descriptor.fields_by_name[&#39;value&#39;]._sizer(self.value)
else:
for field_descriptor, field_value in self.ListFields():
size += field_descriptor._sizer(field_value)
for tag_bytes, value_bytes in self._unknown_fields:
size += len(tag_bytes) + len(value_bytes)
self._cached_byte_size = size
self._cached_byte_size_dirty = False
self._listener_for_children.dirty = False
return size</code></pre>
</details>
</dd>
<dt id="meshtastic.apponly_pb2.ChannelSet.Clear"><code class="name flex">
<span>def <span class="ident">Clear</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def _Clear(self):
# Clear fields.
self._fields = {}
self._unknown_fields = ()
# pylint: disable=protected-access
if self._unknown_field_set is not None:
self._unknown_field_set._clear()
self._unknown_field_set = None
self._oneofs = {}
self._Modified()</code></pre>
</details>
</dd>
<dt id="meshtastic.apponly_pb2.ChannelSet.ClearField"><code class="name flex">
<span>def <span class="ident">ClearField</span></span>(<span>self, field_name)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ClearField(self, field_name):
try:
field = message_descriptor.fields_by_name[field_name]
except KeyError:
try:
field = message_descriptor.oneofs_by_name[field_name]
if field in self._oneofs:
field = self._oneofs[field]
else:
return
except KeyError:
raise ValueError(&#39;Protocol message %s has no &#34;%s&#34; field.&#39; %
(message_descriptor.name, field_name))
if field in self._fields:
# To match the C++ implementation, we need to invalidate iterators
# for map fields when ClearField() happens.
if hasattr(self._fields[field], &#39;InvalidateIterators&#39;):
self._fields[field].InvalidateIterators()
# Note: If the field is a sub-message, its listener will still point
# at us. That&#39;s fine, because the worst than can happen is that it
# will call _Modified() and invalidate our byte size. Big deal.
del self._fields[field]
if self._oneofs.get(field.containing_oneof, None) is field:
del self._oneofs[field.containing_oneof]
# Always call _Modified() -- even if nothing was changed, this is
# a mutating method, and thus calling it should cause the field to become
# present in the parent message.
self._Modified()</code></pre>
</details>
</dd>
<dt id="meshtastic.apponly_pb2.ChannelSet.DiscardUnknownFields"><code class="name flex">
<span>def <span class="ident">DiscardUnknownFields</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def _DiscardUnknownFields(self):
self._unknown_fields = []
self._unknown_field_set = None # pylint: disable=protected-access
for field, value in self.ListFields():
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
if _IsMapField(field):
if _IsMessageMapField(field):
for key in value:
value[key].DiscardUnknownFields()
elif field.label == _FieldDescriptor.LABEL_REPEATED:
for sub_message in value:
sub_message.DiscardUnknownFields()
else:
value.DiscardUnknownFields()</code></pre>
</details>
</dd>
<dt id="meshtastic.apponly_pb2.ChannelSet.FindInitializationErrors"><code class="name flex">
<span>def <span class="ident">FindInitializationErrors</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Finds required fields which are not initialized.</p>
<h2 id="returns">Returns</h2>
<p>A list of strings.
Each string is a path to an uninitialized field from
the top-level message, e.g. "foo.bar[5].baz".</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def FindInitializationErrors(self):
&#34;&#34;&#34;Finds required fields which are not initialized.
Returns:
A list of strings. Each string is a path to an uninitialized field from
the top-level message, e.g. &#34;foo.bar[5].baz&#34;.
&#34;&#34;&#34;
errors = [] # simplify things
for field in required_fields:
if not self.HasField(field.name):
errors.append(field.name)
for field, value in self.ListFields():
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
if field.is_extension:
name = &#39;(%s)&#39; % field.full_name
else:
name = field.name
if _IsMapField(field):
if _IsMessageMapField(field):
for key in value:
element = value[key]
prefix = &#39;%s[%s].&#39; % (name, key)
sub_errors = element.FindInitializationErrors()
errors += [prefix + error for error in sub_errors]
else:
# ScalarMaps can&#39;t have any initialization errors.
pass
elif field.label == _FieldDescriptor.LABEL_REPEATED:
for i in range(len(value)):
element = value[i]
prefix = &#39;%s[%d].&#39; % (name, i)
sub_errors = element.FindInitializationErrors()
errors += [prefix + error for error in sub_errors]
else:
prefix = name + &#39;.&#39;
sub_errors = value.FindInitializationErrors()
errors += [prefix + error for error in sub_errors]
return errors</code></pre>
</details>
</dd>
<dt id="meshtastic.apponly_pb2.ChannelSet.HasField"><code class="name flex">
<span>def <span class="ident">HasField</span></span>(<span>self, field_name)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def HasField(self, field_name):
try:
field = hassable_fields[field_name]
except KeyError:
raise ValueError(error_msg % (message_descriptor.full_name, field_name))
if isinstance(field, descriptor_mod.OneofDescriptor):
try:
return HasField(self, self._oneofs[field].name)
except KeyError:
return False
else:
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
value = self._fields.get(field)
return value is not None and value._is_present_in_parent
else:
return field in self._fields</code></pre>
</details>
</dd>
<dt id="meshtastic.apponly_pb2.ChannelSet.IsInitialized"><code class="name flex">
<span>def <span class="ident">IsInitialized</span></span>(<span>self, errors=None)</span>
</code></dt>
<dd>
<div class="desc"><p>Checks if all required fields of a message are set.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>errors</code></strong></dt>
<dd>A list which, if provided, will be populated with the field
paths of all missing required fields.</dd>
</dl>
<h2 id="returns">Returns</h2>
<p>True iff the specified message has all required fields set.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def IsInitialized(self, errors=None):
&#34;&#34;&#34;Checks if all required fields of a message are set.
Args:
errors: A list which, if provided, will be populated with the field
paths of all missing required fields.
Returns:
True iff the specified message has all required fields set.
&#34;&#34;&#34;
# Performance is critical so we avoid HasField() and ListFields().
for field in required_fields:
if (field not in self._fields or
(field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
not self._fields[field]._is_present_in_parent)):
if errors is not None:
errors.extend(self.FindInitializationErrors())
return False
for field, value in list(self._fields.items()): # dict can change size!
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
if field.label == _FieldDescriptor.LABEL_REPEATED:
if (field.message_type.has_options and
field.message_type.GetOptions().map_entry):
continue
for element in value:
if not element.IsInitialized():
if errors is not None:
errors.extend(self.FindInitializationErrors())
return False
elif value._is_present_in_parent and not value.IsInitialized():
if errors is not None:
errors.extend(self.FindInitializationErrors())
return False
return True</code></pre>
</details>
</dd>
<dt id="meshtastic.apponly_pb2.ChannelSet.ListFields"><code class="name flex">
<span>def <span class="ident">ListFields</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ListFields(self):
all_fields = [item for item in self._fields.items() if _IsPresent(item)]
all_fields.sort(key = lambda item: item[0].number)
return all_fields</code></pre>
</details>
</dd>
<dt id="meshtastic.apponly_pb2.ChannelSet.MergeFrom"><code class="name flex">
<span>def <span class="ident">MergeFrom</span></span>(<span>self, msg)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def MergeFrom(self, msg):
if not isinstance(msg, cls):
raise TypeError(
&#39;Parameter to MergeFrom() must be instance of same class: &#39;
&#39;expected %s got %s.&#39; % (_FullyQualifiedClassName(cls),
_FullyQualifiedClassName(msg.__class__)))
assert msg is not self
self._Modified()
fields = self._fields
for field, value in msg._fields.items():
if field.label == LABEL_REPEATED:
field_value = fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
fields[field] = field_value
field_value.MergeFrom(value)
elif field.cpp_type == CPPTYPE_MESSAGE:
if value._is_present_in_parent:
field_value = fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
fields[field] = field_value
field_value.MergeFrom(value)
else:
self._fields[field] = value
if field.containing_oneof:
self._UpdateOneofState(field)
if msg._unknown_fields:
if not self._unknown_fields:
self._unknown_fields = []
self._unknown_fields.extend(msg._unknown_fields)
# pylint: disable=protected-access
if self._unknown_field_set is None:
self._unknown_field_set = containers.UnknownFieldSet()
self._unknown_field_set._extend(msg._unknown_field_set)</code></pre>
</details>
</dd>
<dt id="meshtastic.apponly_pb2.ChannelSet.MergeFromString"><code class="name flex">
<span>def <span class="ident">MergeFromString</span></span>(<span>self, serialized)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def MergeFromString(self, serialized):
serialized = memoryview(serialized)
length = len(serialized)
try:
if self._InternalParse(serialized, 0, length) != length:
# The only reason _InternalParse would return early is if it
# encountered an end-group tag.
raise message_mod.DecodeError(&#39;Unexpected end-group tag.&#39;)
except (IndexError, TypeError):
# Now ord(buf[p:p+1]) == ord(&#39;&#39;) gets TypeError.
raise message_mod.DecodeError(&#39;Truncated message.&#39;)
except struct.error as e:
raise message_mod.DecodeError(e)
return length # Return this for legacy reasons.</code></pre>
</details>
</dd>
<dt id="meshtastic.apponly_pb2.ChannelSet.SerializePartialToString"><code class="name flex">
<span>def <span class="ident">SerializePartialToString</span></span>(<span>self, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def SerializePartialToString(self, **kwargs):
out = BytesIO()
self._InternalSerialize(out.write, **kwargs)
return out.getvalue()</code></pre>
</details>
</dd>
<dt id="meshtastic.apponly_pb2.ChannelSet.SerializeToString"><code class="name flex">
<span>def <span class="ident">SerializeToString</span></span>(<span>self, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def SerializeToString(self, **kwargs):
# Check if the message has all of its required fields set.
if not self.IsInitialized():
raise message_mod.EncodeError(
&#39;Message %s is missing required fields: %s&#39; % (
self.DESCRIPTOR.full_name, &#39;,&#39;.join(self.FindInitializationErrors())))
return self.SerializePartialToString(**kwargs)</code></pre>
</details>
</dd>
<dt id="meshtastic.apponly_pb2.ChannelSet.SetInParent"><code class="name flex">
<span>def <span class="ident">SetInParent</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Sets the _cached_byte_size_dirty bit to true,
and propagates this to our listener iff this was a state change.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def Modified(self):
&#34;&#34;&#34;Sets the _cached_byte_size_dirty bit to true,
and propagates this to our listener iff this was a state change.
&#34;&#34;&#34;
# Note: Some callers check _cached_byte_size_dirty before calling
# _Modified() as an extra optimization. So, if this method is ever
# changed such that it does stuff even when _cached_byte_size_dirty is
# already true, the callers need to be updated.
if not self._cached_byte_size_dirty:
self._cached_byte_size_dirty = True
self._listener_for_children.dirty = True
self._is_present_in_parent = True
self._listener.Modified()</code></pre>
</details>
</dd>
<dt id="meshtastic.apponly_pb2.ChannelSet.UnknownFields"><code class="name flex">
<span>def <span class="ident">UnknownFields</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def _UnknownFields(self):
if self._unknown_field_set is None: # pylint: disable=protected-access
# pylint: disable=protected-access
self._unknown_field_set = containers.UnknownFieldSet()
return self._unknown_field_set # pylint: disable=protected-access</code></pre>
</details>
</dd>
<dt id="meshtastic.apponly_pb2.ChannelSet.WhichOneof"><code class="name flex">
<span>def <span class="ident">WhichOneof</span></span>(<span>self, oneof_name)</span>
</code></dt>
<dd>
<div class="desc"><p>Returns the name of the currently set field inside a oneof, or None.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def WhichOneof(self, oneof_name):
&#34;&#34;&#34;Returns the name of the currently set field inside a oneof, or None.&#34;&#34;&#34;
try:
field = message_descriptor.oneofs_by_name[oneof_name]
except KeyError:
raise ValueError(
&#39;Protocol message has no oneof &#34;%s&#34; field.&#39; % oneof_name)
nested_field = self._oneofs.get(field, None)
if nested_field is not None and self.HasField(nested_field.name):
return nested_field.name
else:
return None</code></pre>
</details>
</dd>
</dl>
</dd>
@@ -157,7 +673,25 @@ DESCRIPTOR._options = None
<li>
<h4><code><a title="meshtastic.apponly_pb2.ChannelSet" href="#meshtastic.apponly_pb2.ChannelSet">ChannelSet</a></code></h4>
<ul class="">
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.ByteSize" href="#meshtastic.apponly_pb2.ChannelSet.ByteSize">ByteSize</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.Clear" href="#meshtastic.apponly_pb2.ChannelSet.Clear">Clear</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.ClearField" href="#meshtastic.apponly_pb2.ChannelSet.ClearField">ClearField</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.DESCRIPTOR" href="#meshtastic.apponly_pb2.ChannelSet.DESCRIPTOR">DESCRIPTOR</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.DiscardUnknownFields" href="#meshtastic.apponly_pb2.ChannelSet.DiscardUnknownFields">DiscardUnknownFields</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.FindInitializationErrors" href="#meshtastic.apponly_pb2.ChannelSet.FindInitializationErrors">FindInitializationErrors</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.FromString" href="#meshtastic.apponly_pb2.ChannelSet.FromString">FromString</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.HasField" href="#meshtastic.apponly_pb2.ChannelSet.HasField">HasField</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.IsInitialized" href="#meshtastic.apponly_pb2.ChannelSet.IsInitialized">IsInitialized</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.ListFields" href="#meshtastic.apponly_pb2.ChannelSet.ListFields">ListFields</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.MergeFrom" href="#meshtastic.apponly_pb2.ChannelSet.MergeFrom">MergeFrom</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.MergeFromString" href="#meshtastic.apponly_pb2.ChannelSet.MergeFromString">MergeFromString</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.RegisterExtension" href="#meshtastic.apponly_pb2.ChannelSet.RegisterExtension">RegisterExtension</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.SETTINGS_FIELD_NUMBER" href="#meshtastic.apponly_pb2.ChannelSet.SETTINGS_FIELD_NUMBER">SETTINGS_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.SerializePartialToString" href="#meshtastic.apponly_pb2.ChannelSet.SerializePartialToString">SerializePartialToString</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.SerializeToString" href="#meshtastic.apponly_pb2.ChannelSet.SerializeToString">SerializeToString</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.SetInParent" href="#meshtastic.apponly_pb2.ChannelSet.SetInParent">SetInParent</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.UnknownFields" href="#meshtastic.apponly_pb2.ChannelSet.UnknownFields">UnknownFields</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.WhichOneof" href="#meshtastic.apponly_pb2.ChannelSet.WhichOneof">WhichOneof</a></code></li>
<li><code><a title="meshtastic.apponly_pb2.ChannelSet.settings" href="#meshtastic.apponly_pb2.ChannelSet.settings">settings</a></code></li>
</ul>
</li>
@@ -167,7 +701,7 @@ DESCRIPTOR._options = None
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc"><cite>pdoc</cite> 0.9.2</a>.</p>
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.9.2" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.ble API documentation</title>
<meta name="description" content="" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
@@ -47,7 +47,7 @@
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc"><cite>pdoc</cite> 0.9.2</a>.</p>
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,212 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.ble_interface API documentation</title>
<meta name="description" content="Bluetooth interface" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.ble_interface</code></h1>
</header>
<section id="section-intro">
<p>Bluetooth interface</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Bluetooth interface
&#34;&#34;&#34;
import logging
import pygatt
from .mesh_interface import MeshInterface
# Our standard BLE characteristics
TORADIO_UUID = &#34;f75c76d2-129e-4dad-a1dd-7866124401e7&#34;
FROMRADIO_UUID = &#34;8ba2bcc2-ee02-4a55-a531-c525c5e454d5&#34;
FROMNUM_UUID = &#34;ed9da18c-a800-4f66-a670-aa7547e34453&#34;
class BLEInterface(MeshInterface):
&#34;&#34;&#34;A not quite ready - FIXME - BLE interface to devices&#34;&#34;&#34;
def __init__(self, address, noProto=False, debugOut=None):
self.address = address
if not noProto:
self.adapter = pygatt.GATTToolBackend() # BGAPIBackend()
self.adapter.start()
logging.debug(f&#34;Connecting to {self.address}&#34;)
self.device = self.adapter.connect(address)
else:
self.adapter = None
self.device = None
logging.debug(&#34;Connected to device&#34;)
# fromradio = self.device.char_read(FROMRADIO_UUID)
MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto)
self._readFromRadio() # read the initial responses
def handle_data(handle, data):
self._handleFromRadio(data)
if self.device:
self.device.subscribe(FROMNUM_UUID, callback=handle_data)
def _sendToRadioImpl(self, toRadio):
&#34;&#34;&#34;Send a ToRadio protobuf to the device&#34;&#34;&#34;
#logging.debug(f&#34;Sending: {stripnl(toRadio)}&#34;)
b = toRadio.SerializeToString()
self.device.char_write(TORADIO_UUID, b)
def close(self):
MeshInterface.close(self)
if self.adapter:
self.adapter.stop()
def _readFromRadio(self):
if not self.noProto:
wasEmpty = False
while not wasEmpty:
if self.device:
b = self.device.char_read(FROMRADIO_UUID)
wasEmpty = len(b) == 0
if not wasEmpty:
self._handleFromRadio(b)</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="meshtastic.ble_interface.BLEInterface"><code class="flex name class">
<span>class <span class="ident">BLEInterface</span></span>
<span>(</span><span>address, noProto=False, debugOut=None)</span>
</code></dt>
<dd>
<div class="desc"><p>A not quite ready - FIXME - BLE interface to devices</p>
<p>Constructor</p>
<p>Keyword Arguments:
noProto &ndash; If True, don't try to run our protocol on the
link - just be a dumb serial client.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class BLEInterface(MeshInterface):
&#34;&#34;&#34;A not quite ready - FIXME - BLE interface to devices&#34;&#34;&#34;
def __init__(self, address, noProto=False, debugOut=None):
self.address = address
if not noProto:
self.adapter = pygatt.GATTToolBackend() # BGAPIBackend()
self.adapter.start()
logging.debug(f&#34;Connecting to {self.address}&#34;)
self.device = self.adapter.connect(address)
else:
self.adapter = None
self.device = None
logging.debug(&#34;Connected to device&#34;)
# fromradio = self.device.char_read(FROMRADIO_UUID)
MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto)
self._readFromRadio() # read the initial responses
def handle_data(handle, data):
self._handleFromRadio(data)
if self.device:
self.device.subscribe(FROMNUM_UUID, callback=handle_data)
def _sendToRadioImpl(self, toRadio):
&#34;&#34;&#34;Send a ToRadio protobuf to the device&#34;&#34;&#34;
#logging.debug(f&#34;Sending: {stripnl(toRadio)}&#34;)
b = toRadio.SerializeToString()
self.device.char_write(TORADIO_UUID, b)
def close(self):
MeshInterface.close(self)
if self.adapter:
self.adapter.stop()
def _readFromRadio(self):
if not self.noProto:
wasEmpty = False
while not wasEmpty:
if self.device:
b = self.device.char_read(FROMRADIO_UUID)
wasEmpty = len(b) == 0
if not wasEmpty:
self._handleFromRadio(b)</code></pre>
</details>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="meshtastic.mesh_interface.MeshInterface" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface">MeshInterface</a></li>
</ul>
<h3>Inherited members</h3>
<ul class="hlist">
<li><code><b><a title="meshtastic.mesh_interface.MeshInterface" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface">MeshInterface</a></b></code>:
<ul class="hlist">
<li><code><a title="meshtastic.mesh_interface.MeshInterface.close" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.close">close</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.getLongName" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getLongName">getLongName</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.getMyNodeInfo" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getMyNodeInfo">getMyNodeInfo</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.getMyUser" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getMyUser">getMyUser</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.getNode" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getNode">getNode</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.getShortName" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getShortName">getShortName</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.sendData" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.sendData">sendData</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.sendPosition" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.sendPosition">sendPosition</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.sendText" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.sendText">sendText</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.showInfo" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.showInfo">showInfo</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.showNodes" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.showNodes">showNodes</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.waitForConfig" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.waitForConfig">waitForConfig</a></code></li>
</ul>
</li>
</ul>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic" href="index.html">meshtastic</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="meshtastic.ble_interface.BLEInterface" href="#meshtastic.ble_interface.BLEInterface">BLEInterface</a></code></h4>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

@@ -3,9 +3,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.9.2" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.environmental_measurement_pb2 API documentation</title>
<meta name="description" content="Generated protocol buffer code." />
<meta name="description" content="" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
@@ -22,7 +22,6 @@
<h1 class="title">Module <code>meshtastic.environmental_measurement_pb2</code></h1>
</header>
<section id="section-intro">
<p>Generated protocol buffer code.</p>
<details class="source">
<summary>
<span>Expand source code</span>
@@ -30,7 +29,7 @@
<pre><code class="python"># -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: environmental_measurement.proto
&#34;&#34;&#34;Generated protocol buffer code.&#34;&#34;&#34;
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
@@ -47,7 +46,6 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package=&#39;&#39;,
syntax=&#39;proto3&#39;,
serialized_options=b&#39;Z!github.com/meshtastic/gomeshproto&#39;,
create_key=_descriptor._internal_create_key,
serialized_pb=b&#39;\n\x1f\x65nvironmental_measurement.proto\&#34;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&#39;
)
@@ -60,7 +58,6 @@ _ENVIRONMENTALMEASUREMENT = _descriptor.Descriptor(
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name=&#39;temperature&#39;, full_name=&#39;EnvironmentalMeasurement.temperature&#39;, index=0,
@@ -68,21 +65,21 @@ _ENVIRONMENTALMEASUREMENT = _descriptor.Descriptor(
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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;relative_humidity&#39;, full_name=&#39;EnvironmentalMeasurement.relative_humidity&#39;, 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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;barometric_pressure&#39;, full_name=&#39;EnvironmentalMeasurement.barometric_pressure&#39;, 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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
@@ -125,35 +122,570 @@ DESCRIPTOR._options = None
<dl>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement"><code class="flex name class">
<span>class <span class="ident">EnvironmentalMeasurement</span></span>
<span>(</span><span>*args, **kwargs)</span>
<span>(</span><span>**kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<div class="desc"><p>Abstract base class for protocol messages.</p>
<p>Protocol message classes are almost always generated by the protocol
compiler.
These generated types subclass Message and implement the methods
shown below.</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google.protobuf.pyext._message.CMessage</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.BAROMETRIC_PRESSURE_FIELD_NUMBER"><code class="name">var <span class="ident">BAROMETRIC_PRESSURE_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.RELATIVE_HUMIDITY_FIELD_NUMBER"><code class="name">var <span class="ident">RELATIVE_HUMIDITY_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.TEMPERATURE_FIELD_NUMBER"><code class="name">var <span class="ident">TEMPERATURE_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
<h3>Static methods</h3>
<dl>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.FromString"><code class="name flex">
<span>def <span class="ident">FromString</span></span>(<span>s)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def FromString(s):
message = cls()
message.MergeFromString(s)
return message</code></pre>
</details>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.RegisterExtension"><code class="name flex">
<span>def <span class="ident">RegisterExtension</span></span>(<span>extension_handle)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def RegisterExtension(extension_handle):
extension_handle.containing_type = cls.DESCRIPTOR
# TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
# pylint: disable=protected-access
cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
_AttachFieldHelpers(cls, extension_handle)</code></pre>
</details>
</dd>
</dl>
<h3>Instance variables</h3>
<dl>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.barometric_pressure"><code class="name">var <span class="ident">barometric_pressure</span></code></dt>
<dd>
<div class="desc"><p>Field EnvironmentalMeasurement.barometric_pressure</p></div>
<div class="desc"><p>Getter for barometric_pressure.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.relative_humidity"><code class="name">var <span class="ident">relative_humidity</span></code></dt>
<dd>
<div class="desc"><p>Field EnvironmentalMeasurement.relative_humidity</p></div>
<div class="desc"><p>Getter for relative_humidity.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.temperature"><code class="name">var <span class="ident">temperature</span></code></dt>
<dd>
<div class="desc"><p>Field EnvironmentalMeasurement.temperature</p></div>
<div class="desc"><p>Getter for temperature.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
</dl>
<h3>Methods</h3>
<dl>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.ByteSize"><code class="name flex">
<span>def <span class="ident">ByteSize</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ByteSize(self):
if not self._cached_byte_size_dirty:
return self._cached_byte_size
size = 0
descriptor = self.DESCRIPTOR
if descriptor.GetOptions().map_entry:
# Fields of map entry should always be serialized.
size = descriptor.fields_by_name[&#39;key&#39;]._sizer(self.key)
size += descriptor.fields_by_name[&#39;value&#39;]._sizer(self.value)
else:
for field_descriptor, field_value in self.ListFields():
size += field_descriptor._sizer(field_value)
for tag_bytes, value_bytes in self._unknown_fields:
size += len(tag_bytes) + len(value_bytes)
self._cached_byte_size = size
self._cached_byte_size_dirty = False
self._listener_for_children.dirty = False
return size</code></pre>
</details>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.Clear"><code class="name flex">
<span>def <span class="ident">Clear</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def _Clear(self):
# Clear fields.
self._fields = {}
self._unknown_fields = ()
# pylint: disable=protected-access
if self._unknown_field_set is not None:
self._unknown_field_set._clear()
self._unknown_field_set = None
self._oneofs = {}
self._Modified()</code></pre>
</details>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.ClearField"><code class="name flex">
<span>def <span class="ident">ClearField</span></span>(<span>self, field_name)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ClearField(self, field_name):
try:
field = message_descriptor.fields_by_name[field_name]
except KeyError:
try:
field = message_descriptor.oneofs_by_name[field_name]
if field in self._oneofs:
field = self._oneofs[field]
else:
return
except KeyError:
raise ValueError(&#39;Protocol message %s has no &#34;%s&#34; field.&#39; %
(message_descriptor.name, field_name))
if field in self._fields:
# To match the C++ implementation, we need to invalidate iterators
# for map fields when ClearField() happens.
if hasattr(self._fields[field], &#39;InvalidateIterators&#39;):
self._fields[field].InvalidateIterators()
# Note: If the field is a sub-message, its listener will still point
# at us. That&#39;s fine, because the worst than can happen is that it
# will call _Modified() and invalidate our byte size. Big deal.
del self._fields[field]
if self._oneofs.get(field.containing_oneof, None) is field:
del self._oneofs[field.containing_oneof]
# Always call _Modified() -- even if nothing was changed, this is
# a mutating method, and thus calling it should cause the field to become
# present in the parent message.
self._Modified()</code></pre>
</details>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.DiscardUnknownFields"><code class="name flex">
<span>def <span class="ident">DiscardUnknownFields</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def _DiscardUnknownFields(self):
self._unknown_fields = []
self._unknown_field_set = None # pylint: disable=protected-access
for field, value in self.ListFields():
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
if _IsMapField(field):
if _IsMessageMapField(field):
for key in value:
value[key].DiscardUnknownFields()
elif field.label == _FieldDescriptor.LABEL_REPEATED:
for sub_message in value:
sub_message.DiscardUnknownFields()
else:
value.DiscardUnknownFields()</code></pre>
</details>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.FindInitializationErrors"><code class="name flex">
<span>def <span class="ident">FindInitializationErrors</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Finds required fields which are not initialized.</p>
<h2 id="returns">Returns</h2>
<p>A list of strings.
Each string is a path to an uninitialized field from
the top-level message, e.g. "foo.bar[5].baz".</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def FindInitializationErrors(self):
&#34;&#34;&#34;Finds required fields which are not initialized.
Returns:
A list of strings. Each string is a path to an uninitialized field from
the top-level message, e.g. &#34;foo.bar[5].baz&#34;.
&#34;&#34;&#34;
errors = [] # simplify things
for field in required_fields:
if not self.HasField(field.name):
errors.append(field.name)
for field, value in self.ListFields():
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
if field.is_extension:
name = &#39;(%s)&#39; % field.full_name
else:
name = field.name
if _IsMapField(field):
if _IsMessageMapField(field):
for key in value:
element = value[key]
prefix = &#39;%s[%s].&#39; % (name, key)
sub_errors = element.FindInitializationErrors()
errors += [prefix + error for error in sub_errors]
else:
# ScalarMaps can&#39;t have any initialization errors.
pass
elif field.label == _FieldDescriptor.LABEL_REPEATED:
for i in range(len(value)):
element = value[i]
prefix = &#39;%s[%d].&#39; % (name, i)
sub_errors = element.FindInitializationErrors()
errors += [prefix + error for error in sub_errors]
else:
prefix = name + &#39;.&#39;
sub_errors = value.FindInitializationErrors()
errors += [prefix + error for error in sub_errors]
return errors</code></pre>
</details>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.HasField"><code class="name flex">
<span>def <span class="ident">HasField</span></span>(<span>self, field_name)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def HasField(self, field_name):
try:
field = hassable_fields[field_name]
except KeyError:
raise ValueError(error_msg % (message_descriptor.full_name, field_name))
if isinstance(field, descriptor_mod.OneofDescriptor):
try:
return HasField(self, self._oneofs[field].name)
except KeyError:
return False
else:
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
value = self._fields.get(field)
return value is not None and value._is_present_in_parent
else:
return field in self._fields</code></pre>
</details>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.IsInitialized"><code class="name flex">
<span>def <span class="ident">IsInitialized</span></span>(<span>self, errors=None)</span>
</code></dt>
<dd>
<div class="desc"><p>Checks if all required fields of a message are set.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>errors</code></strong></dt>
<dd>A list which, if provided, will be populated with the field
paths of all missing required fields.</dd>
</dl>
<h2 id="returns">Returns</h2>
<p>True iff the specified message has all required fields set.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def IsInitialized(self, errors=None):
&#34;&#34;&#34;Checks if all required fields of a message are set.
Args:
errors: A list which, if provided, will be populated with the field
paths of all missing required fields.
Returns:
True iff the specified message has all required fields set.
&#34;&#34;&#34;
# Performance is critical so we avoid HasField() and ListFields().
for field in required_fields:
if (field not in self._fields or
(field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
not self._fields[field]._is_present_in_parent)):
if errors is not None:
errors.extend(self.FindInitializationErrors())
return False
for field, value in list(self._fields.items()): # dict can change size!
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
if field.label == _FieldDescriptor.LABEL_REPEATED:
if (field.message_type.has_options and
field.message_type.GetOptions().map_entry):
continue
for element in value:
if not element.IsInitialized():
if errors is not None:
errors.extend(self.FindInitializationErrors())
return False
elif value._is_present_in_parent and not value.IsInitialized():
if errors is not None:
errors.extend(self.FindInitializationErrors())
return False
return True</code></pre>
</details>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.ListFields"><code class="name flex">
<span>def <span class="ident">ListFields</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ListFields(self):
all_fields = [item for item in self._fields.items() if _IsPresent(item)]
all_fields.sort(key = lambda item: item[0].number)
return all_fields</code></pre>
</details>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.MergeFrom"><code class="name flex">
<span>def <span class="ident">MergeFrom</span></span>(<span>self, msg)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def MergeFrom(self, msg):
if not isinstance(msg, cls):
raise TypeError(
&#39;Parameter to MergeFrom() must be instance of same class: &#39;
&#39;expected %s got %s.&#39; % (_FullyQualifiedClassName(cls),
_FullyQualifiedClassName(msg.__class__)))
assert msg is not self
self._Modified()
fields = self._fields
for field, value in msg._fields.items():
if field.label == LABEL_REPEATED:
field_value = fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
fields[field] = field_value
field_value.MergeFrom(value)
elif field.cpp_type == CPPTYPE_MESSAGE:
if value._is_present_in_parent:
field_value = fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
fields[field] = field_value
field_value.MergeFrom(value)
else:
self._fields[field] = value
if field.containing_oneof:
self._UpdateOneofState(field)
if msg._unknown_fields:
if not self._unknown_fields:
self._unknown_fields = []
self._unknown_fields.extend(msg._unknown_fields)
# pylint: disable=protected-access
if self._unknown_field_set is None:
self._unknown_field_set = containers.UnknownFieldSet()
self._unknown_field_set._extend(msg._unknown_field_set)</code></pre>
</details>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.MergeFromString"><code class="name flex">
<span>def <span class="ident">MergeFromString</span></span>(<span>self, serialized)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def MergeFromString(self, serialized):
serialized = memoryview(serialized)
length = len(serialized)
try:
if self._InternalParse(serialized, 0, length) != length:
# The only reason _InternalParse would return early is if it
# encountered an end-group tag.
raise message_mod.DecodeError(&#39;Unexpected end-group tag.&#39;)
except (IndexError, TypeError):
# Now ord(buf[p:p+1]) == ord(&#39;&#39;) gets TypeError.
raise message_mod.DecodeError(&#39;Truncated message.&#39;)
except struct.error as e:
raise message_mod.DecodeError(e)
return length # Return this for legacy reasons.</code></pre>
</details>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.SerializePartialToString"><code class="name flex">
<span>def <span class="ident">SerializePartialToString</span></span>(<span>self, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def SerializePartialToString(self, **kwargs):
out = BytesIO()
self._InternalSerialize(out.write, **kwargs)
return out.getvalue()</code></pre>
</details>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.SerializeToString"><code class="name flex">
<span>def <span class="ident">SerializeToString</span></span>(<span>self, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def SerializeToString(self, **kwargs):
# Check if the message has all of its required fields set.
if not self.IsInitialized():
raise message_mod.EncodeError(
&#39;Message %s is missing required fields: %s&#39; % (
self.DESCRIPTOR.full_name, &#39;,&#39;.join(self.FindInitializationErrors())))
return self.SerializePartialToString(**kwargs)</code></pre>
</details>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.SetInParent"><code class="name flex">
<span>def <span class="ident">SetInParent</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Sets the _cached_byte_size_dirty bit to true,
and propagates this to our listener iff this was a state change.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def Modified(self):
&#34;&#34;&#34;Sets the _cached_byte_size_dirty bit to true,
and propagates this to our listener iff this was a state change.
&#34;&#34;&#34;
# Note: Some callers check _cached_byte_size_dirty before calling
# _Modified() as an extra optimization. So, if this method is ever
# changed such that it does stuff even when _cached_byte_size_dirty is
# already true, the callers need to be updated.
if not self._cached_byte_size_dirty:
self._cached_byte_size_dirty = True
self._listener_for_children.dirty = True
self._is_present_in_parent = True
self._listener.Modified()</code></pre>
</details>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.UnknownFields"><code class="name flex">
<span>def <span class="ident">UnknownFields</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def _UnknownFields(self):
if self._unknown_field_set is None: # pylint: disable=protected-access
# pylint: disable=protected-access
self._unknown_field_set = containers.UnknownFieldSet()
return self._unknown_field_set # pylint: disable=protected-access</code></pre>
</details>
</dd>
<dt id="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.WhichOneof"><code class="name flex">
<span>def <span class="ident">WhichOneof</span></span>(<span>self, oneof_name)</span>
</code></dt>
<dd>
<div class="desc"><p>Returns the name of the currently set field inside a oneof, or None.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def WhichOneof(self, oneof_name):
&#34;&#34;&#34;Returns the name of the currently set field inside a oneof, or None.&#34;&#34;&#34;
try:
field = message_descriptor.oneofs_by_name[oneof_name]
except KeyError:
raise ValueError(
&#39;Protocol message has no oneof &#34;%s&#34; field.&#39; % oneof_name)
nested_field = self._oneofs.get(field, None)
if nested_field is not None and self.HasField(nested_field.name):
return nested_field.name
else:
return None</code></pre>
</details>
</dd>
</dl>
</dd>
@@ -176,7 +708,27 @@ DESCRIPTOR._options = None
<li>
<h4><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement">EnvironmentalMeasurement</a></code></h4>
<ul class="">
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.BAROMETRIC_PRESSURE_FIELD_NUMBER" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.BAROMETRIC_PRESSURE_FIELD_NUMBER">BAROMETRIC_PRESSURE_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.ByteSize" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.ByteSize">ByteSize</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.Clear" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.Clear">Clear</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.ClearField" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.ClearField">ClearField</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.DESCRIPTOR" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.DESCRIPTOR">DESCRIPTOR</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.DiscardUnknownFields" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.DiscardUnknownFields">DiscardUnknownFields</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.FindInitializationErrors" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.FindInitializationErrors">FindInitializationErrors</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.FromString" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.FromString">FromString</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.HasField" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.HasField">HasField</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.IsInitialized" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.IsInitialized">IsInitialized</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.ListFields" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.ListFields">ListFields</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.MergeFrom" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.MergeFrom">MergeFrom</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.MergeFromString" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.MergeFromString">MergeFromString</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.RELATIVE_HUMIDITY_FIELD_NUMBER" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.RELATIVE_HUMIDITY_FIELD_NUMBER">RELATIVE_HUMIDITY_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.RegisterExtension" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.RegisterExtension">RegisterExtension</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.SerializePartialToString" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.SerializePartialToString">SerializePartialToString</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.SerializeToString" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.SerializeToString">SerializeToString</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.SetInParent" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.SetInParent">SetInParent</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.TEMPERATURE_FIELD_NUMBER" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.TEMPERATURE_FIELD_NUMBER">TEMPERATURE_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.UnknownFields" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.UnknownFields">UnknownFields</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.WhichOneof" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.WhichOneof">WhichOneof</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.barometric_pressure" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.barometric_pressure">barometric_pressure</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.relative_humidity" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.relative_humidity">relative_humidity</a></code></li>
<li><code><a title="meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.temperature" href="#meshtastic.environmental_measurement_pb2.EnvironmentalMeasurement.temperature">temperature</a></code></li>
@@ -188,7 +740,7 @@ DESCRIPTOR._options = None
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc"><cite>pdoc</cite> 0.9.2</a>.</p>
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,380 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.globals API documentation</title>
<meta name="description" content="Globals singleton class …" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.globals</code></h1>
</header>
<section id="section-intro">
<p>Globals singleton class.</p>
<p>Instead of using a global, stuff your variables in this "trash can".
This is not much better than using python's globals, but it allows
us to better test meshtastic. Plus, there are some weird python
global issues/gotcha that we can hopefully avoid by using this
class instead.</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Globals singleton class.
Instead of using a global, stuff your variables in this &#34;trash can&#34;.
This is not much better than using python&#39;s globals, but it allows
us to better test meshtastic. Plus, there are some weird python
global issues/gotcha that we can hopefully avoid by using this
class instead.
&#34;&#34;&#34;
class Globals:
&#34;&#34;&#34;Globals class is a Singleton.&#34;&#34;&#34;
__instance = None
@staticmethod
def getInstance():
&#34;&#34;&#34;Get an instance of the Globals class.&#34;&#34;&#34;
if Globals.__instance is None:
Globals()
return Globals.__instance
def __init__(self):
&#34;&#34;&#34;Constructor for the Globals CLass&#34;&#34;&#34;
if Globals.__instance is not None:
raise Exception(&#34;This class is a singleton&#34;)
else:
Globals.__instance = self
self.args = None
self.parser = None
self.target_node = None
self.channel_index = None
def reset(self):
&#34;&#34;&#34;Reset all of our globals. If you add a member, add it to this method, too.&#34;&#34;&#34;
self.args = None
self.parser = None
self.target_node = None
self.channel_index = None
def set_args(self, args):
&#34;&#34;&#34;Set the args&#34;&#34;&#34;
self.args = args
def set_parser(self, parser):
&#34;&#34;&#34;Set the parser&#34;&#34;&#34;
self.parser = parser
def set_target_node(self, target_node):
&#34;&#34;&#34;Set the target_node&#34;&#34;&#34;
self.target_node = target_node
def set_channel_index(self, channel_index):
&#34;&#34;&#34;Set the channel_index&#34;&#34;&#34;
self.channel_index = channel_index
def get_args(self):
&#34;&#34;&#34;Get args&#34;&#34;&#34;
return self.args
def get_parser(self):
&#34;&#34;&#34;Get parser&#34;&#34;&#34;
return self.parser
def get_target_node(self):
&#34;&#34;&#34;Get target_node&#34;&#34;&#34;
return self.target_node
def get_channel_index(self):
&#34;&#34;&#34;Get channel_index&#34;&#34;&#34;
return self.channel_index</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="meshtastic.globals.Globals"><code class="flex name class">
<span>class <span class="ident">Globals</span></span>
</code></dt>
<dd>
<div class="desc"><p>Globals class is a Singleton.</p>
<p>Constructor for the Globals CLass</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class Globals:
&#34;&#34;&#34;Globals class is a Singleton.&#34;&#34;&#34;
__instance = None
@staticmethod
def getInstance():
&#34;&#34;&#34;Get an instance of the Globals class.&#34;&#34;&#34;
if Globals.__instance is None:
Globals()
return Globals.__instance
def __init__(self):
&#34;&#34;&#34;Constructor for the Globals CLass&#34;&#34;&#34;
if Globals.__instance is not None:
raise Exception(&#34;This class is a singleton&#34;)
else:
Globals.__instance = self
self.args = None
self.parser = None
self.target_node = None
self.channel_index = None
def reset(self):
&#34;&#34;&#34;Reset all of our globals. If you add a member, add it to this method, too.&#34;&#34;&#34;
self.args = None
self.parser = None
self.target_node = None
self.channel_index = None
def set_args(self, args):
&#34;&#34;&#34;Set the args&#34;&#34;&#34;
self.args = args
def set_parser(self, parser):
&#34;&#34;&#34;Set the parser&#34;&#34;&#34;
self.parser = parser
def set_target_node(self, target_node):
&#34;&#34;&#34;Set the target_node&#34;&#34;&#34;
self.target_node = target_node
def set_channel_index(self, channel_index):
&#34;&#34;&#34;Set the channel_index&#34;&#34;&#34;
self.channel_index = channel_index
def get_args(self):
&#34;&#34;&#34;Get args&#34;&#34;&#34;
return self.args
def get_parser(self):
&#34;&#34;&#34;Get parser&#34;&#34;&#34;
return self.parser
def get_target_node(self):
&#34;&#34;&#34;Get target_node&#34;&#34;&#34;
return self.target_node
def get_channel_index(self):
&#34;&#34;&#34;Get channel_index&#34;&#34;&#34;
return self.channel_index</code></pre>
</details>
<h3>Static methods</h3>
<dl>
<dt id="meshtastic.globals.Globals.getInstance"><code class="name flex">
<span>def <span class="ident">getInstance</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Get an instance of the Globals class.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@staticmethod
def getInstance():
&#34;&#34;&#34;Get an instance of the Globals class.&#34;&#34;&#34;
if Globals.__instance is None:
Globals()
return Globals.__instance</code></pre>
</details>
</dd>
</dl>
<h3>Methods</h3>
<dl>
<dt id="meshtastic.globals.Globals.get_args"><code class="name flex">
<span>def <span class="ident">get_args</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Get args</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_args(self):
&#34;&#34;&#34;Get args&#34;&#34;&#34;
return self.args</code></pre>
</details>
</dd>
<dt id="meshtastic.globals.Globals.get_channel_index"><code class="name flex">
<span>def <span class="ident">get_channel_index</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Get channel_index</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_channel_index(self):
&#34;&#34;&#34;Get channel_index&#34;&#34;&#34;
return self.channel_index</code></pre>
</details>
</dd>
<dt id="meshtastic.globals.Globals.get_parser"><code class="name flex">
<span>def <span class="ident">get_parser</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Get parser</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_parser(self):
&#34;&#34;&#34;Get parser&#34;&#34;&#34;
return self.parser</code></pre>
</details>
</dd>
<dt id="meshtastic.globals.Globals.get_target_node"><code class="name flex">
<span>def <span class="ident">get_target_node</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Get target_node</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_target_node(self):
&#34;&#34;&#34;Get target_node&#34;&#34;&#34;
return self.target_node</code></pre>
</details>
</dd>
<dt id="meshtastic.globals.Globals.reset"><code class="name flex">
<span>def <span class="ident">reset</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Reset all of our globals. If you add a member, add it to this method, too.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def reset(self):
&#34;&#34;&#34;Reset all of our globals. If you add a member, add it to this method, too.&#34;&#34;&#34;
self.args = None
self.parser = None
self.target_node = None
self.channel_index = None</code></pre>
</details>
</dd>
<dt id="meshtastic.globals.Globals.set_args"><code class="name flex">
<span>def <span class="ident">set_args</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<div class="desc"><p>Set the args</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def set_args(self, args):
&#34;&#34;&#34;Set the args&#34;&#34;&#34;
self.args = args</code></pre>
</details>
</dd>
<dt id="meshtastic.globals.Globals.set_channel_index"><code class="name flex">
<span>def <span class="ident">set_channel_index</span></span>(<span>self, channel_index)</span>
</code></dt>
<dd>
<div class="desc"><p>Set the channel_index</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def set_channel_index(self, channel_index):
&#34;&#34;&#34;Set the channel_index&#34;&#34;&#34;
self.channel_index = channel_index</code></pre>
</details>
</dd>
<dt id="meshtastic.globals.Globals.set_parser"><code class="name flex">
<span>def <span class="ident">set_parser</span></span>(<span>self, parser)</span>
</code></dt>
<dd>
<div class="desc"><p>Set the parser</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def set_parser(self, parser):
&#34;&#34;&#34;Set the parser&#34;&#34;&#34;
self.parser = parser</code></pre>
</details>
</dd>
<dt id="meshtastic.globals.Globals.set_target_node"><code class="name flex">
<span>def <span class="ident">set_target_node</span></span>(<span>self, target_node)</span>
</code></dt>
<dd>
<div class="desc"><p>Set the target_node</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def set_target_node(self, target_node):
&#34;&#34;&#34;Set the target_node&#34;&#34;&#34;
self.target_node = target_node</code></pre>
</details>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic" href="index.html">meshtastic</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="meshtastic.globals.Globals" href="#meshtastic.globals.Globals">Globals</a></code></h4>
<ul class="two-column">
<li><code><a title="meshtastic.globals.Globals.getInstance" href="#meshtastic.globals.Globals.getInstance">getInstance</a></code></li>
<li><code><a title="meshtastic.globals.Globals.get_args" href="#meshtastic.globals.Globals.get_args">get_args</a></code></li>
<li><code><a title="meshtastic.globals.Globals.get_channel_index" href="#meshtastic.globals.Globals.get_channel_index">get_channel_index</a></code></li>
<li><code><a title="meshtastic.globals.Globals.get_parser" href="#meshtastic.globals.Globals.get_parser">get_parser</a></code></li>
<li><code><a title="meshtastic.globals.Globals.get_target_node" href="#meshtastic.globals.Globals.get_target_node">get_target_node</a></code></li>
<li><code><a title="meshtastic.globals.Globals.reset" href="#meshtastic.globals.Globals.reset">reset</a></code></li>
<li><code><a title="meshtastic.globals.Globals.set_args" href="#meshtastic.globals.Globals.set_args">set_args</a></code></li>
<li><code><a title="meshtastic.globals.Globals.set_channel_index" href="#meshtastic.globals.Globals.set_channel_index">set_channel_index</a></code></li>
<li><code><a title="meshtastic.globals.Globals.set_parser" href="#meshtastic.globals.Globals.set_parser">set_parser</a></code></li>
<li><code><a title="meshtastic.globals.Globals.set_target_node" href="#meshtastic.globals.Globals.set_target_node">set_target_node</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because one or more lines are too long

View File

@@ -3,9 +3,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.9.2" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.mqtt_pb2 API documentation</title>
<meta name="description" content="Generated protocol buffer code." />
<meta name="description" content="" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
@@ -22,7 +22,6 @@
<h1 class="title">Module <code>meshtastic.mqtt_pb2</code></h1>
</header>
<section id="section-intro">
<p>Generated protocol buffer code.</p>
<details class="source">
<summary>
<span>Expand source code</span>
@@ -30,7 +29,7 @@
<pre><code class="python"># -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: mqtt.proto
&#34;&#34;&#34;Generated protocol buffer code.&#34;&#34;&#34;
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
@@ -48,7 +47,6 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package=&#39;&#39;,
syntax=&#39;proto3&#39;,
serialized_options=b&#39;\n\023com.geeksville.meshB\nMQTTProtosH\003Z!github.com/meshtastic/gomeshproto&#39;,
create_key=_descriptor._internal_create_key,
serialized_pb=b&#39;\n\nmqtt.proto\x1a\nmesh.proto\&#34;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&#39;
,
dependencies=[mesh__pb2.DESCRIPTOR,])
@@ -62,7 +60,6 @@ _SERVICEENVELOPE = _descriptor.Descriptor(
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name=&#39;packet&#39;, full_name=&#39;ServiceEnvelope.packet&#39;, index=0,
@@ -70,21 +67,21 @@ _SERVICEENVELOPE = _descriptor.Descriptor(
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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;channel_id&#39;, full_name=&#39;ServiceEnvelope.channel_id&#39;, index=1,
number=2, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b&#34;&#34;.decode(&#39;utf-8&#39;),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;gateway_id&#39;, full_name=&#39;ServiceEnvelope.gateway_id&#39;, index=2,
number=3, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b&#34;&#34;.decode(&#39;utf-8&#39;),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
@@ -128,35 +125,580 @@ DESCRIPTOR._options = None
<dl>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope"><code class="flex name class">
<span>class <span class="ident">ServiceEnvelope</span></span>
<span>(</span><span>*args, **kwargs)</span>
<span>(</span><span>**kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<div class="desc"><p>Abstract base class for protocol messages.</p>
<p>Protocol message classes are almost always generated by the protocol
compiler.
These generated types subclass Message and implement the methods
shown below.</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google.protobuf.pyext._message.CMessage</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.CHANNEL_ID_FIELD_NUMBER"><code class="name">var <span class="ident">CHANNEL_ID_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.GATEWAY_ID_FIELD_NUMBER"><code class="name">var <span class="ident">GATEWAY_ID_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.PACKET_FIELD_NUMBER"><code class="name">var <span class="ident">PACKET_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
<h3>Static methods</h3>
<dl>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.FromString"><code class="name flex">
<span>def <span class="ident">FromString</span></span>(<span>s)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def FromString(s):
message = cls()
message.MergeFromString(s)
return message</code></pre>
</details>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.RegisterExtension"><code class="name flex">
<span>def <span class="ident">RegisterExtension</span></span>(<span>extension_handle)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def RegisterExtension(extension_handle):
extension_handle.containing_type = cls.DESCRIPTOR
# TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
# pylint: disable=protected-access
cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
_AttachFieldHelpers(cls, extension_handle)</code></pre>
</details>
</dd>
</dl>
<h3>Instance variables</h3>
<dl>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.channel_id"><code class="name">var <span class="ident">channel_id</span></code></dt>
<dd>
<div class="desc"><p>Field ServiceEnvelope.channel_id</p></div>
<div class="desc"><p>Getter for channel_id.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.gateway_id"><code class="name">var <span class="ident">gateway_id</span></code></dt>
<dd>
<div class="desc"><p>Field ServiceEnvelope.gateway_id</p></div>
<div class="desc"><p>Getter for gateway_id.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.packet"><code class="name">var <span class="ident">packet</span></code></dt>
<dd>
<div class="desc"><p>Field ServiceEnvelope.packet</p></div>
<div class="desc"><p>Getter for packet.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
field_value = self._fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
# Atomically check if another thread has preempted us and, if not, swap
# in the new object we just created. If someone has preempted us, we
# take that object and discard ours.
# WARNING: We are relying on setdefault() being atomic. This is true
# in CPython but we haven&#39;t investigated others. This warning appears
# in several other locations in this file.
field_value = self._fields.setdefault(field, field_value)
return field_value</code></pre>
</details>
</dd>
</dl>
<h3>Methods</h3>
<dl>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.ByteSize"><code class="name flex">
<span>def <span class="ident">ByteSize</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ByteSize(self):
if not self._cached_byte_size_dirty:
return self._cached_byte_size
size = 0
descriptor = self.DESCRIPTOR
if descriptor.GetOptions().map_entry:
# Fields of map entry should always be serialized.
size = descriptor.fields_by_name[&#39;key&#39;]._sizer(self.key)
size += descriptor.fields_by_name[&#39;value&#39;]._sizer(self.value)
else:
for field_descriptor, field_value in self.ListFields():
size += field_descriptor._sizer(field_value)
for tag_bytes, value_bytes in self._unknown_fields:
size += len(tag_bytes) + len(value_bytes)
self._cached_byte_size = size
self._cached_byte_size_dirty = False
self._listener_for_children.dirty = False
return size</code></pre>
</details>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.Clear"><code class="name flex">
<span>def <span class="ident">Clear</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def _Clear(self):
# Clear fields.
self._fields = {}
self._unknown_fields = ()
# pylint: disable=protected-access
if self._unknown_field_set is not None:
self._unknown_field_set._clear()
self._unknown_field_set = None
self._oneofs = {}
self._Modified()</code></pre>
</details>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.ClearField"><code class="name flex">
<span>def <span class="ident">ClearField</span></span>(<span>self, field_name)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ClearField(self, field_name):
try:
field = message_descriptor.fields_by_name[field_name]
except KeyError:
try:
field = message_descriptor.oneofs_by_name[field_name]
if field in self._oneofs:
field = self._oneofs[field]
else:
return
except KeyError:
raise ValueError(&#39;Protocol message %s has no &#34;%s&#34; field.&#39; %
(message_descriptor.name, field_name))
if field in self._fields:
# To match the C++ implementation, we need to invalidate iterators
# for map fields when ClearField() happens.
if hasattr(self._fields[field], &#39;InvalidateIterators&#39;):
self._fields[field].InvalidateIterators()
# Note: If the field is a sub-message, its listener will still point
# at us. That&#39;s fine, because the worst than can happen is that it
# will call _Modified() and invalidate our byte size. Big deal.
del self._fields[field]
if self._oneofs.get(field.containing_oneof, None) is field:
del self._oneofs[field.containing_oneof]
# Always call _Modified() -- even if nothing was changed, this is
# a mutating method, and thus calling it should cause the field to become
# present in the parent message.
self._Modified()</code></pre>
</details>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.DiscardUnknownFields"><code class="name flex">
<span>def <span class="ident">DiscardUnknownFields</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def _DiscardUnknownFields(self):
self._unknown_fields = []
self._unknown_field_set = None # pylint: disable=protected-access
for field, value in self.ListFields():
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
if _IsMapField(field):
if _IsMessageMapField(field):
for key in value:
value[key].DiscardUnknownFields()
elif field.label == _FieldDescriptor.LABEL_REPEATED:
for sub_message in value:
sub_message.DiscardUnknownFields()
else:
value.DiscardUnknownFields()</code></pre>
</details>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.FindInitializationErrors"><code class="name flex">
<span>def <span class="ident">FindInitializationErrors</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Finds required fields which are not initialized.</p>
<h2 id="returns">Returns</h2>
<p>A list of strings.
Each string is a path to an uninitialized field from
the top-level message, e.g. "foo.bar[5].baz".</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def FindInitializationErrors(self):
&#34;&#34;&#34;Finds required fields which are not initialized.
Returns:
A list of strings. Each string is a path to an uninitialized field from
the top-level message, e.g. &#34;foo.bar[5].baz&#34;.
&#34;&#34;&#34;
errors = [] # simplify things
for field in required_fields:
if not self.HasField(field.name):
errors.append(field.name)
for field, value in self.ListFields():
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
if field.is_extension:
name = &#39;(%s)&#39; % field.full_name
else:
name = field.name
if _IsMapField(field):
if _IsMessageMapField(field):
for key in value:
element = value[key]
prefix = &#39;%s[%s].&#39; % (name, key)
sub_errors = element.FindInitializationErrors()
errors += [prefix + error for error in sub_errors]
else:
# ScalarMaps can&#39;t have any initialization errors.
pass
elif field.label == _FieldDescriptor.LABEL_REPEATED:
for i in range(len(value)):
element = value[i]
prefix = &#39;%s[%d].&#39; % (name, i)
sub_errors = element.FindInitializationErrors()
errors += [prefix + error for error in sub_errors]
else:
prefix = name + &#39;.&#39;
sub_errors = value.FindInitializationErrors()
errors += [prefix + error for error in sub_errors]
return errors</code></pre>
</details>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.HasField"><code class="name flex">
<span>def <span class="ident">HasField</span></span>(<span>self, field_name)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def HasField(self, field_name):
try:
field = hassable_fields[field_name]
except KeyError:
raise ValueError(error_msg % (message_descriptor.full_name, field_name))
if isinstance(field, descriptor_mod.OneofDescriptor):
try:
return HasField(self, self._oneofs[field].name)
except KeyError:
return False
else:
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
value = self._fields.get(field)
return value is not None and value._is_present_in_parent
else:
return field in self._fields</code></pre>
</details>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.IsInitialized"><code class="name flex">
<span>def <span class="ident">IsInitialized</span></span>(<span>self, errors=None)</span>
</code></dt>
<dd>
<div class="desc"><p>Checks if all required fields of a message are set.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>errors</code></strong></dt>
<dd>A list which, if provided, will be populated with the field
paths of all missing required fields.</dd>
</dl>
<h2 id="returns">Returns</h2>
<p>True iff the specified message has all required fields set.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def IsInitialized(self, errors=None):
&#34;&#34;&#34;Checks if all required fields of a message are set.
Args:
errors: A list which, if provided, will be populated with the field
paths of all missing required fields.
Returns:
True iff the specified message has all required fields set.
&#34;&#34;&#34;
# Performance is critical so we avoid HasField() and ListFields().
for field in required_fields:
if (field not in self._fields or
(field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
not self._fields[field]._is_present_in_parent)):
if errors is not None:
errors.extend(self.FindInitializationErrors())
return False
for field, value in list(self._fields.items()): # dict can change size!
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
if field.label == _FieldDescriptor.LABEL_REPEATED:
if (field.message_type.has_options and
field.message_type.GetOptions().map_entry):
continue
for element in value:
if not element.IsInitialized():
if errors is not None:
errors.extend(self.FindInitializationErrors())
return False
elif value._is_present_in_parent and not value.IsInitialized():
if errors is not None:
errors.extend(self.FindInitializationErrors())
return False
return True</code></pre>
</details>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.ListFields"><code class="name flex">
<span>def <span class="ident">ListFields</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ListFields(self):
all_fields = [item for item in self._fields.items() if _IsPresent(item)]
all_fields.sort(key = lambda item: item[0].number)
return all_fields</code></pre>
</details>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.MergeFrom"><code class="name flex">
<span>def <span class="ident">MergeFrom</span></span>(<span>self, msg)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def MergeFrom(self, msg):
if not isinstance(msg, cls):
raise TypeError(
&#39;Parameter to MergeFrom() must be instance of same class: &#39;
&#39;expected %s got %s.&#39; % (_FullyQualifiedClassName(cls),
_FullyQualifiedClassName(msg.__class__)))
assert msg is not self
self._Modified()
fields = self._fields
for field, value in msg._fields.items():
if field.label == LABEL_REPEATED:
field_value = fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
fields[field] = field_value
field_value.MergeFrom(value)
elif field.cpp_type == CPPTYPE_MESSAGE:
if value._is_present_in_parent:
field_value = fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
fields[field] = field_value
field_value.MergeFrom(value)
else:
self._fields[field] = value
if field.containing_oneof:
self._UpdateOneofState(field)
if msg._unknown_fields:
if not self._unknown_fields:
self._unknown_fields = []
self._unknown_fields.extend(msg._unknown_fields)
# pylint: disable=protected-access
if self._unknown_field_set is None:
self._unknown_field_set = containers.UnknownFieldSet()
self._unknown_field_set._extend(msg._unknown_field_set)</code></pre>
</details>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.MergeFromString"><code class="name flex">
<span>def <span class="ident">MergeFromString</span></span>(<span>self, serialized)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def MergeFromString(self, serialized):
serialized = memoryview(serialized)
length = len(serialized)
try:
if self._InternalParse(serialized, 0, length) != length:
# The only reason _InternalParse would return early is if it
# encountered an end-group tag.
raise message_mod.DecodeError(&#39;Unexpected end-group tag.&#39;)
except (IndexError, TypeError):
# Now ord(buf[p:p+1]) == ord(&#39;&#39;) gets TypeError.
raise message_mod.DecodeError(&#39;Truncated message.&#39;)
except struct.error as e:
raise message_mod.DecodeError(e)
return length # Return this for legacy reasons.</code></pre>
</details>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.SerializePartialToString"><code class="name flex">
<span>def <span class="ident">SerializePartialToString</span></span>(<span>self, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def SerializePartialToString(self, **kwargs):
out = BytesIO()
self._InternalSerialize(out.write, **kwargs)
return out.getvalue()</code></pre>
</details>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.SerializeToString"><code class="name flex">
<span>def <span class="ident">SerializeToString</span></span>(<span>self, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def SerializeToString(self, **kwargs):
# Check if the message has all of its required fields set.
if not self.IsInitialized():
raise message_mod.EncodeError(
&#39;Message %s is missing required fields: %s&#39; % (
self.DESCRIPTOR.full_name, &#39;,&#39;.join(self.FindInitializationErrors())))
return self.SerializePartialToString(**kwargs)</code></pre>
</details>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.SetInParent"><code class="name flex">
<span>def <span class="ident">SetInParent</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Sets the _cached_byte_size_dirty bit to true,
and propagates this to our listener iff this was a state change.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def Modified(self):
&#34;&#34;&#34;Sets the _cached_byte_size_dirty bit to true,
and propagates this to our listener iff this was a state change.
&#34;&#34;&#34;
# Note: Some callers check _cached_byte_size_dirty before calling
# _Modified() as an extra optimization. So, if this method is ever
# changed such that it does stuff even when _cached_byte_size_dirty is
# already true, the callers need to be updated.
if not self._cached_byte_size_dirty:
self._cached_byte_size_dirty = True
self._listener_for_children.dirty = True
self._is_present_in_parent = True
self._listener.Modified()</code></pre>
</details>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.UnknownFields"><code class="name flex">
<span>def <span class="ident">UnknownFields</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def _UnknownFields(self):
if self._unknown_field_set is None: # pylint: disable=protected-access
# pylint: disable=protected-access
self._unknown_field_set = containers.UnknownFieldSet()
return self._unknown_field_set # pylint: disable=protected-access</code></pre>
</details>
</dd>
<dt id="meshtastic.mqtt_pb2.ServiceEnvelope.WhichOneof"><code class="name flex">
<span>def <span class="ident">WhichOneof</span></span>(<span>self, oneof_name)</span>
</code></dt>
<dd>
<div class="desc"><p>Returns the name of the currently set field inside a oneof, or None.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def WhichOneof(self, oneof_name):
&#34;&#34;&#34;Returns the name of the currently set field inside a oneof, or None.&#34;&#34;&#34;
try:
field = message_descriptor.oneofs_by_name[oneof_name]
except KeyError:
raise ValueError(
&#39;Protocol message has no oneof &#34;%s&#34; field.&#39; % oneof_name)
nested_field = self._oneofs.get(field, None)
if nested_field is not None and self.HasField(nested_field.name):
return nested_field.name
else:
return None</code></pre>
</details>
</dd>
</dl>
</dd>
@@ -179,7 +721,27 @@ DESCRIPTOR._options = None
<li>
<h4><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope" href="#meshtastic.mqtt_pb2.ServiceEnvelope">ServiceEnvelope</a></code></h4>
<ul class="">
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.ByteSize" href="#meshtastic.mqtt_pb2.ServiceEnvelope.ByteSize">ByteSize</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.CHANNEL_ID_FIELD_NUMBER" href="#meshtastic.mqtt_pb2.ServiceEnvelope.CHANNEL_ID_FIELD_NUMBER">CHANNEL_ID_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.Clear" href="#meshtastic.mqtt_pb2.ServiceEnvelope.Clear">Clear</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.ClearField" href="#meshtastic.mqtt_pb2.ServiceEnvelope.ClearField">ClearField</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.DESCRIPTOR" href="#meshtastic.mqtt_pb2.ServiceEnvelope.DESCRIPTOR">DESCRIPTOR</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.DiscardUnknownFields" href="#meshtastic.mqtt_pb2.ServiceEnvelope.DiscardUnknownFields">DiscardUnknownFields</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.FindInitializationErrors" href="#meshtastic.mqtt_pb2.ServiceEnvelope.FindInitializationErrors">FindInitializationErrors</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.FromString" href="#meshtastic.mqtt_pb2.ServiceEnvelope.FromString">FromString</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.GATEWAY_ID_FIELD_NUMBER" href="#meshtastic.mqtt_pb2.ServiceEnvelope.GATEWAY_ID_FIELD_NUMBER">GATEWAY_ID_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.HasField" href="#meshtastic.mqtt_pb2.ServiceEnvelope.HasField">HasField</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.IsInitialized" href="#meshtastic.mqtt_pb2.ServiceEnvelope.IsInitialized">IsInitialized</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.ListFields" href="#meshtastic.mqtt_pb2.ServiceEnvelope.ListFields">ListFields</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.MergeFrom" href="#meshtastic.mqtt_pb2.ServiceEnvelope.MergeFrom">MergeFrom</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.MergeFromString" href="#meshtastic.mqtt_pb2.ServiceEnvelope.MergeFromString">MergeFromString</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.PACKET_FIELD_NUMBER" href="#meshtastic.mqtt_pb2.ServiceEnvelope.PACKET_FIELD_NUMBER">PACKET_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.RegisterExtension" href="#meshtastic.mqtt_pb2.ServiceEnvelope.RegisterExtension">RegisterExtension</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.SerializePartialToString" href="#meshtastic.mqtt_pb2.ServiceEnvelope.SerializePartialToString">SerializePartialToString</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.SerializeToString" href="#meshtastic.mqtt_pb2.ServiceEnvelope.SerializeToString">SerializeToString</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.SetInParent" href="#meshtastic.mqtt_pb2.ServiceEnvelope.SetInParent">SetInParent</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.UnknownFields" href="#meshtastic.mqtt_pb2.ServiceEnvelope.UnknownFields">UnknownFields</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.WhichOneof" href="#meshtastic.mqtt_pb2.ServiceEnvelope.WhichOneof">WhichOneof</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.channel_id" href="#meshtastic.mqtt_pb2.ServiceEnvelope.channel_id">channel_id</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.gateway_id" href="#meshtastic.mqtt_pb2.ServiceEnvelope.gateway_id">gateway_id</a></code></li>
<li><code><a title="meshtastic.mqtt_pb2.ServiceEnvelope.packet" href="#meshtastic.mqtt_pb2.ServiceEnvelope.packet">packet</a></code></li>
@@ -191,7 +753,7 @@ DESCRIPTOR._options = None
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc"><cite>pdoc</cite> 0.9.2</a>.</p>
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -3,9 +3,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.9.2" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.node API documentation</title>
<meta name="description" content="an API for Meshtastic devices …" />
<meta name="description" content="Node class" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
@@ -22,161 +22,19 @@
<h1 class="title">Module <code>meshtastic.node</code></h1>
</header>
<section id="section-intro">
<h1 id="an-api-for-meshtastic-devices">an API for Meshtastic devices</h1>
<p>Primary class: SerialInterface
Install with pip: "<a href="https://pypi.org/project/meshtastic/">pip3 install meshtastic</a>"
Source code on <a href="https://github.com/meshtastic/Meshtastic-python">github</a></p>
<p>properties of SerialInterface:</p>
<ul>
<li>radioConfig - Current radio configuration and device settings, if you write to this the new settings will be applied to
the device.</li>
<li>nodes - The database of received nodes.
Includes always up-to-date location and username information for each
node in the mesh.
This is a read-only datastructure.</li>
<li>nodesByNum - like "nodes" but keyed by nodeNum instead of nodeId</li>
<li>myInfo - Contains read-only information about the local radio device (software version, hardware version, etc)</li>
</ul>
<h1 id="published-pubsub-topics">Published PubSub topics</h1>
<p>We use a <a href="https://pypubsub.readthedocs.io/en/v4.0.3/">publish-subscribe</a> model to communicate asynchronous events.
Available
topics:</p>
<ul>
<li>meshtastic.connection.established - published once we've successfully connected to the radio and downloaded the node DB</li>
<li>meshtastic.connection.lost - published once we've lost our link to the radio</li>
<li>meshtastic.receive.text(packet) - delivers a received packet as a dictionary, if you only care about a particular
type of packet, you should subscribe to the full topic name.
If you want to see all packets, simply subscribe to "meshtastic.receive".</li>
<li>meshtastic.receive.position(packet)</li>
<li>meshtastic.receive.user(packet)</li>
<li>meshtastic.receive.data.portnum(packet) (where portnum is an integer or well known PortNum enum)</li>
<li>meshtastic.node.updated(node = NodeInfo) - published when a node in the DB changes (appears, location changed, username changed, etc&hellip;)</li>
</ul>
<p>We receive position, user, or data packets from the mesh.
You probably only care about meshtastic.receive.data.
The first argument for
that publish will be the packet.
Text or binary data packets (from sendData or sendText) will both arrive this way.
If you print packet
you'll see the fields in the dictionary.
decoded.data.payload will contain the raw bytes that were sent.
If the packet was sent with
sendText, decoded.data.text will <strong>also</strong> be populated with the decoded string.
For ASCII these two strings will be the same, but for
unicode scripts they can be different.</p>
<h1 id="example-usage">Example Usage</h1>
<pre><code>import meshtastic
from pubsub import pub
def onReceive(packet, interface): # called when a packet arrives
print(f&quot;Received: {packet}&quot;)
def onConnection(interface, topic=pub.AUTO_TOPIC): # called when we (re)connect to the radio
# defaults to broadcast, specify a destination ID if you wish
interface.sendText(&quot;hello mesh&quot;)
pub.subscribe(onReceive, &quot;meshtastic.receive&quot;)
pub.subscribe(onConnection, &quot;meshtastic.connection.established&quot;)
# By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0
interface = meshtastic.SerialInterface()
</code></pre>
<p>Node class</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;
# an API for Meshtastic devices
Primary class: SerialInterface
Install with pip: &#34;[pip3 install meshtastic](https://pypi.org/project/meshtastic/)&#34;
Source code on [github](https://github.com/meshtastic/Meshtastic-python)
properties of SerialInterface:
- radioConfig - Current radio configuration and device settings, if you write to this the new settings will be applied to
the device.
- nodes - The database of received nodes. Includes always up-to-date location and username information for each
node in the mesh. This is a read-only datastructure.
- nodesByNum - like &#34;nodes&#34; but keyed by nodeNum instead of nodeId
- myInfo - Contains read-only information about the local radio device (software version, hardware version, etc)
# Published PubSub topics
We use a [publish-subscribe](https://pypubsub.readthedocs.io/en/v4.0.3/) model to communicate asynchronous events. Available
topics:
- meshtastic.connection.established - published once we&#39;ve successfully connected to the radio and downloaded the node DB
- meshtastic.connection.lost - published once we&#39;ve lost our link to the radio
- meshtastic.receive.text(packet) - delivers a received packet as a dictionary, if you only care about a particular
type of packet, you should subscribe to the full topic name. If you want to see all packets, simply subscribe to &#34;meshtastic.receive&#34;.
- meshtastic.receive.position(packet)
- meshtastic.receive.user(packet)
- meshtastic.receive.data.portnum(packet) (where portnum is an integer or well known PortNum enum)
- meshtastic.node.updated(node = NodeInfo) - published when a node in the DB changes (appears, location changed, username changed, etc...)
We receive position, user, or data packets from the mesh. You probably only care about meshtastic.receive.data. The first argument for
that publish will be the packet. Text or binary data packets (from sendData or sendText) will both arrive this way. If you print packet
you&#39;ll see the fields in the dictionary. decoded.data.payload will contain the raw bytes that were sent. If the packet was sent with
sendText, decoded.data.text will **also** be populated with the decoded string. For ASCII these two strings will be the same, but for
unicode scripts they can be different.
# Example Usage
```
import meshtastic
from pubsub import pub
def onReceive(packet, interface): # called when a packet arrives
print(f&#34;Received: {packet}&#34;)
def onConnection(interface, topic=pub.AUTO_TOPIC): # called when we (re)connect to the radio
# defaults to broadcast, specify a destination ID if you wish
interface.sendText(&#34;hello mesh&#34;)
pub.subscribe(onReceive, &#34;meshtastic.receive&#34;)
pub.subscribe(onConnection, &#34;meshtastic.connection.established&#34;)
# By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0
interface = meshtastic.SerialInterface()
```
<pre><code class="python">&#34;&#34;&#34;Node class
&#34;&#34;&#34;
import pygatt
import google.protobuf.json_format
import serial
import threading
import logging
import sys
import random
import traceback
import time
import base64
import platform
import socket
from . import mesh_pb2, portnums_pb2, apponly_pb2, admin_pb2, environmental_measurement_pb2, remote_hardware_pb2, channel_pb2, radioconfig_pb2, util
from .util import fixme, catchAndIgnore, stripnl, DeferredExecution, Timeout
from pubsub import pub
from dotmap import DotMap
from typing import *
from google.protobuf.json_format import MessageToJson
def pskToString(psk: bytes):
&#34;&#34;&#34;Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string&#34;&#34;&#34;
if len(psk) == 0:
return &#34;unencrypted&#34;
elif len(psk) == 1:
b = psk[0]
if b == 0:
return &#34;unencrypted&#34;
elif b == 1:
return &#34;default&#34;
else:
return f&#34;simple{b - 1}&#34;
else:
return &#34;secret&#34;
from . import portnums_pb2, apponly_pb2, admin_pb2, channel_pb2
from .util import pskToString, stripnl, Timeout, our_exit, fromPSK
class Node:
@@ -192,15 +50,17 @@ class Node:
self.radioConfig = None
self.channels = None
self._timeout = Timeout(maxSecs=60)
self.partialChannels = None
def showChannels(self):
&#34;&#34;&#34;Show human readable description of our channels&#34;&#34;&#34;
&#34;&#34;&#34;Show human readable description of our channels.&#34;&#34;&#34;
print(&#34;Channels:&#34;)
for c in self.channels:
if c.role != channel_pb2.Channel.Role.DISABLED:
if self.channels:
for c in self.channels:
cStr = stripnl(MessageToJson(c.settings))
print(
f&#34; {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}&#34;)
# only show if there is no psk (meaning disabled channel)
if c.settings.psk:
print(f&#34; {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}&#34;)
publicURL = self.getURL(includeAll=False)
adminURL = self.getURL(includeAll=True)
print(f&#34;\nPrimary channel URL: {publicURL}&#34;)
@@ -209,28 +69,34 @@ class Node:
def showInfo(self):
&#34;&#34;&#34;Show human readable description of our node&#34;&#34;&#34;
print(
f&#34;Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n&#34;)
prefs = &#34;&#34;
if self.radioConfig and self.radioConfig.preferences:
prefs = stripnl(MessageToJson(self.radioConfig.preferences))
print(f&#34;Preferences: {prefs}\n&#34;)
self.showChannels()
def requestConfig(self):
&#34;&#34;&#34;
Send regular MeshPackets to ask for settings and channels
&#34;&#34;&#34;
&#34;&#34;&#34;Send regular MeshPackets to ask for settings and channels.&#34;&#34;&#34;
self.radioConfig = None
self.channels = None
self.partialChannels = [] # We keep our channels in a temp array until finished
self._requestSettings()
def turnOffEncryptionOnPrimaryChannel(self):
&#34;&#34;&#34;Turn off encryption on primary channel.&#34;&#34;&#34;
self.channels[0].settings.psk = fromPSK(&#34;none&#34;)
print(&#34;Writing modified channels to device&#34;)
self.writeChannel(0)
def waitForConfig(self):
&#34;&#34;&#34;Block until radio config is received. Returns True if config has been received.&#34;&#34;&#34;
return self._timeout.waitForSet(self, attrs=(&#39;radioConfig&#39;, &#39;channels&#39;))
def writeConfig(self):
&#34;&#34;&#34;Write the current (edited) radioConfig to the device&#34;&#34;&#34;
if self.radioConfig == None:
raise Exception(&#34;No RadioConfig has been read&#34;)
if self.radioConfig is None:
our_exit(&#34;Error: No RadioConfig has been read&#34;)
p = admin_pb2.AdminMessage()
p.set_radio.CopyFrom(self.radioConfig)
@@ -250,8 +116,8 @@ class Node:
def deleteChannel(self, channelIndex):
&#34;&#34;&#34;Delete the specifed channelIndex and shift other channels up&#34;&#34;&#34;
ch = self.channels[channelIndex]
if ch.role != channel_pb2.Channel.Role.SECONDARY:
raise Exception(&#34;Only SECONDARY channels can be deleted&#34;)
if ch.role not in (channel_pb2.Channel.Role.SECONDARY, channel_pb2.Channel.Role.DISABLED):
our_exit(&#34;Warning: Only SECONDARY channels can be deleted&#34;)
# we are careful here because if we move the &#34;admin&#34; channel the channelIndex we need to use
# for sending admin channels will also change
@@ -265,9 +131,11 @@ class Node:
self.writeChannel(index, adminIndex=adminIndex)
index += 1
# if we are updating the local node, we might end up *moving* the admin channel index as we are writing
# if we are updating the local node, we might end up
# *moving* the admin channel index as we are writing
if (self.iface.localNode == self) and index &gt;= adminIndex:
# We&#39;ve now passed the old location for admin index (and writen it), so we can start finding it by name again
# We&#39;ve now passed the old location for admin index
# (and writen it), so we can start finding it by name again
adminIndex = 0
def getChannelByName(self, name):
@@ -292,7 +160,7 @@ class Node:
else:
return 0
def setOwner(self, long_name, short_name=None, is_licensed=False):
def setOwner(self, long_name=None, short_name=None, is_licensed=False, team=None):
&#34;&#34;&#34;Set device owner name&#34;&#34;&#34;
nChars = 3
minChars = 2
@@ -320,25 +188,27 @@ class Node:
short_name = short_name[:nChars]
p.set_owner.short_name = short_name
p.set_owner.is_licensed = is_licensed
if team is not None:
p.set_owner.team = team
return self._sendAdmin(p)
def getURL(self, includeAll: bool = True):
&#34;&#34;&#34;The sharable URL that describes the current channel
&#34;&#34;&#34;
&#34;&#34;&#34;The sharable URL that describes the current channel&#34;&#34;&#34;
# Only keep the primary/secondary channels, assume primary is first
channelSet = apponly_pb2.ChannelSet()
for c in self.channels:
if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY):
channelSet.settings.append(c.settings)
bytes = channelSet.SerializeToString()
s = base64.urlsafe_b64encode(bytes).decode(&#39;ascii&#39;)
if self.channels:
for c in self.channels:
if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY):
channelSet.settings.append(c.settings)
some_bytes = channelSet.SerializeToString()
s = base64.urlsafe_b64encode(some_bytes).decode(&#39;ascii&#39;)
return f&#34;https://www.meshtastic.org/d/#{s}&#34;.replace(&#34;=&#34;, &#34;&#34;)
def setURL(self, url):
&#34;&#34;&#34;Set mesh network URL&#34;&#34;&#34;
if self.radioConfig == None:
raise Exception(&#34;No RadioConfig has been read&#34;)
if self.radioConfig is None:
our_exit(&#34;Warning: No RadioConfig has been read&#34;)
# URLs are of the form https://www.meshtastic.org/d/#{base64_channel_set}
# Split on &#39;/#&#39; to find the base64 encoded channel settings
@@ -356,6 +226,9 @@ class Node:
channelSet = apponly_pb2.ChannelSet()
channelSet.ParseFromString(decodedURL)
if len(channelSet.settings) == 0:
our_exit(&#34;Warning: There were no settings.&#34;)
i = 0
for chs in channelSet.settings:
ch = channel_pb2.Channel()
@@ -367,41 +240,40 @@ class Node:
i = i + 1
def _requestSettings(self):
&#34;&#34;&#34;
Done with initial config messages, now send regular MeshPackets to ask for settings
&#34;&#34;&#34;
&#34;&#34;&#34;Done with initial config messages, now send regular
MeshPackets to ask for settings.&#34;&#34;&#34;
p = admin_pb2.AdminMessage()
p.get_radio_request = True
def onResponse(p):
&#34;&#34;&#34;A closure to handle the response packet&#34;&#34;&#34;
self.radioConfig = p[&#34;decoded&#34;][&#34;admin&#34;][&#34;raw&#34;].get_radio_response
logging.debug(&#34;Received radio config, now fetching channels...&#34;)
self._timeout.reset() # We made foreward progress
self._requestChannel(0) # now start fetching channels
errorFound = False
if &#39;routing&#39; in p[&#34;decoded&#34;]:
if p[&#34;decoded&#34;][&#34;routing&#34;][&#34;errorReason&#34;] != &#34;NONE&#34;:
errorFound = True
print(f&#39;Error on response: {p[&#34;decoded&#34;][&#34;routing&#34;][&#34;errorReason&#34;]}&#39;)
if errorFound is False:
self.radioConfig = p[&#34;decoded&#34;][&#34;admin&#34;][&#34;raw&#34;].get_radio_response
logging.debug(&#34;Received radio config, now fetching channels...&#34;)
self._timeout.reset() # We made foreward progress
self._requestChannel(0) # now start fetching channels
# Show progress message for super slow operations
if self != self.iface.localNode:
logging.info(
&#34;Requesting preferences from remote node (this could take a while)&#34;)
print(&#34;Requesting preferences from remote node (this could take a while)&#34;)
return self._sendAdmin(p,
wantResponse=True,
onResponse=onResponse)
return self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
def exitSimulator(self):
&#34;&#34;&#34;
Tell a simulator node to exit (this message is ignored for other nodes)
&#34;&#34;&#34;
&#34;&#34;&#34;Tell a simulator node to exit (this message
is ignored for other nodes)&#34;&#34;&#34;
p = admin_pb2.AdminMessage()
p.exit_simulator = True
return self._sendAdmin(p)
def reboot(self, secs: int = 10):
&#34;&#34;&#34;
Tell the node to reboot
&#34;&#34;&#34;
&#34;&#34;&#34;Tell the node to reboot.&#34;&#34;&#34;
p = admin_pb2.AdminMessage()
p.reboot_seconds = secs
logging.info(f&#34;Telling node to reboot in {secs} seconds&#34;)
@@ -412,6 +284,7 @@ class Node:
&#34;&#34;&#34;Fixup indexes and add disabled channels as needed&#34;&#34;&#34;
# Add extra disabled channels as needed
# TODO: These 2 lines seem to not do anything.
for index, ch in enumerate(self.channels):
ch.index = index # fixup indexes
@@ -430,21 +303,19 @@ class Node:
index += 1
def _requestChannel(self, channelNum: int):
&#34;&#34;&#34;
Done with initial config messages, now send regular MeshPackets to ask for settings
&#34;&#34;&#34;
&#34;&#34;&#34;Done with initial config messages, now send regular
MeshPackets to ask for settings&#34;&#34;&#34;
p = admin_pb2.AdminMessage()
p.get_channel_request = channelNum + 1
# Show progress message for super slow operations
if self != self.iface.localNode:
logging.info(
f&#34;Requesting channel {channelNum} info from remote node (this could take a while)&#34;)
logging.info(f&#34;Requesting channel {channelNum} info from remote node (this could take a while)&#34;)
else:
logging.debug(f&#34;Requesting channel {channelNum}&#34;)
def onResponse(p):
&#34;&#34;&#34;A closure to handle the response packet&#34;&#34;&#34;
&#34;&#34;&#34;A closure to handle the response packet for requesting a channel&#34;&#34;&#34;
c = p[&#34;decoded&#34;][&#34;admin&#34;][&#34;raw&#34;].get_channel_response
self.partialChannels.append(c)
self._timeout.reset() # We made foreward progress
@@ -454,9 +325,9 @@ class Node:
# for stress testing, we can always download all channels
fastChannelDownload = True
# Once we see a response that has NO settings, assume we are at the end of channels and stop fetching
quitEarly = (
c.role == channel_pb2.Channel.Role.DISABLED) and fastChannelDownload
# Once we see a response that has NO settings, assume
# we are at the end of channels and stop fetching
quitEarly = (c.role == channel_pb2.Channel.Role.DISABLED) and fastChannelDownload
if quitEarly or index &gt;= self.iface.myInfo.max_channels - 1:
logging.debug(&#34;Finished downloading channels&#34;)
@@ -469,13 +340,10 @@ class Node:
else:
self._requestChannel(index + 1)
return self._sendAdmin(p,
wantResponse=True,
onResponse=onResponse)
return self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
def _sendAdmin(self, p: admin_pb2.AdminMessage, wantResponse=False,
onResponse=None,
adminIndex=0):
onResponse=None, adminIndex=0):
&#34;&#34;&#34;Send an admin message to the specified node (or the local node if destNodeNum is zero)&#34;&#34;&#34;
if adminIndex == 0: # unless a special channel index was used, we want to use the admin index
@@ -494,34 +362,6 @@ class Node:
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.node.pskToString"><code class="name flex">
<span>def <span class="ident">pskToString</span></span>(<span>psk: bytes)</span>
</code></dt>
<dd>
<div class="desc"><p>Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def pskToString(psk: bytes):
&#34;&#34;&#34;Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string&#34;&#34;&#34;
if len(psk) == 0:
return &#34;unencrypted&#34;
elif len(psk) == 1:
b = psk[0]
if b == 0:
return &#34;unencrypted&#34;
elif b == 1:
return &#34;default&#34;
else:
return f&#34;simple{b - 1}&#34;
else:
return &#34;secret&#34;</code></pre>
</details>
</dd>
</dl>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
@@ -551,15 +391,17 @@ class Node:
self.radioConfig = None
self.channels = None
self._timeout = Timeout(maxSecs=60)
self.partialChannels = None
def showChannels(self):
&#34;&#34;&#34;Show human readable description of our channels&#34;&#34;&#34;
&#34;&#34;&#34;Show human readable description of our channels.&#34;&#34;&#34;
print(&#34;Channels:&#34;)
for c in self.channels:
if c.role != channel_pb2.Channel.Role.DISABLED:
if self.channels:
for c in self.channels:
cStr = stripnl(MessageToJson(c.settings))
print(
f&#34; {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}&#34;)
# only show if there is no psk (meaning disabled channel)
if c.settings.psk:
print(f&#34; {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}&#34;)
publicURL = self.getURL(includeAll=False)
adminURL = self.getURL(includeAll=True)
print(f&#34;\nPrimary channel URL: {publicURL}&#34;)
@@ -568,28 +410,34 @@ class Node:
def showInfo(self):
&#34;&#34;&#34;Show human readable description of our node&#34;&#34;&#34;
print(
f&#34;Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n&#34;)
prefs = &#34;&#34;
if self.radioConfig and self.radioConfig.preferences:
prefs = stripnl(MessageToJson(self.radioConfig.preferences))
print(f&#34;Preferences: {prefs}\n&#34;)
self.showChannels()
def requestConfig(self):
&#34;&#34;&#34;
Send regular MeshPackets to ask for settings and channels
&#34;&#34;&#34;
&#34;&#34;&#34;Send regular MeshPackets to ask for settings and channels.&#34;&#34;&#34;
self.radioConfig = None
self.channels = None
self.partialChannels = [] # We keep our channels in a temp array until finished
self._requestSettings()
def turnOffEncryptionOnPrimaryChannel(self):
&#34;&#34;&#34;Turn off encryption on primary channel.&#34;&#34;&#34;
self.channels[0].settings.psk = fromPSK(&#34;none&#34;)
print(&#34;Writing modified channels to device&#34;)
self.writeChannel(0)
def waitForConfig(self):
&#34;&#34;&#34;Block until radio config is received. Returns True if config has been received.&#34;&#34;&#34;
return self._timeout.waitForSet(self, attrs=(&#39;radioConfig&#39;, &#39;channels&#39;))
def writeConfig(self):
&#34;&#34;&#34;Write the current (edited) radioConfig to the device&#34;&#34;&#34;
if self.radioConfig == None:
raise Exception(&#34;No RadioConfig has been read&#34;)
if self.radioConfig is None:
our_exit(&#34;Error: No RadioConfig has been read&#34;)
p = admin_pb2.AdminMessage()
p.set_radio.CopyFrom(self.radioConfig)
@@ -609,8 +457,8 @@ class Node:
def deleteChannel(self, channelIndex):
&#34;&#34;&#34;Delete the specifed channelIndex and shift other channels up&#34;&#34;&#34;
ch = self.channels[channelIndex]
if ch.role != channel_pb2.Channel.Role.SECONDARY:
raise Exception(&#34;Only SECONDARY channels can be deleted&#34;)
if ch.role not in (channel_pb2.Channel.Role.SECONDARY, channel_pb2.Channel.Role.DISABLED):
our_exit(&#34;Warning: Only SECONDARY channels can be deleted&#34;)
# we are careful here because if we move the &#34;admin&#34; channel the channelIndex we need to use
# for sending admin channels will also change
@@ -624,9 +472,11 @@ class Node:
self.writeChannel(index, adminIndex=adminIndex)
index += 1
# if we are updating the local node, we might end up *moving* the admin channel index as we are writing
# if we are updating the local node, we might end up
# *moving* the admin channel index as we are writing
if (self.iface.localNode == self) and index &gt;= adminIndex:
# We&#39;ve now passed the old location for admin index (and writen it), so we can start finding it by name again
# We&#39;ve now passed the old location for admin index
# (and writen it), so we can start finding it by name again
adminIndex = 0
def getChannelByName(self, name):
@@ -651,7 +501,7 @@ class Node:
else:
return 0
def setOwner(self, long_name, short_name=None, is_licensed=False):
def setOwner(self, long_name=None, short_name=None, is_licensed=False, team=None):
&#34;&#34;&#34;Set device owner name&#34;&#34;&#34;
nChars = 3
minChars = 2
@@ -679,25 +529,27 @@ class Node:
short_name = short_name[:nChars]
p.set_owner.short_name = short_name
p.set_owner.is_licensed = is_licensed
if team is not None:
p.set_owner.team = team
return self._sendAdmin(p)
def getURL(self, includeAll: bool = True):
&#34;&#34;&#34;The sharable URL that describes the current channel
&#34;&#34;&#34;
&#34;&#34;&#34;The sharable URL that describes the current channel&#34;&#34;&#34;
# Only keep the primary/secondary channels, assume primary is first
channelSet = apponly_pb2.ChannelSet()
for c in self.channels:
if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY):
channelSet.settings.append(c.settings)
bytes = channelSet.SerializeToString()
s = base64.urlsafe_b64encode(bytes).decode(&#39;ascii&#39;)
if self.channels:
for c in self.channels:
if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY):
channelSet.settings.append(c.settings)
some_bytes = channelSet.SerializeToString()
s = base64.urlsafe_b64encode(some_bytes).decode(&#39;ascii&#39;)
return f&#34;https://www.meshtastic.org/d/#{s}&#34;.replace(&#34;=&#34;, &#34;&#34;)
def setURL(self, url):
&#34;&#34;&#34;Set mesh network URL&#34;&#34;&#34;
if self.radioConfig == None:
raise Exception(&#34;No RadioConfig has been read&#34;)
if self.radioConfig is None:
our_exit(&#34;Warning: No RadioConfig has been read&#34;)
# URLs are of the form https://www.meshtastic.org/d/#{base64_channel_set}
# Split on &#39;/#&#39; to find the base64 encoded channel settings
@@ -715,6 +567,9 @@ class Node:
channelSet = apponly_pb2.ChannelSet()
channelSet.ParseFromString(decodedURL)
if len(channelSet.settings) == 0:
our_exit(&#34;Warning: There were no settings.&#34;)
i = 0
for chs in channelSet.settings:
ch = channel_pb2.Channel()
@@ -726,41 +581,40 @@ class Node:
i = i + 1
def _requestSettings(self):
&#34;&#34;&#34;
Done with initial config messages, now send regular MeshPackets to ask for settings
&#34;&#34;&#34;
&#34;&#34;&#34;Done with initial config messages, now send regular
MeshPackets to ask for settings.&#34;&#34;&#34;
p = admin_pb2.AdminMessage()
p.get_radio_request = True
def onResponse(p):
&#34;&#34;&#34;A closure to handle the response packet&#34;&#34;&#34;
self.radioConfig = p[&#34;decoded&#34;][&#34;admin&#34;][&#34;raw&#34;].get_radio_response
logging.debug(&#34;Received radio config, now fetching channels...&#34;)
self._timeout.reset() # We made foreward progress
self._requestChannel(0) # now start fetching channels
errorFound = False
if &#39;routing&#39; in p[&#34;decoded&#34;]:
if p[&#34;decoded&#34;][&#34;routing&#34;][&#34;errorReason&#34;] != &#34;NONE&#34;:
errorFound = True
print(f&#39;Error on response: {p[&#34;decoded&#34;][&#34;routing&#34;][&#34;errorReason&#34;]}&#39;)
if errorFound is False:
self.radioConfig = p[&#34;decoded&#34;][&#34;admin&#34;][&#34;raw&#34;].get_radio_response
logging.debug(&#34;Received radio config, now fetching channels...&#34;)
self._timeout.reset() # We made foreward progress
self._requestChannel(0) # now start fetching channels
# Show progress message for super slow operations
if self != self.iface.localNode:
logging.info(
&#34;Requesting preferences from remote node (this could take a while)&#34;)
print(&#34;Requesting preferences from remote node (this could take a while)&#34;)
return self._sendAdmin(p,
wantResponse=True,
onResponse=onResponse)
return self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
def exitSimulator(self):
&#34;&#34;&#34;
Tell a simulator node to exit (this message is ignored for other nodes)
&#34;&#34;&#34;
&#34;&#34;&#34;Tell a simulator node to exit (this message
is ignored for other nodes)&#34;&#34;&#34;
p = admin_pb2.AdminMessage()
p.exit_simulator = True
return self._sendAdmin(p)
def reboot(self, secs: int = 10):
&#34;&#34;&#34;
Tell the node to reboot
&#34;&#34;&#34;
&#34;&#34;&#34;Tell the node to reboot.&#34;&#34;&#34;
p = admin_pb2.AdminMessage()
p.reboot_seconds = secs
logging.info(f&#34;Telling node to reboot in {secs} seconds&#34;)
@@ -771,6 +625,7 @@ class Node:
&#34;&#34;&#34;Fixup indexes and add disabled channels as needed&#34;&#34;&#34;
# Add extra disabled channels as needed
# TODO: These 2 lines seem to not do anything.
for index, ch in enumerate(self.channels):
ch.index = index # fixup indexes
@@ -789,21 +644,19 @@ class Node:
index += 1
def _requestChannel(self, channelNum: int):
&#34;&#34;&#34;
Done with initial config messages, now send regular MeshPackets to ask for settings
&#34;&#34;&#34;
&#34;&#34;&#34;Done with initial config messages, now send regular
MeshPackets to ask for settings&#34;&#34;&#34;
p = admin_pb2.AdminMessage()
p.get_channel_request = channelNum + 1
# Show progress message for super slow operations
if self != self.iface.localNode:
logging.info(
f&#34;Requesting channel {channelNum} info from remote node (this could take a while)&#34;)
logging.info(f&#34;Requesting channel {channelNum} info from remote node (this could take a while)&#34;)
else:
logging.debug(f&#34;Requesting channel {channelNum}&#34;)
def onResponse(p):
&#34;&#34;&#34;A closure to handle the response packet&#34;&#34;&#34;
&#34;&#34;&#34;A closure to handle the response packet for requesting a channel&#34;&#34;&#34;
c = p[&#34;decoded&#34;][&#34;admin&#34;][&#34;raw&#34;].get_channel_response
self.partialChannels.append(c)
self._timeout.reset() # We made foreward progress
@@ -813,9 +666,9 @@ class Node:
# for stress testing, we can always download all channels
fastChannelDownload = True
# Once we see a response that has NO settings, assume we are at the end of channels and stop fetching
quitEarly = (
c.role == channel_pb2.Channel.Role.DISABLED) and fastChannelDownload
# Once we see a response that has NO settings, assume
# we are at the end of channels and stop fetching
quitEarly = (c.role == channel_pb2.Channel.Role.DISABLED) and fastChannelDownload
if quitEarly or index &gt;= self.iface.myInfo.max_channels - 1:
logging.debug(&#34;Finished downloading channels&#34;)
@@ -828,13 +681,10 @@ class Node:
else:
self._requestChannel(index + 1)
return self._sendAdmin(p,
wantResponse=True,
onResponse=onResponse)
return self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
def _sendAdmin(self, p: admin_pb2.AdminMessage, wantResponse=False,
onResponse=None,
adminIndex=0):
onResponse=None, adminIndex=0):
&#34;&#34;&#34;Send an admin message to the specified node (or the local node if destNodeNum is zero)&#34;&#34;&#34;
if adminIndex == 0: # unless a special channel index was used, we want to use the admin index
@@ -861,8 +711,8 @@ class Node:
<pre><code class="python">def deleteChannel(self, channelIndex):
&#34;&#34;&#34;Delete the specifed channelIndex and shift other channels up&#34;&#34;&#34;
ch = self.channels[channelIndex]
if ch.role != channel_pb2.Channel.Role.SECONDARY:
raise Exception(&#34;Only SECONDARY channels can be deleted&#34;)
if ch.role not in (channel_pb2.Channel.Role.SECONDARY, channel_pb2.Channel.Role.DISABLED):
our_exit(&#34;Warning: Only SECONDARY channels can be deleted&#34;)
# we are careful here because if we move the &#34;admin&#34; channel the channelIndex we need to use
# for sending admin channels will also change
@@ -876,9 +726,11 @@ class Node:
self.writeChannel(index, adminIndex=adminIndex)
index += 1
# if we are updating the local node, we might end up *moving* the admin channel index as we are writing
# if we are updating the local node, we might end up
# *moving* the admin channel index as we are writing
if (self.iface.localNode == self) and index &gt;= adminIndex:
# We&#39;ve now passed the old location for admin index (and writen it), so we can start finding it by name again
# We&#39;ve now passed the old location for admin index
# (and writen it), so we can start finding it by name again
adminIndex = 0</code></pre>
</details>
</dd>
@@ -886,15 +738,15 @@ class Node:
<span>def <span class="ident">exitSimulator</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Tell a simulator node to exit (this message is ignored for other nodes)</p></div>
<div class="desc"><p>Tell a simulator node to exit (this message
is ignored for other nodes)</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def exitSimulator(self):
&#34;&#34;&#34;
Tell a simulator node to exit (this message is ignored for other nodes)
&#34;&#34;&#34;
&#34;&#34;&#34;Tell a simulator node to exit (this message
is ignored for other nodes)&#34;&#34;&#34;
p = admin_pb2.AdminMessage()
p.exit_simulator = True
@@ -945,15 +797,15 @@ class Node:
<span>Expand source code</span>
</summary>
<pre><code class="python">def getURL(self, includeAll: bool = True):
&#34;&#34;&#34;The sharable URL that describes the current channel
&#34;&#34;&#34;
&#34;&#34;&#34;The sharable URL that describes the current channel&#34;&#34;&#34;
# Only keep the primary/secondary channels, assume primary is first
channelSet = apponly_pb2.ChannelSet()
for c in self.channels:
if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY):
channelSet.settings.append(c.settings)
bytes = channelSet.SerializeToString()
s = base64.urlsafe_b64encode(bytes).decode(&#39;ascii&#39;)
if self.channels:
for c in self.channels:
if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY):
channelSet.settings.append(c.settings)
some_bytes = channelSet.SerializeToString()
s = base64.urlsafe_b64encode(some_bytes).decode(&#39;ascii&#39;)
return f&#34;https://www.meshtastic.org/d/#{s}&#34;.replace(&#34;=&#34;, &#34;&#34;)</code></pre>
</details>
</dd>
@@ -961,15 +813,13 @@ class Node:
<span>def <span class="ident">reboot</span></span>(<span>self, secs: int = 10)</span>
</code></dt>
<dd>
<div class="desc"><p>Tell the node to reboot</p></div>
<div class="desc"><p>Tell the node to reboot.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def reboot(self, secs: int = 10):
&#34;&#34;&#34;
Tell the node to reboot
&#34;&#34;&#34;
&#34;&#34;&#34;Tell the node to reboot.&#34;&#34;&#34;
p = admin_pb2.AdminMessage()
p.reboot_seconds = secs
logging.info(f&#34;Telling node to reboot in {secs} seconds&#34;)
@@ -981,15 +831,13 @@ class Node:
<span>def <span class="ident">requestConfig</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Send regular MeshPackets to ask for settings and channels</p></div>
<div class="desc"><p>Send regular MeshPackets to ask for settings and channels.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def requestConfig(self):
&#34;&#34;&#34;
Send regular MeshPackets to ask for settings and channels
&#34;&#34;&#34;
&#34;&#34;&#34;Send regular MeshPackets to ask for settings and channels.&#34;&#34;&#34;
self.radioConfig = None
self.channels = None
self.partialChannels = [] # We keep our channels in a temp array until finished
@@ -998,7 +846,7 @@ class Node:
</details>
</dd>
<dt id="meshtastic.node.Node.setOwner"><code class="name flex">
<span>def <span class="ident">setOwner</span></span>(<span>self, long_name, short_name=None, is_licensed=False)</span>
<span>def <span class="ident">setOwner</span></span>(<span>self, long_name=None, short_name=None, is_licensed=False, team=None)</span>
</code></dt>
<dd>
<div class="desc"><p>Set device owner name</p></div>
@@ -1006,7 +854,7 @@ class Node:
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def setOwner(self, long_name, short_name=None, is_licensed=False):
<pre><code class="python">def setOwner(self, long_name=None, short_name=None, is_licensed=False, team=None):
&#34;&#34;&#34;Set device owner name&#34;&#34;&#34;
nChars = 3
minChars = 2
@@ -1034,6 +882,8 @@ class Node:
short_name = short_name[:nChars]
p.set_owner.short_name = short_name
p.set_owner.is_licensed = is_licensed
if team is not None:
p.set_owner.team = team
return self._sendAdmin(p)</code></pre>
</details>
@@ -1049,8 +899,8 @@ class Node:
</summary>
<pre><code class="python">def setURL(self, url):
&#34;&#34;&#34;Set mesh network URL&#34;&#34;&#34;
if self.radioConfig == None:
raise Exception(&#34;No RadioConfig has been read&#34;)
if self.radioConfig is None:
our_exit(&#34;Warning: No RadioConfig has been read&#34;)
# URLs are of the form https://www.meshtastic.org/d/#{base64_channel_set}
# Split on &#39;/#&#39; to find the base64 encoded channel settings
@@ -1068,6 +918,9 @@ class Node:
channelSet = apponly_pb2.ChannelSet()
channelSet.ParseFromString(decodedURL)
if len(channelSet.settings) == 0:
our_exit(&#34;Warning: There were no settings.&#34;)
i = 0
for chs in channelSet.settings:
ch = channel_pb2.Channel()
@@ -1083,19 +936,20 @@ class Node:
<span>def <span class="ident">showChannels</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Show human readable description of our channels</p></div>
<div class="desc"><p>Show human readable description of our channels.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def showChannels(self):
&#34;&#34;&#34;Show human readable description of our channels&#34;&#34;&#34;
&#34;&#34;&#34;Show human readable description of our channels.&#34;&#34;&#34;
print(&#34;Channels:&#34;)
for c in self.channels:
if c.role != channel_pb2.Channel.Role.DISABLED:
if self.channels:
for c in self.channels:
cStr = stripnl(MessageToJson(c.settings))
print(
f&#34; {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}&#34;)
# only show if there is no psk (meaning disabled channel)
if c.settings.psk:
print(f&#34; {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}&#34;)
publicURL = self.getURL(includeAll=False)
adminURL = self.getURL(includeAll=True)
print(f&#34;\nPrimary channel URL: {publicURL}&#34;)
@@ -1114,11 +968,29 @@ class Node:
</summary>
<pre><code class="python">def showInfo(self):
&#34;&#34;&#34;Show human readable description of our node&#34;&#34;&#34;
print(
f&#34;Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n&#34;)
prefs = &#34;&#34;
if self.radioConfig and self.radioConfig.preferences:
prefs = stripnl(MessageToJson(self.radioConfig.preferences))
print(f&#34;Preferences: {prefs}\n&#34;)
self.showChannels()</code></pre>
</details>
</dd>
<dt id="meshtastic.node.Node.turnOffEncryptionOnPrimaryChannel"><code class="name flex">
<span>def <span class="ident">turnOffEncryptionOnPrimaryChannel</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Turn off encryption on primary channel.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def turnOffEncryptionOnPrimaryChannel(self):
&#34;&#34;&#34;Turn off encryption on primary channel.&#34;&#34;&#34;
self.channels[0].settings.psk = fromPSK(&#34;none&#34;)
print(&#34;Writing modified channels to device&#34;)
self.writeChannel(0)</code></pre>
</details>
</dd>
<dt id="meshtastic.node.Node.waitForConfig"><code class="name flex">
<span>def <span class="ident">waitForConfig</span></span>(<span>self)</span>
</code></dt>
@@ -1163,8 +1035,8 @@ class Node:
</summary>
<pre><code class="python">def writeConfig(self):
&#34;&#34;&#34;Write the current (edited) radioConfig to the device&#34;&#34;&#34;
if self.radioConfig == None:
raise Exception(&#34;No RadioConfig has been read&#34;)
if self.radioConfig is None:
our_exit(&#34;Error: No RadioConfig has been read&#34;)
p = admin_pb2.AdminMessage()
p.set_radio.CopyFrom(self.radioConfig)
@@ -1181,11 +1053,7 @@ class Node:
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul>
<li><a href="#an-api-for-meshtastic-devices">an API for Meshtastic devices</a></li>
<li><a href="#published-pubsub-topics">Published PubSub topics</a></li>
<li><a href="#example-usage">Example Usage</a></li>
</ul>
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
@@ -1193,16 +1061,11 @@ class Node:
<li><code><a title="meshtastic" href="index.html">meshtastic</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.node.pskToString" href="#meshtastic.node.pskToString">pskToString</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="meshtastic.node.Node" href="#meshtastic.node.Node">Node</a></code></h4>
<ul class="two-column">
<ul class="">
<li><code><a title="meshtastic.node.Node.deleteChannel" href="#meshtastic.node.Node.deleteChannel">deleteChannel</a></code></li>
<li><code><a title="meshtastic.node.Node.exitSimulator" href="#meshtastic.node.Node.exitSimulator">exitSimulator</a></code></li>
<li><code><a title="meshtastic.node.Node.getChannelByName" href="#meshtastic.node.Node.getChannelByName">getChannelByName</a></code></li>
@@ -1214,6 +1077,7 @@ class Node:
<li><code><a title="meshtastic.node.Node.setURL" href="#meshtastic.node.Node.setURL">setURL</a></code></li>
<li><code><a title="meshtastic.node.Node.showChannels" href="#meshtastic.node.Node.showChannels">showChannels</a></code></li>
<li><code><a title="meshtastic.node.Node.showInfo" href="#meshtastic.node.Node.showInfo">showInfo</a></code></li>
<li><code><a title="meshtastic.node.Node.turnOffEncryptionOnPrimaryChannel" href="#meshtastic.node.Node.turnOffEncryptionOnPrimaryChannel">turnOffEncryptionOnPrimaryChannel</a></code></li>
<li><code><a title="meshtastic.node.Node.waitForConfig" href="#meshtastic.node.Node.waitForConfig">waitForConfig</a></code></li>
<li><code><a title="meshtastic.node.Node.writeChannel" href="#meshtastic.node.Node.writeChannel">writeChannel</a></code></li>
<li><code><a title="meshtastic.node.Node.writeConfig" href="#meshtastic.node.Node.writeConfig">writeConfig</a></code></li>
@@ -1225,7 +1089,7 @@ class Node:
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc"><cite>pdoc</cite> 0.9.2</a>.</p>
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -3,9 +3,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.9.2" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.portnums_pb2 API documentation</title>
<meta name="description" content="Generated protocol buffer code." />
<meta name="description" content="" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
@@ -22,7 +22,6 @@
<h1 class="title">Module <code>meshtastic.portnums_pb2</code></h1>
</header>
<section id="section-intro">
<p>Generated protocol buffer code.</p>
<details class="source">
<summary>
<span>Expand source code</span>
@@ -30,7 +29,7 @@
<pre><code class="python"># -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: portnums.proto
&#34;&#34;&#34;Generated protocol buffer code.&#34;&#34;&#34;
from google.protobuf.internal import enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
@@ -48,8 +47,7 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package=&#39;&#39;,
syntax=&#39;proto3&#39;,
serialized_options=b&#39;\n\023com.geeksville.meshB\010PortnumsH\003Z!github.com/meshtastic/gomeshproto&#39;,
create_key=_descriptor._internal_create_key,
serialized_pb=b&#39;\n\x0eportnums.proto*\xbe\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\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&#39;
serialized_pb=b&#39;\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&#39;
)
_PORTNUM = _descriptor.EnumDescriptor(
@@ -57,93 +55,80 @@ _PORTNUM = _descriptor.EnumDescriptor(
full_name=&#39;PortNum&#39;,
filename=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key,
values=[
_descriptor.EnumValueDescriptor(
name=&#39;UNKNOWN_APP&#39;, index=0, number=0,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;TEXT_MESSAGE_APP&#39;, index=1, number=1,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;REMOTE_HARDWARE_APP&#39;, index=2, number=2,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;POSITION_APP&#39;, index=3, number=3,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;NODEINFO_APP&#39;, index=4, number=4,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;ROUTING_APP&#39;, index=5, number=5,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;ADMIN_APP&#39;, index=6, number=6,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;REPLY_APP&#39;, index=7, number=32,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;IP_TUNNEL_APP&#39;, index=8, number=33,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;SERIAL_APP&#39;, index=9, number=64,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;STORE_FORWARD_APP&#39;, index=10, number=65,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;RANGE_TEST_APP&#39;, index=11, number=66,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;ENVIRONMENTAL_MEASUREMENT_APP&#39;, index=12, number=67,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;PRIVATE_APP&#39;, index=13, number=256,
name=&#39;ZPS_APP&#39;, index=13, number=68,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;ATAK_FORWARDER&#39;, index=14, number=257,
name=&#39;PRIVATE_APP&#39;, index=14, number=256,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;MAX&#39;, index=15, number=511,
name=&#39;ATAK_FORWARDER&#39;, index=15, number=257,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;MAX&#39;, index=16, number=511,
serialized_options=None,
type=None),
],
containing_type=None,
serialized_options=None,
serialized_start=19,
serialized_end=337,
serialized_end=350,
)
_sym_db.RegisterEnumDescriptor(_PORTNUM)
@@ -161,6 +146,7 @@ SERIAL_APP = 64
STORE_FORWARD_APP = 65
RANGE_TEST_APP = 66
ENVIRONMENTAL_MEASUREMENT_APP = 67
ZPS_APP = 68
PRIVATE_APP = 256
ATAK_FORWARDER = 257
MAX = 511
@@ -198,7 +184,7 @@ DESCRIPTOR._options = None
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc"><cite>pdoc</cite> 0.9.2</a>.</p>
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

File diff suppressed because one or more lines are too long

View File

@@ -3,9 +3,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.9.2" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.remote_hardware API documentation</title>
<meta name="description" content="" />
<meta name="description" content="Remote hardware" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
@@ -22,12 +22,15 @@
<h1 class="title">Module <code>meshtastic.remote_hardware</code></h1>
</header>
<section id="section-intro">
<p>Remote hardware</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">from . import portnums_pb2, remote_hardware_pb2
<pre><code class="python">&#34;&#34;&#34; Remote hardware
&#34;&#34;&#34;
from pubsub import pub
from . import portnums_pb2, remote_hardware_pb2
def onGPIOreceive(packet, interface):
@@ -40,7 +43,7 @@ def onGPIOreceive(packet, interface):
class RemoteHardwareClient:
&#34;&#34;&#34;
This is the client code to control/monitor simple hardware built into the
This is the client code to control/monitor simple hardware built into the
meshtastic devices. It is intended to be both a useful API/service and example
code for how you can connect to your own custom meshtastic services
&#34;&#34;&#34;
@@ -55,7 +58,8 @@ class RemoteHardwareClient:
ch = iface.localNode.getChannelByName(&#34;gpio&#34;)
if not ch:
raise Exception(
&#34;No gpio channel found, please create on the sending and receive nodes to use this (secured) service (--ch-add gpio --info then --seturl)&#34;)
&#34;No gpio channel found, please create on the sending and receive nodes &#34;\
&#34;to use this (secured) service (--ch-add gpio --info then --seturl)&#34;)
self.channelIndex = ch.index
pub.subscribe(
@@ -64,7 +68,7 @@ class RemoteHardwareClient:
def _sendHardware(self, nodeid, r, wantResponse=False, onResponse=None):
if not nodeid:
raise Exception(
&#34;You must set a destination node ID for this operation (use --dest \!xxxxxxxxx)&#34;)
r&#34;You must set a destination node ID for this operation (use --dest \!xxxxxxxxx)&#34;)
return self.iface.sendData(r, nodeid, portnums_pb2.REMOTE_HARDWARE_APP,
wantAck=True, channelIndex=self.channelIndex, wantResponse=wantResponse, onResponse=onResponse)
@@ -141,7 +145,7 @@ code for how you can connect to your own custom meshtastic services</p>
</summary>
<pre><code class="python">class RemoteHardwareClient:
&#34;&#34;&#34;
This is the client code to control/monitor simple hardware built into the
This is the client code to control/monitor simple hardware built into the
meshtastic devices. It is intended to be both a useful API/service and example
code for how you can connect to your own custom meshtastic services
&#34;&#34;&#34;
@@ -156,7 +160,8 @@ code for how you can connect to your own custom meshtastic services</p>
ch = iface.localNode.getChannelByName(&#34;gpio&#34;)
if not ch:
raise Exception(
&#34;No gpio channel found, please create on the sending and receive nodes to use this (secured) service (--ch-add gpio --info then --seturl)&#34;)
&#34;No gpio channel found, please create on the sending and receive nodes &#34;\
&#34;to use this (secured) service (--ch-add gpio --info then --seturl)&#34;)
self.channelIndex = ch.index
pub.subscribe(
@@ -165,7 +170,7 @@ code for how you can connect to your own custom meshtastic services</p>
def _sendHardware(self, nodeid, r, wantResponse=False, onResponse=None):
if not nodeid:
raise Exception(
&#34;You must set a destination node ID for this operation (use --dest \!xxxxxxxxx)&#34;)
r&#34;You must set a destination node ID for this operation (use --dest \!xxxxxxxxx)&#34;)
return self.iface.sendData(r, nodeid, portnums_pb2.REMOTE_HARDWARE_APP,
wantAck=True, channelIndex=self.channelIndex, wantResponse=wantResponse, onResponse=onResponse)
@@ -290,7 +295,7 @@ are 1 will be changed</p></div>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc"><cite>pdoc</cite> 0.9.2</a>.</p>
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -3,9 +3,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.9.2" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.remote_hardware_pb2 API documentation</title>
<meta name="description" content="Generated protocol buffer code." />
<meta name="description" content="" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
@@ -22,7 +22,6 @@
<h1 class="title">Module <code>meshtastic.remote_hardware_pb2</code></h1>
</header>
<section id="section-intro">
<p>Generated protocol buffer code.</p>
<details class="source">
<summary>
<span>Expand source code</span>
@@ -30,7 +29,7 @@
<pre><code class="python"># -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: remote_hardware.proto
&#34;&#34;&#34;Generated protocol buffer code.&#34;&#34;&#34;
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
@@ -47,7 +46,6 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package=&#39;&#39;,
syntax=&#39;proto3&#39;,
serialized_options=b&#39;\n\023com.geeksville.meshB\016RemoteHardwareH\003Z!github.com/meshtastic/gomeshproto&#39;,
create_key=_descriptor._internal_create_key,
serialized_pb=b&#39;\n\x15remote_hardware.proto\&#34;\xca\x01\n\x0fHardwareMessage\x12\&#34;\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\&#34;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&#39;
)
@@ -58,38 +56,31 @@ _HARDWAREMESSAGE_TYPE = _descriptor.EnumDescriptor(
full_name=&#39;HardwareMessage.Type&#39;,
filename=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key,
values=[
_descriptor.EnumValueDescriptor(
name=&#39;UNSET&#39;, index=0, number=0,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;WRITE_GPIOS&#39;, index=1, number=1,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;WATCH_GPIOS&#39;, index=2, number=2,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;GPIOS_CHANGED&#39;, index=3, number=3,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;READ_GPIOS&#39;, index=4, number=4,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;READ_GPIOS_REPLY&#39;, index=5, number=5,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
],
containing_type=None,
serialized_options=None,
@@ -105,7 +96,6 @@ _HARDWAREMESSAGE = _descriptor.Descriptor(
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name=&#39;typ&#39;, full_name=&#39;HardwareMessage.typ&#39;, index=0,
@@ -113,21 +103,21 @@ _HARDWAREMESSAGE = _descriptor.Descriptor(
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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;gpio_mask&#39;, full_name=&#39;HardwareMessage.gpio_mask&#39;, 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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;gpio_value&#39;, full_name=&#39;HardwareMessage.gpio_value&#39;, 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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
@@ -173,13 +163,16 @@ DESCRIPTOR._options = None
<dl>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage"><code class="flex name class">
<span>class <span class="ident">HardwareMessage</span></span>
<span>(</span><span>*args, **kwargs)</span>
<span>(</span><span>**kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<div class="desc"><p>Abstract base class for protocol messages.</p>
<p>Protocol message classes are almost always generated by the protocol
compiler.
These generated types subclass Message and implement the methods
shown below.</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google.protobuf.pyext._message.CMessage</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
@@ -192,6 +185,14 @@ DESCRIPTOR._options = None
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.GPIO_MASK_FIELD_NUMBER"><code class="name">var <span class="ident">GPIO_MASK_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.GPIO_VALUE_FIELD_NUMBER"><code class="name">var <span class="ident">GPIO_VALUE_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.READ_GPIOS"><code class="name">var <span class="ident">READ_GPIOS</span></code></dt>
<dd>
<div class="desc"></div>
@@ -200,6 +201,10 @@ DESCRIPTOR._options = None
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.TYP_FIELD_NUMBER"><code class="name">var <span class="ident">TYP_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.Type"><code class="name">var <span class="ident">Type</span></code></dt>
<dd>
<div class="desc"></div>
@@ -217,19 +222,539 @@ DESCRIPTOR._options = None
<div class="desc"></div>
</dd>
</dl>
<h3>Static methods</h3>
<dl>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.FromString"><code class="name flex">
<span>def <span class="ident">FromString</span></span>(<span>s)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def FromString(s):
message = cls()
message.MergeFromString(s)
return message</code></pre>
</details>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.RegisterExtension"><code class="name flex">
<span>def <span class="ident">RegisterExtension</span></span>(<span>extension_handle)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def RegisterExtension(extension_handle):
extension_handle.containing_type = cls.DESCRIPTOR
# TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
# pylint: disable=protected-access
cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
_AttachFieldHelpers(cls, extension_handle)</code></pre>
</details>
</dd>
</dl>
<h3>Instance variables</h3>
<dl>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.gpio_mask"><code class="name">var <span class="ident">gpio_mask</span></code></dt>
<dd>
<div class="desc"><p>Field HardwareMessage.gpio_mask</p></div>
<div class="desc"><p>Getter for gpio_mask.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.gpio_value"><code class="name">var <span class="ident">gpio_value</span></code></dt>
<dd>
<div class="desc"><p>Field HardwareMessage.gpio_value</p></div>
<div class="desc"><p>Getter for gpio_value.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.typ"><code class="name">var <span class="ident">typ</span></code></dt>
<dd>
<div class="desc"><p>Field HardwareMessage.typ</p></div>
<div class="desc"><p>Getter for typ.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
</dl>
<h3>Methods</h3>
<dl>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.ByteSize"><code class="name flex">
<span>def <span class="ident">ByteSize</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ByteSize(self):
if not self._cached_byte_size_dirty:
return self._cached_byte_size
size = 0
descriptor = self.DESCRIPTOR
if descriptor.GetOptions().map_entry:
# Fields of map entry should always be serialized.
size = descriptor.fields_by_name[&#39;key&#39;]._sizer(self.key)
size += descriptor.fields_by_name[&#39;value&#39;]._sizer(self.value)
else:
for field_descriptor, field_value in self.ListFields():
size += field_descriptor._sizer(field_value)
for tag_bytes, value_bytes in self._unknown_fields:
size += len(tag_bytes) + len(value_bytes)
self._cached_byte_size = size
self._cached_byte_size_dirty = False
self._listener_for_children.dirty = False
return size</code></pre>
</details>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.Clear"><code class="name flex">
<span>def <span class="ident">Clear</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def _Clear(self):
# Clear fields.
self._fields = {}
self._unknown_fields = ()
# pylint: disable=protected-access
if self._unknown_field_set is not None:
self._unknown_field_set._clear()
self._unknown_field_set = None
self._oneofs = {}
self._Modified()</code></pre>
</details>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.ClearField"><code class="name flex">
<span>def <span class="ident">ClearField</span></span>(<span>self, field_name)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ClearField(self, field_name):
try:
field = message_descriptor.fields_by_name[field_name]
except KeyError:
try:
field = message_descriptor.oneofs_by_name[field_name]
if field in self._oneofs:
field = self._oneofs[field]
else:
return
except KeyError:
raise ValueError(&#39;Protocol message %s has no &#34;%s&#34; field.&#39; %
(message_descriptor.name, field_name))
if field in self._fields:
# To match the C++ implementation, we need to invalidate iterators
# for map fields when ClearField() happens.
if hasattr(self._fields[field], &#39;InvalidateIterators&#39;):
self._fields[field].InvalidateIterators()
# Note: If the field is a sub-message, its listener will still point
# at us. That&#39;s fine, because the worst than can happen is that it
# will call _Modified() and invalidate our byte size. Big deal.
del self._fields[field]
if self._oneofs.get(field.containing_oneof, None) is field:
del self._oneofs[field.containing_oneof]
# Always call _Modified() -- even if nothing was changed, this is
# a mutating method, and thus calling it should cause the field to become
# present in the parent message.
self._Modified()</code></pre>
</details>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.DiscardUnknownFields"><code class="name flex">
<span>def <span class="ident">DiscardUnknownFields</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def _DiscardUnknownFields(self):
self._unknown_fields = []
self._unknown_field_set = None # pylint: disable=protected-access
for field, value in self.ListFields():
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
if _IsMapField(field):
if _IsMessageMapField(field):
for key in value:
value[key].DiscardUnknownFields()
elif field.label == _FieldDescriptor.LABEL_REPEATED:
for sub_message in value:
sub_message.DiscardUnknownFields()
else:
value.DiscardUnknownFields()</code></pre>
</details>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.FindInitializationErrors"><code class="name flex">
<span>def <span class="ident">FindInitializationErrors</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Finds required fields which are not initialized.</p>
<h2 id="returns">Returns</h2>
<p>A list of strings.
Each string is a path to an uninitialized field from
the top-level message, e.g. "foo.bar[5].baz".</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def FindInitializationErrors(self):
&#34;&#34;&#34;Finds required fields which are not initialized.
Returns:
A list of strings. Each string is a path to an uninitialized field from
the top-level message, e.g. &#34;foo.bar[5].baz&#34;.
&#34;&#34;&#34;
errors = [] # simplify things
for field in required_fields:
if not self.HasField(field.name):
errors.append(field.name)
for field, value in self.ListFields():
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
if field.is_extension:
name = &#39;(%s)&#39; % field.full_name
else:
name = field.name
if _IsMapField(field):
if _IsMessageMapField(field):
for key in value:
element = value[key]
prefix = &#39;%s[%s].&#39; % (name, key)
sub_errors = element.FindInitializationErrors()
errors += [prefix + error for error in sub_errors]
else:
# ScalarMaps can&#39;t have any initialization errors.
pass
elif field.label == _FieldDescriptor.LABEL_REPEATED:
for i in range(len(value)):
element = value[i]
prefix = &#39;%s[%d].&#39; % (name, i)
sub_errors = element.FindInitializationErrors()
errors += [prefix + error for error in sub_errors]
else:
prefix = name + &#39;.&#39;
sub_errors = value.FindInitializationErrors()
errors += [prefix + error for error in sub_errors]
return errors</code></pre>
</details>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.HasField"><code class="name flex">
<span>def <span class="ident">HasField</span></span>(<span>self, field_name)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def HasField(self, field_name):
try:
field = hassable_fields[field_name]
except KeyError:
raise ValueError(error_msg % (message_descriptor.full_name, field_name))
if isinstance(field, descriptor_mod.OneofDescriptor):
try:
return HasField(self, self._oneofs[field].name)
except KeyError:
return False
else:
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
value = self._fields.get(field)
return value is not None and value._is_present_in_parent
else:
return field in self._fields</code></pre>
</details>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.IsInitialized"><code class="name flex">
<span>def <span class="ident">IsInitialized</span></span>(<span>self, errors=None)</span>
</code></dt>
<dd>
<div class="desc"><p>Checks if all required fields of a message are set.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>errors</code></strong></dt>
<dd>A list which, if provided, will be populated with the field
paths of all missing required fields.</dd>
</dl>
<h2 id="returns">Returns</h2>
<p>True iff the specified message has all required fields set.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def IsInitialized(self, errors=None):
&#34;&#34;&#34;Checks if all required fields of a message are set.
Args:
errors: A list which, if provided, will be populated with the field
paths of all missing required fields.
Returns:
True iff the specified message has all required fields set.
&#34;&#34;&#34;
# Performance is critical so we avoid HasField() and ListFields().
for field in required_fields:
if (field not in self._fields or
(field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
not self._fields[field]._is_present_in_parent)):
if errors is not None:
errors.extend(self.FindInitializationErrors())
return False
for field, value in list(self._fields.items()): # dict can change size!
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
if field.label == _FieldDescriptor.LABEL_REPEATED:
if (field.message_type.has_options and
field.message_type.GetOptions().map_entry):
continue
for element in value:
if not element.IsInitialized():
if errors is not None:
errors.extend(self.FindInitializationErrors())
return False
elif value._is_present_in_parent and not value.IsInitialized():
if errors is not None:
errors.extend(self.FindInitializationErrors())
return False
return True</code></pre>
</details>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.ListFields"><code class="name flex">
<span>def <span class="ident">ListFields</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ListFields(self):
all_fields = [item for item in self._fields.items() if _IsPresent(item)]
all_fields.sort(key = lambda item: item[0].number)
return all_fields</code></pre>
</details>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.MergeFrom"><code class="name flex">
<span>def <span class="ident">MergeFrom</span></span>(<span>self, msg)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def MergeFrom(self, msg):
if not isinstance(msg, cls):
raise TypeError(
&#39;Parameter to MergeFrom() must be instance of same class: &#39;
&#39;expected %s got %s.&#39; % (_FullyQualifiedClassName(cls),
_FullyQualifiedClassName(msg.__class__)))
assert msg is not self
self._Modified()
fields = self._fields
for field, value in msg._fields.items():
if field.label == LABEL_REPEATED:
field_value = fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
fields[field] = field_value
field_value.MergeFrom(value)
elif field.cpp_type == CPPTYPE_MESSAGE:
if value._is_present_in_parent:
field_value = fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
fields[field] = field_value
field_value.MergeFrom(value)
else:
self._fields[field] = value
if field.containing_oneof:
self._UpdateOneofState(field)
if msg._unknown_fields:
if not self._unknown_fields:
self._unknown_fields = []
self._unknown_fields.extend(msg._unknown_fields)
# pylint: disable=protected-access
if self._unknown_field_set is None:
self._unknown_field_set = containers.UnknownFieldSet()
self._unknown_field_set._extend(msg._unknown_field_set)</code></pre>
</details>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.MergeFromString"><code class="name flex">
<span>def <span class="ident">MergeFromString</span></span>(<span>self, serialized)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def MergeFromString(self, serialized):
serialized = memoryview(serialized)
length = len(serialized)
try:
if self._InternalParse(serialized, 0, length) != length:
# The only reason _InternalParse would return early is if it
# encountered an end-group tag.
raise message_mod.DecodeError(&#39;Unexpected end-group tag.&#39;)
except (IndexError, TypeError):
# Now ord(buf[p:p+1]) == ord(&#39;&#39;) gets TypeError.
raise message_mod.DecodeError(&#39;Truncated message.&#39;)
except struct.error as e:
raise message_mod.DecodeError(e)
return length # Return this for legacy reasons.</code></pre>
</details>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.SerializePartialToString"><code class="name flex">
<span>def <span class="ident">SerializePartialToString</span></span>(<span>self, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def SerializePartialToString(self, **kwargs):
out = BytesIO()
self._InternalSerialize(out.write, **kwargs)
return out.getvalue()</code></pre>
</details>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.SerializeToString"><code class="name flex">
<span>def <span class="ident">SerializeToString</span></span>(<span>self, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def SerializeToString(self, **kwargs):
# Check if the message has all of its required fields set.
if not self.IsInitialized():
raise message_mod.EncodeError(
&#39;Message %s is missing required fields: %s&#39; % (
self.DESCRIPTOR.full_name, &#39;,&#39;.join(self.FindInitializationErrors())))
return self.SerializePartialToString(**kwargs)</code></pre>
</details>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.SetInParent"><code class="name flex">
<span>def <span class="ident">SetInParent</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Sets the _cached_byte_size_dirty bit to true,
and propagates this to our listener iff this was a state change.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def Modified(self):
&#34;&#34;&#34;Sets the _cached_byte_size_dirty bit to true,
and propagates this to our listener iff this was a state change.
&#34;&#34;&#34;
# Note: Some callers check _cached_byte_size_dirty before calling
# _Modified() as an extra optimization. So, if this method is ever
# changed such that it does stuff even when _cached_byte_size_dirty is
# already true, the callers need to be updated.
if not self._cached_byte_size_dirty:
self._cached_byte_size_dirty = True
self._listener_for_children.dirty = True
self._is_present_in_parent = True
self._listener.Modified()</code></pre>
</details>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.UnknownFields"><code class="name flex">
<span>def <span class="ident">UnknownFields</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def _UnknownFields(self):
if self._unknown_field_set is None: # pylint: disable=protected-access
# pylint: disable=protected-access
self._unknown_field_set = containers.UnknownFieldSet()
return self._unknown_field_set # pylint: disable=protected-access</code></pre>
</details>
</dd>
<dt id="meshtastic.remote_hardware_pb2.HardwareMessage.WhichOneof"><code class="name flex">
<span>def <span class="ident">WhichOneof</span></span>(<span>self, oneof_name)</span>
</code></dt>
<dd>
<div class="desc"><p>Returns the name of the currently set field inside a oneof, or None.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def WhichOneof(self, oneof_name):
&#34;&#34;&#34;Returns the name of the currently set field inside a oneof, or None.&#34;&#34;&#34;
try:
field = message_descriptor.oneofs_by_name[oneof_name]
except KeyError:
raise ValueError(
&#39;Protocol message has no oneof &#34;%s&#34; field.&#39; % oneof_name)
nested_field = self._oneofs.get(field, None)
if nested_field is not None and self.HasField(nested_field.name):
return nested_field.name
else:
return None</code></pre>
</details>
</dd>
</dl>
</dd>
@@ -251,15 +776,35 @@ DESCRIPTOR._options = None
<ul>
<li>
<h4><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage" href="#meshtastic.remote_hardware_pb2.HardwareMessage">HardwareMessage</a></code></h4>
<ul class="two-column">
<ul class="">
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.ByteSize" href="#meshtastic.remote_hardware_pb2.HardwareMessage.ByteSize">ByteSize</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.Clear" href="#meshtastic.remote_hardware_pb2.HardwareMessage.Clear">Clear</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.ClearField" href="#meshtastic.remote_hardware_pb2.HardwareMessage.ClearField">ClearField</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.DESCRIPTOR" href="#meshtastic.remote_hardware_pb2.HardwareMessage.DESCRIPTOR">DESCRIPTOR</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.DiscardUnknownFields" href="#meshtastic.remote_hardware_pb2.HardwareMessage.DiscardUnknownFields">DiscardUnknownFields</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.FindInitializationErrors" href="#meshtastic.remote_hardware_pb2.HardwareMessage.FindInitializationErrors">FindInitializationErrors</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.FromString" href="#meshtastic.remote_hardware_pb2.HardwareMessage.FromString">FromString</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.GPIOS_CHANGED" href="#meshtastic.remote_hardware_pb2.HardwareMessage.GPIOS_CHANGED">GPIOS_CHANGED</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.GPIO_MASK_FIELD_NUMBER" href="#meshtastic.remote_hardware_pb2.HardwareMessage.GPIO_MASK_FIELD_NUMBER">GPIO_MASK_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.GPIO_VALUE_FIELD_NUMBER" href="#meshtastic.remote_hardware_pb2.HardwareMessage.GPIO_VALUE_FIELD_NUMBER">GPIO_VALUE_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.HasField" href="#meshtastic.remote_hardware_pb2.HardwareMessage.HasField">HasField</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.IsInitialized" href="#meshtastic.remote_hardware_pb2.HardwareMessage.IsInitialized">IsInitialized</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.ListFields" href="#meshtastic.remote_hardware_pb2.HardwareMessage.ListFields">ListFields</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.MergeFrom" href="#meshtastic.remote_hardware_pb2.HardwareMessage.MergeFrom">MergeFrom</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.MergeFromString" href="#meshtastic.remote_hardware_pb2.HardwareMessage.MergeFromString">MergeFromString</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.READ_GPIOS" href="#meshtastic.remote_hardware_pb2.HardwareMessage.READ_GPIOS">READ_GPIOS</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.READ_GPIOS_REPLY" href="#meshtastic.remote_hardware_pb2.HardwareMessage.READ_GPIOS_REPLY">READ_GPIOS_REPLY</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.RegisterExtension" href="#meshtastic.remote_hardware_pb2.HardwareMessage.RegisterExtension">RegisterExtension</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.SerializePartialToString" href="#meshtastic.remote_hardware_pb2.HardwareMessage.SerializePartialToString">SerializePartialToString</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.SerializeToString" href="#meshtastic.remote_hardware_pb2.HardwareMessage.SerializeToString">SerializeToString</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.SetInParent" href="#meshtastic.remote_hardware_pb2.HardwareMessage.SetInParent">SetInParent</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.TYP_FIELD_NUMBER" href="#meshtastic.remote_hardware_pb2.HardwareMessage.TYP_FIELD_NUMBER">TYP_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.Type" href="#meshtastic.remote_hardware_pb2.HardwareMessage.Type">Type</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.UNSET" href="#meshtastic.remote_hardware_pb2.HardwareMessage.UNSET">UNSET</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.UnknownFields" href="#meshtastic.remote_hardware_pb2.HardwareMessage.UnknownFields">UnknownFields</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.WATCH_GPIOS" href="#meshtastic.remote_hardware_pb2.HardwareMessage.WATCH_GPIOS">WATCH_GPIOS</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.WRITE_GPIOS" href="#meshtastic.remote_hardware_pb2.HardwareMessage.WRITE_GPIOS">WRITE_GPIOS</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.WhichOneof" href="#meshtastic.remote_hardware_pb2.HardwareMessage.WhichOneof">WhichOneof</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.gpio_mask" href="#meshtastic.remote_hardware_pb2.HardwareMessage.gpio_mask">gpio_mask</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.gpio_value" href="#meshtastic.remote_hardware_pb2.HardwareMessage.gpio_value">gpio_value</a></code></li>
<li><code><a title="meshtastic.remote_hardware_pb2.HardwareMessage.typ" href="#meshtastic.remote_hardware_pb2.HardwareMessage.typ">typ</a></code></li>
@@ -271,7 +816,7 @@ DESCRIPTOR._options = None
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc"><cite>pdoc</cite> 0.9.2</a>.</p>
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,254 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.serial_interface API documentation</title>
<meta name="description" content="Serial interface class" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.serial_interface</code></h1>
</header>
<section id="section-intro">
<p>Serial interface class</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34; Serial interface class
&#34;&#34;&#34;
import logging
import platform
import os
import stat
import serial
import meshtastic.util
from .stream_interface import StreamInterface
class SerialInterface(StreamInterface):
&#34;&#34;&#34;Interface class for meshtastic devices over a serial link&#34;&#34;&#34;
def __init__(self, devPath=None, debugOut=None, noProto=False, connectNow=True):
&#34;&#34;&#34;Constructor, opens a connection to a specified serial port, or if unspecified try to
find one Meshtastic device by probing
Keyword Arguments:
devPath {string} -- A filepath to a device, i.e. /dev/ttyUSB0 (default: {None})
debugOut {stream} -- If a stream is provided, any debug serial output from the device will be emitted to that stream. (default: {None})
&#34;&#34;&#34;
if devPath is None:
ports = meshtastic.util.findPorts()
logging.debug(f&#34;ports:{ports}&#34;)
if len(ports) == 0:
meshtastic.util.our_exit(&#34;Warning: No Meshtastic devices detected.&#34;)
elif len(ports) &gt; 1:
message = &#34;Warning: Multiple serial ports were detected so one serial port must be specified with the &#39;--port&#39;.\n&#34;
message += f&#34; Ports detected:{ports}&#34;
meshtastic.util.our_exit(message)
else:
devPath = ports[0]
logging.debug(f&#34;Connecting to {devPath}&#34;)
# Note: we provide None for port here, because we will be opening it later
self.stream = serial.Serial(
None, 921600, exclusive=True, timeout=0.5, write_timeout=0)
# rts=False Needed to prevent TBEAMs resetting on OSX, because rts is connected to reset
self.stream.port = devPath
# HACK: If the platform driving the serial port is unable to leave the RTS pin in high-impedance
# mode, set RTS to false so that the device platform won&#39;t be reset spuriously.
# Linux does this properly, so don&#39;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__(
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow)
&#34;&#34;&#34;true if platform driving the serial port is Windows Subsystem for Linux 1.&#34;&#34;&#34;
def _isWsl1(self):
# WSL1 identifies itself as Linux, but has a special char device at /dev/lxss for use with session control,
# e.g. /init. We should treat WSL1 as Windows for the RTS-driving hack because the underlying platfrom
# serial driver for the CP21xx still exhibits the buggy behavior.
# 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() == &#39;Linux&#39; and stat.S_ISCHR(os.stat(&#39;/dev/lxss&#39;).st_mode)
except:
# Couldn&#39;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 &#34;share&#34; 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() != &#39;Linux&#39; or self._isWsl1()</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="meshtastic.serial_interface.SerialInterface"><code class="flex name class">
<span>class <span class="ident">SerialInterface</span></span>
<span>(</span><span>devPath=None, debugOut=None, noProto=False, connectNow=True)</span>
</code></dt>
<dd>
<div class="desc"><p>Interface class for meshtastic devices over a serial link</p>
<p>Constructor, opens a connection to a specified serial port, or if unspecified try to
find one Meshtastic device by probing</p>
<p>Keyword Arguments:
devPath {string} &ndash; A filepath to a device, i.e. /dev/ttyUSB0 (default: {None})
debugOut {stream} &ndash; If a stream is provided, any debug serial output from the device will be emitted to that stream. (default: {None})</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class SerialInterface(StreamInterface):
&#34;&#34;&#34;Interface class for meshtastic devices over a serial link&#34;&#34;&#34;
def __init__(self, devPath=None, debugOut=None, noProto=False, connectNow=True):
&#34;&#34;&#34;Constructor, opens a connection to a specified serial port, or if unspecified try to
find one Meshtastic device by probing
Keyword Arguments:
devPath {string} -- A filepath to a device, i.e. /dev/ttyUSB0 (default: {None})
debugOut {stream} -- If a stream is provided, any debug serial output from the device will be emitted to that stream. (default: {None})
&#34;&#34;&#34;
if devPath is None:
ports = meshtastic.util.findPorts()
logging.debug(f&#34;ports:{ports}&#34;)
if len(ports) == 0:
meshtastic.util.our_exit(&#34;Warning: No Meshtastic devices detected.&#34;)
elif len(ports) &gt; 1:
message = &#34;Warning: Multiple serial ports were detected so one serial port must be specified with the &#39;--port&#39;.\n&#34;
message += f&#34; Ports detected:{ports}&#34;
meshtastic.util.our_exit(message)
else:
devPath = ports[0]
logging.debug(f&#34;Connecting to {devPath}&#34;)
# Note: we provide None for port here, because we will be opening it later
self.stream = serial.Serial(
None, 921600, exclusive=True, timeout=0.5, write_timeout=0)
# rts=False Needed to prevent TBEAMs resetting on OSX, because rts is connected to reset
self.stream.port = devPath
# HACK: If the platform driving the serial port is unable to leave the RTS pin in high-impedance
# mode, set RTS to false so that the device platform won&#39;t be reset spuriously.
# Linux does this properly, so don&#39;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__(
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow)
&#34;&#34;&#34;true if platform driving the serial port is Windows Subsystem for Linux 1.&#34;&#34;&#34;
def _isWsl1(self):
# WSL1 identifies itself as Linux, but has a special char device at /dev/lxss for use with session control,
# e.g. /init. We should treat WSL1 as Windows for the RTS-driving hack because the underlying platfrom
# serial driver for the CP21xx still exhibits the buggy behavior.
# 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() == &#39;Linux&#39; and stat.S_ISCHR(os.stat(&#39;/dev/lxss&#39;).st_mode)
except:
# Couldn&#39;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 &#34;share&#34; 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() != &#39;Linux&#39; or self._isWsl1()</code></pre>
</details>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="meshtastic.stream_interface.StreamInterface" href="stream_interface.html#meshtastic.stream_interface.StreamInterface">StreamInterface</a></li>
<li><a title="meshtastic.mesh_interface.MeshInterface" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface">MeshInterface</a></li>
</ul>
<h3>Inherited members</h3>
<ul class="hlist">
<li><code><b><a title="meshtastic.stream_interface.StreamInterface" href="stream_interface.html#meshtastic.stream_interface.StreamInterface">StreamInterface</a></b></code>:
<ul class="hlist">
<li><code><a title="meshtastic.stream_interface.StreamInterface.close" href="stream_interface.html#meshtastic.stream_interface.StreamInterface.close">close</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.connect" href="stream_interface.html#meshtastic.stream_interface.StreamInterface.connect">connect</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.getLongName" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getLongName">getLongName</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.getMyNodeInfo" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getMyNodeInfo">getMyNodeInfo</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.getMyUser" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getMyUser">getMyUser</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.getNode" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getNode">getNode</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.getShortName" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getShortName">getShortName</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.sendData" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.sendData">sendData</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.sendPosition" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.sendPosition">sendPosition</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.sendText" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.sendText">sendText</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.showInfo" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.showInfo">showInfo</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.showNodes" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.showNodes">showNodes</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.waitForConfig" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.waitForConfig">waitForConfig</a></code></li>
</ul>
</li>
</ul>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic" href="index.html">meshtastic</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="meshtastic.serial_interface.SerialInterface" href="#meshtastic.serial_interface.SerialInterface">SerialInterface</a></code></h4>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,510 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.stream_interface API documentation</title>
<meta name="description" content="Stream Interface base class" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.stream_interface</code></h1>
</header>
<section id="section-intro">
<p>Stream Interface base class</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Stream Interface base class
&#34;&#34;&#34;
import logging
import threading
import time
import traceback
import serial
from .mesh_interface import MeshInterface
from .util import stripnl
START1 = 0x94
START2 = 0xc3
HEADER_LEN = 4
MAX_TO_FROM_RADIO_SIZE = 512
class StreamInterface(MeshInterface):
&#34;&#34;&#34;Interface class for meshtastic devices over a stream link (serial, TCP, etc)&#34;&#34;&#34;
def __init__(self, debugOut=None, noProto=False, connectNow=True):
&#34;&#34;&#34;Constructor, opens a connection to self.stream
Keyword Arguments:
devPath {string} -- A filepath to a device, i.e. /dev/ttyUSB0 (default: {None})
debugOut {stream} -- If a stream is provided, any debug serial output from the
device will be emitted to that stream. (default: {None})
Raises:
Exception: [description]
Exception: [description]
&#34;&#34;&#34;
if not hasattr(self, &#39;stream&#39;):
raise Exception(
&#34;StreamInterface is now abstract (to update existing code create SerialInterface instead)&#34;)
self._rxBuf = bytes() # empty
self._wantExit = False
# FIXME, figure out why daemon=True causes reader thread to exit too early
self._rxThread = threading.Thread(
target=self.__reader, args=(), daemon=True)
MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto)
# Start the reader thread after superclass constructor completes init
if connectNow:
self.connect()
if not noProto:
self.waitForConfig()
def connect(self):
&#34;&#34;&#34;Connect to our radio
Normally this is called automatically by the constructor, but if you
passed in connectNow=False you can manually start the reading thread later.
&#34;&#34;&#34;
# Send some bogus UART characters to force a sleeping device to wake, and
# if the reading statemachine was parsing a bad packet make sure
# we write enought start bytes to force it to resync (we don&#39;t use START1
# because we want to ensure it is looking for START1)
p = bytearray([START2] * 32)
self._writeBytes(p)
time.sleep(0.1) # wait 100ms to give device time to start running
self._rxThread.start()
self._startConfig()
if not self.noProto: # Wait for the db download if using the protocol
self._waitConnected()
def _disconnected(self):
&#34;&#34;&#34;We override the superclass implementation to close our port&#34;&#34;&#34;
MeshInterface._disconnected(self)
logging.debug(&#34;Closing our port&#34;)
# pylint: disable=E0203
if not self.stream is None:
# pylint: disable=E0203
self.stream.close()
# pylint: disable=W0201
self.stream = None
def _writeBytes(self, b):
&#34;&#34;&#34;Write an array of bytes to our stream and flush&#34;&#34;&#34;
if self.stream: # ignore writes when stream is closed
self.stream.write(b)
self.stream.flush()
def _readBytes(self, length):
&#34;&#34;&#34;Read an array of bytes from our stream&#34;&#34;&#34;
return self.stream.read(length)
def _sendToRadioImpl(self, toRadio):
&#34;&#34;&#34;Send a ToRadio protobuf to the device&#34;&#34;&#34;
logging.debug(f&#34;Sending: {stripnl(toRadio)}&#34;)
b = toRadio.SerializeToString()
bufLen = len(b)
# We convert into a string, because the TCP code doesn&#39;t work with byte arrays
header = bytes([START1, START2, (bufLen &gt;&gt; 8) &amp; 0xff, bufLen &amp; 0xff])
self._writeBytes(header + b)
def close(self):
&#34;&#34;&#34;Close a connection to the device&#34;&#34;&#34;
logging.debug(&#34;Closing stream&#34;)
MeshInterface.close(self)
# pyserial cancel_read doesn&#39;t seem to work, therefore we ask the
# reader thread to close things for us
self._wantExit = True
if self._rxThread != threading.current_thread():
self._rxThread.join() # wait for it to exit
def __reader(self):
&#34;&#34;&#34;The reader thread that reads bytes from our stream&#34;&#34;&#34;
empty = bytes()
try:
while not self._wantExit:
# logging.debug(&#34;reading character&#34;)
b = self._readBytes(1)
# logging.debug(&#34;In reader loop&#34;)
# logging.debug(f&#34;read returned {b}&#34;)
if len(b) &gt; 0:
c = b[0]
ptr = len(self._rxBuf)
# Assume we want to append this byte, fixme use bytearray instead
self._rxBuf = self._rxBuf + b
if ptr == 0: # looking for START1
if c != START1:
self._rxBuf = empty # failed to find start
if self.debugOut is not None:
try:
self.debugOut.write(b.decode(&#34;utf-8&#34;))
except:
self.debugOut.write(&#39;?&#39;)
elif ptr == 1: # looking for START2
if c != START2:
self._rxBuf = empty # failed to find start2
elif ptr &gt;= HEADER_LEN - 1: # we&#39;ve at least got a header
# big endian length follos header
packetlen = (self._rxBuf[2] &lt;&lt; 8) + self._rxBuf[3]
if ptr == HEADER_LEN - 1: # we _just_ finished reading the header, validate length
if packetlen &gt; MAX_TO_FROM_RADIO_SIZE:
self._rxBuf = empty # length ws out out bounds, restart
if len(self._rxBuf) != 0 and ptr + 1 &gt;= packetlen + HEADER_LEN:
try:
self._handleFromRadio(self._rxBuf[HEADER_LEN:])
except Exception as ex:
logging.error(f&#34;Error while handling message from radio {ex}&#34;)
traceback.print_exc()
self._rxBuf = empty
else:
# logging.debug(f&#34;timeout&#34;)
pass
except serial.SerialException as ex:
if not self._wantExit: # We might intentionally get an exception during shutdown
logging.warning(f&#34;Meshtastic serial port disconnected, disconnecting... {ex}&#34;)
except OSError as ex:
if not self._wantExit: # We might intentionally get an exception during shutdown
logging.error(f&#34;Unexpected OSError, terminating meshtastic reader... {ex}&#34;)
except Exception as ex:
logging.error(f&#34;Unexpected exception, terminating meshtastic reader... {ex}&#34;)
finally:
logging.debug(&#34;reader is exiting&#34;)
self._disconnected()</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="meshtastic.stream_interface.StreamInterface"><code class="flex name class">
<span>class <span class="ident">StreamInterface</span></span>
<span>(</span><span>debugOut=None, noProto=False, connectNow=True)</span>
</code></dt>
<dd>
<div class="desc"><p>Interface class for meshtastic devices over a stream link (serial, TCP, etc)</p>
<p>Constructor, opens a connection to self.stream</p>
<p>Keyword Arguments:
devPath {string} &ndash; A filepath to a device, i.e. /dev/ttyUSB0 (default: {None})
debugOut {stream} &ndash; If a stream is provided, any debug serial output from the
device will be emitted to that stream. (default: {None})</p>
<h2 id="raises">Raises</h2>
<dl>
<dt><code>Exception</code></dt>
<dd>[description]</dd>
<dt><code>Exception</code></dt>
<dd>[description]</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class StreamInterface(MeshInterface):
&#34;&#34;&#34;Interface class for meshtastic devices over a stream link (serial, TCP, etc)&#34;&#34;&#34;
def __init__(self, debugOut=None, noProto=False, connectNow=True):
&#34;&#34;&#34;Constructor, opens a connection to self.stream
Keyword Arguments:
devPath {string} -- A filepath to a device, i.e. /dev/ttyUSB0 (default: {None})
debugOut {stream} -- If a stream is provided, any debug serial output from the
device will be emitted to that stream. (default: {None})
Raises:
Exception: [description]
Exception: [description]
&#34;&#34;&#34;
if not hasattr(self, &#39;stream&#39;):
raise Exception(
&#34;StreamInterface is now abstract (to update existing code create SerialInterface instead)&#34;)
self._rxBuf = bytes() # empty
self._wantExit = False
# FIXME, figure out why daemon=True causes reader thread to exit too early
self._rxThread = threading.Thread(
target=self.__reader, args=(), daemon=True)
MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto)
# Start the reader thread after superclass constructor completes init
if connectNow:
self.connect()
if not noProto:
self.waitForConfig()
def connect(self):
&#34;&#34;&#34;Connect to our radio
Normally this is called automatically by the constructor, but if you
passed in connectNow=False you can manually start the reading thread later.
&#34;&#34;&#34;
# Send some bogus UART characters to force a sleeping device to wake, and
# if the reading statemachine was parsing a bad packet make sure
# we write enought start bytes to force it to resync (we don&#39;t use START1
# because we want to ensure it is looking for START1)
p = bytearray([START2] * 32)
self._writeBytes(p)
time.sleep(0.1) # wait 100ms to give device time to start running
self._rxThread.start()
self._startConfig()
if not self.noProto: # Wait for the db download if using the protocol
self._waitConnected()
def _disconnected(self):
&#34;&#34;&#34;We override the superclass implementation to close our port&#34;&#34;&#34;
MeshInterface._disconnected(self)
logging.debug(&#34;Closing our port&#34;)
# pylint: disable=E0203
if not self.stream is None:
# pylint: disable=E0203
self.stream.close()
# pylint: disable=W0201
self.stream = None
def _writeBytes(self, b):
&#34;&#34;&#34;Write an array of bytes to our stream and flush&#34;&#34;&#34;
if self.stream: # ignore writes when stream is closed
self.stream.write(b)
self.stream.flush()
def _readBytes(self, length):
&#34;&#34;&#34;Read an array of bytes from our stream&#34;&#34;&#34;
return self.stream.read(length)
def _sendToRadioImpl(self, toRadio):
&#34;&#34;&#34;Send a ToRadio protobuf to the device&#34;&#34;&#34;
logging.debug(f&#34;Sending: {stripnl(toRadio)}&#34;)
b = toRadio.SerializeToString()
bufLen = len(b)
# We convert into a string, because the TCP code doesn&#39;t work with byte arrays
header = bytes([START1, START2, (bufLen &gt;&gt; 8) &amp; 0xff, bufLen &amp; 0xff])
self._writeBytes(header + b)
def close(self):
&#34;&#34;&#34;Close a connection to the device&#34;&#34;&#34;
logging.debug(&#34;Closing stream&#34;)
MeshInterface.close(self)
# pyserial cancel_read doesn&#39;t seem to work, therefore we ask the
# reader thread to close things for us
self._wantExit = True
if self._rxThread != threading.current_thread():
self._rxThread.join() # wait for it to exit
def __reader(self):
&#34;&#34;&#34;The reader thread that reads bytes from our stream&#34;&#34;&#34;
empty = bytes()
try:
while not self._wantExit:
# logging.debug(&#34;reading character&#34;)
b = self._readBytes(1)
# logging.debug(&#34;In reader loop&#34;)
# logging.debug(f&#34;read returned {b}&#34;)
if len(b) &gt; 0:
c = b[0]
ptr = len(self._rxBuf)
# Assume we want to append this byte, fixme use bytearray instead
self._rxBuf = self._rxBuf + b
if ptr == 0: # looking for START1
if c != START1:
self._rxBuf = empty # failed to find start
if self.debugOut is not None:
try:
self.debugOut.write(b.decode(&#34;utf-8&#34;))
except:
self.debugOut.write(&#39;?&#39;)
elif ptr == 1: # looking for START2
if c != START2:
self._rxBuf = empty # failed to find start2
elif ptr &gt;= HEADER_LEN - 1: # we&#39;ve at least got a header
# big endian length follos header
packetlen = (self._rxBuf[2] &lt;&lt; 8) + self._rxBuf[3]
if ptr == HEADER_LEN - 1: # we _just_ finished reading the header, validate length
if packetlen &gt; MAX_TO_FROM_RADIO_SIZE:
self._rxBuf = empty # length ws out out bounds, restart
if len(self._rxBuf) != 0 and ptr + 1 &gt;= packetlen + HEADER_LEN:
try:
self._handleFromRadio(self._rxBuf[HEADER_LEN:])
except Exception as ex:
logging.error(f&#34;Error while handling message from radio {ex}&#34;)
traceback.print_exc()
self._rxBuf = empty
else:
# logging.debug(f&#34;timeout&#34;)
pass
except serial.SerialException as ex:
if not self._wantExit: # We might intentionally get an exception during shutdown
logging.warning(f&#34;Meshtastic serial port disconnected, disconnecting... {ex}&#34;)
except OSError as ex:
if not self._wantExit: # We might intentionally get an exception during shutdown
logging.error(f&#34;Unexpected OSError, terminating meshtastic reader... {ex}&#34;)
except Exception as ex:
logging.error(f&#34;Unexpected exception, terminating meshtastic reader... {ex}&#34;)
finally:
logging.debug(&#34;reader is exiting&#34;)
self._disconnected()</code></pre>
</details>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="meshtastic.mesh_interface.MeshInterface" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface">MeshInterface</a></li>
</ul>
<h3>Subclasses</h3>
<ul class="hlist">
<li><a title="meshtastic.serial_interface.SerialInterface" href="serial_interface.html#meshtastic.serial_interface.SerialInterface">SerialInterface</a></li>
<li><a title="meshtastic.tcp_interface.TCPInterface" href="tcp_interface.html#meshtastic.tcp_interface.TCPInterface">TCPInterface</a></li>
</ul>
<h3>Methods</h3>
<dl>
<dt id="meshtastic.stream_interface.StreamInterface.close"><code class="name flex">
<span>def <span class="ident">close</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Close a connection to the device</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def close(self):
&#34;&#34;&#34;Close a connection to the device&#34;&#34;&#34;
logging.debug(&#34;Closing stream&#34;)
MeshInterface.close(self)
# pyserial cancel_read doesn&#39;t seem to work, therefore we ask the
# reader thread to close things for us
self._wantExit = True
if self._rxThread != threading.current_thread():
self._rxThread.join() # wait for it to exit</code></pre>
</details>
</dd>
<dt id="meshtastic.stream_interface.StreamInterface.connect"><code class="name flex">
<span>def <span class="ident">connect</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Connect to our radio</p>
<p>Normally this is called automatically by the constructor, but if you
passed in connectNow=False you can manually start the reading thread later.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def connect(self):
&#34;&#34;&#34;Connect to our radio
Normally this is called automatically by the constructor, but if you
passed in connectNow=False you can manually start the reading thread later.
&#34;&#34;&#34;
# Send some bogus UART characters to force a sleeping device to wake, and
# if the reading statemachine was parsing a bad packet make sure
# we write enought start bytes to force it to resync (we don&#39;t use START1
# because we want to ensure it is looking for START1)
p = bytearray([START2] * 32)
self._writeBytes(p)
time.sleep(0.1) # wait 100ms to give device time to start running
self._rxThread.start()
self._startConfig()
if not self.noProto: # Wait for the db download if using the protocol
self._waitConnected()</code></pre>
</details>
</dd>
</dl>
<h3>Inherited members</h3>
<ul class="hlist">
<li><code><b><a title="meshtastic.mesh_interface.MeshInterface" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface">MeshInterface</a></b></code>:
<ul class="hlist">
<li><code><a title="meshtastic.mesh_interface.MeshInterface.getLongName" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getLongName">getLongName</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.getMyNodeInfo" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getMyNodeInfo">getMyNodeInfo</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.getMyUser" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getMyUser">getMyUser</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.getNode" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getNode">getNode</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.getShortName" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getShortName">getShortName</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.sendData" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.sendData">sendData</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.sendPosition" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.sendPosition">sendPosition</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.sendText" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.sendText">sendText</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.showInfo" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.showInfo">showInfo</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.showNodes" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.showNodes">showNodes</a></code></li>
<li><code><a title="meshtastic.mesh_interface.MeshInterface.waitForConfig" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.waitForConfig">waitForConfig</a></code></li>
</ul>
</li>
</ul>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic" href="index.html">meshtastic</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="meshtastic.stream_interface.StreamInterface" href="#meshtastic.stream_interface.StreamInterface">StreamInterface</a></code></h4>
<ul class="">
<li><code><a title="meshtastic.stream_interface.StreamInterface.close" href="#meshtastic.stream_interface.StreamInterface.close">close</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.connect" href="#meshtastic.stream_interface.StreamInterface.connect">connect</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,207 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.tcp_interface API documentation</title>
<meta name="description" content="TCPInterface class for interfacing with http endpoint" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.tcp_interface</code></h1>
</header>
<section id="section-intro">
<p>TCPInterface class for interfacing with http endpoint</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;TCPInterface class for interfacing with http endpoint
&#34;&#34;&#34;
import logging
import socket
from typing import AnyStr
from .stream_interface import StreamInterface
class TCPInterface(StreamInterface):
&#34;&#34;&#34;Interface class for meshtastic devices over a TCP link&#34;&#34;&#34;
def __init__(self, hostname: AnyStr, debugOut=None, noProto=False,
connectNow=True, portNumber=4403):
&#34;&#34;&#34;Constructor, opens a connection to a specified IP address/hostname
Keyword Arguments:
hostname {string} -- Hostname/IP address of the device to connect to
&#34;&#34;&#34;
logging.debug(f&#34;Connecting to {hostname}&#34;)
server_address = (hostname, portNumber)
sock = socket.create_connection(server_address)
# Instead of wrapping as a stream, we use the native socket API
# self.stream = sock.makefile(&#39;rw&#39;)
self.stream = None
self.socket = sock
StreamInterface.__init__(
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow)
def close(self):
&#34;&#34;&#34;Close a connection to the device&#34;&#34;&#34;
logging.debug(&#34;Closing TCP stream&#34;)
StreamInterface.close(self)
# Sometimes the socket read might be blocked in the reader thread.
# Therefore we force the shutdown by closing the socket here
self._wantExit = True
if not self.socket is None:
try:
self.socket.shutdown(socket.SHUT_RDWR)
except:
pass # Ignore errors in shutdown, because we might have a race with the server
self.socket.close()
def _writeBytes(self, b):
&#34;&#34;&#34;Write an array of bytes to our stream and flush&#34;&#34;&#34;
self.socket.send(b)
def _readBytes(self, length):
&#34;&#34;&#34;Read an array of bytes from our stream&#34;&#34;&#34;
return self.socket.recv(length)</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="meshtastic.tcp_interface.TCPInterface"><code class="flex name class">
<span>class <span class="ident">TCPInterface</span></span>
<span>(</span><span>hostname: ~AnyStr, debugOut=None, noProto=False, connectNow=True, portNumber=4403)</span>
</code></dt>
<dd>
<div class="desc"><p>Interface class for meshtastic devices over a TCP link</p>
<p>Constructor, opens a connection to a specified IP address/hostname</p>
<p>Keyword Arguments:
hostname {string} &ndash; Hostname/IP address of the device to connect to</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class TCPInterface(StreamInterface):
&#34;&#34;&#34;Interface class for meshtastic devices over a TCP link&#34;&#34;&#34;
def __init__(self, hostname: AnyStr, debugOut=None, noProto=False,
connectNow=True, portNumber=4403):
&#34;&#34;&#34;Constructor, opens a connection to a specified IP address/hostname
Keyword Arguments:
hostname {string} -- Hostname/IP address of the device to connect to
&#34;&#34;&#34;
logging.debug(f&#34;Connecting to {hostname}&#34;)
server_address = (hostname, portNumber)
sock = socket.create_connection(server_address)
# Instead of wrapping as a stream, we use the native socket API
# self.stream = sock.makefile(&#39;rw&#39;)
self.stream = None
self.socket = sock
StreamInterface.__init__(
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow)
def close(self):
&#34;&#34;&#34;Close a connection to the device&#34;&#34;&#34;
logging.debug(&#34;Closing TCP stream&#34;)
StreamInterface.close(self)
# Sometimes the socket read might be blocked in the reader thread.
# Therefore we force the shutdown by closing the socket here
self._wantExit = True
if not self.socket is None:
try:
self.socket.shutdown(socket.SHUT_RDWR)
except:
pass # Ignore errors in shutdown, because we might have a race with the server
self.socket.close()
def _writeBytes(self, b):
&#34;&#34;&#34;Write an array of bytes to our stream and flush&#34;&#34;&#34;
self.socket.send(b)
def _readBytes(self, length):
&#34;&#34;&#34;Read an array of bytes from our stream&#34;&#34;&#34;
return self.socket.recv(length)</code></pre>
</details>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="meshtastic.stream_interface.StreamInterface" href="stream_interface.html#meshtastic.stream_interface.StreamInterface">StreamInterface</a></li>
<li><a title="meshtastic.mesh_interface.MeshInterface" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface">MeshInterface</a></li>
</ul>
<h3>Inherited members</h3>
<ul class="hlist">
<li><code><b><a title="meshtastic.stream_interface.StreamInterface" href="stream_interface.html#meshtastic.stream_interface.StreamInterface">StreamInterface</a></b></code>:
<ul class="hlist">
<li><code><a title="meshtastic.stream_interface.StreamInterface.close" href="stream_interface.html#meshtastic.stream_interface.StreamInterface.close">close</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.connect" href="stream_interface.html#meshtastic.stream_interface.StreamInterface.connect">connect</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.getLongName" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getLongName">getLongName</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.getMyNodeInfo" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getMyNodeInfo">getMyNodeInfo</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.getMyUser" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getMyUser">getMyUser</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.getNode" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getNode">getNode</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.getShortName" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.getShortName">getShortName</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.sendData" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.sendData">sendData</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.sendPosition" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.sendPosition">sendPosition</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.sendText" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.sendText">sendText</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.showInfo" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.showInfo">showInfo</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.showNodes" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.showNodes">showNodes</a></code></li>
<li><code><a title="meshtastic.stream_interface.StreamInterface.waitForConfig" href="mesh_interface.html#meshtastic.mesh_interface.MeshInterface.waitForConfig">waitForConfig</a></code></li>
</ul>
</li>
</ul>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic" href="index.html">meshtastic</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="meshtastic.tcp_interface.TCPInterface" href="#meshtastic.tcp_interface.TCPInterface">TCPInterface</a></code></h4>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -3,9 +3,10 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.9.2" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.test API documentation</title>
<meta name="description" content="" />
<meta name="description" content="With two radios connected serially, send and receive test
messages and report back if successful." />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
@@ -22,18 +23,26 @@
<h1 class="title">Module <code>meshtastic.test</code></h1>
</header>
<section id="section-intro">
<p>With two radios connected serially, send and receive test
messages and report back if successful.</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">import logging
from . import util
from . import SerialInterface, TCPInterface, BROADCAST_NUM
from pubsub import pub
<pre><code class="python">&#34;&#34;&#34;With two radios connected serially, send and receive test
messages and report back if successful.
&#34;&#34;&#34;
import logging
import time
import sys
import threading, traceback
import traceback
from dotmap import DotMap
from pubsub import pub
import meshtastic.util
from .__init__ import BROADCAST_NUM
from .serial_interface import SerialInterface
from .tcp_interface import TCPInterface
&#34;&#34;&#34;The interfaces we are using for our tests&#34;&#34;&#34;
interfaces = None
@@ -59,7 +68,7 @@ def onReceive(packet, interface):
if p.decoded.portnum == &#34;TEXT_MESSAGE_APP&#34;:
# We only care a about clear text packets
if receivedPackets != None:
if receivedPackets is not None:
receivedPackets.append(p)
@@ -103,18 +112,19 @@ def testSend(fromInterface, toInterface, isBroadcast=False, asBinary=False, want
else:
fromInterface.sendData((f&#34;Binary {testNumber}&#34;).encode(
&#34;utf-8&#34;), toNode, wantAck=wantAck)
for sec in range(60): # max of 60 secs before we timeout
for _ in range(60): # max of 60 secs before we timeout
time.sleep(1)
if (len(receivedPackets) &gt;= 1):
if len(receivedPackets) &gt;= 1:
return True
return False # Failed to send
def runTests(numTests=50, wantAck=False, maxFailures=0):
&#34;&#34;&#34;Run the tests.&#34;&#34;&#34;
logging.info(f&#34;Running {numTests} tests with wantAck={wantAck}&#34;)
numFail = 0
numSuccess = 0
for i in range(numTests):
for _ in range(numTests):
global testNumber
testNumber = testNumber + 1
isBroadcast = True
@@ -130,24 +140,23 @@ def runTests(numTests=50, wantAck=False, maxFailures=0):
logging.info(
f&#34;Test {testNumber} succeeded {numSuccess} successes {numFail} failures so far&#34;)
# if numFail &gt;= 3:
# for i in interfaces:
# i.close()
# return
time.sleep(1)
if numFail &gt; maxFailures:
logging.error(&#34;Too many failures! Test failed!&#34;)
return numFail
return False
return True
def testThread(numTests=50):
&#34;&#34;&#34;Test thread&#34;&#34;&#34;
logging.info(&#34;Found devices, starting tests...&#34;)
runTests(numTests, wantAck=True)
# Allow a few dropped packets
runTests(numTests, wantAck=False, maxFailures=5)
result = runTests(numTests, wantAck=True)
if result:
# Run another test
# Allow a few dropped packets
result = runTests(numTests, wantAck=False, maxFailures=1)
return result
def onConnection(topic=pub.AUTO_TOPIC):
@@ -156,21 +165,21 @@ def onConnection(topic=pub.AUTO_TOPIC):
def openDebugLog(portName):
&#34;&#34;&#34;Open the debug log file&#34;&#34;&#34;
debugname = &#34;log&#34; + portName.replace(&#34;/&#34;, &#34;_&#34;)
logging.info(f&#34;Writing serial debugging to {debugname}&#34;)
return open(debugname, &#39;w+&#39;, buffering=1)
return open(debugname, &#39;w+&#39;, buffering=1, encoding=&#39;utf8&#39;)
def testAll():
def testAll(numTests=5):
&#34;&#34;&#34;
Run a series of tests using devices we can find.
This is called from the cli with the &#34;--test&#34; option.
Raises:
Exception: If not enough devices are found
&#34;&#34;&#34;
ports = util.findPorts()
if (len(ports) &lt; 2):
raise Exception(&#34;Must have at least two devices connected to USB&#34;)
ports = meshtastic.util.findPorts()
if len(ports) &lt; 2:
meshtastic.util.our_exit(&#34;Warning: Must have at least two devices connected to USB.&#34;)
pub.subscribe(onConnection, &#34;meshtastic.connection&#34;)
pub.subscribe(onReceive, &#34;meshtastic.receive&#34;)
@@ -179,11 +188,13 @@ def testAll():
port, debugOut=openDebugLog(port), connectNow=True), ports))
logging.info(&#34;Ports opened, starting test&#34;)
testThread()
result = testThread(numTests)
for i in interfaces:
i.close()
return result
def testSimulator():
&#34;&#34;&#34;
@@ -194,7 +205,7 @@ def testSimulator():
Run with
python3 -c &#39;from meshtastic.test import testSimulator; testSimulator()&#39;
&#34;&#34;&#34;
logging.basicConfig(level=logging.DEBUG if False else logging.INFO)
logging.basicConfig(level=logging.DEBUG)
logging.info(&#34;Connecting to simulator on localhost!&#34;)
try:
iface = TCPInterface(&#34;localhost&#34;)
@@ -272,7 +283,7 @@ def testSimulator():
if p.decoded.portnum == &#34;TEXT_MESSAGE_APP&#34;:
# We only care a about clear text packets
if receivedPackets != None:
if receivedPackets is not None:
receivedPackets.append(p)</code></pre>
</details>
</dd>
@@ -280,31 +291,33 @@ def testSimulator():
<span>def <span class="ident">openDebugLog</span></span>(<span>portName)</span>
</code></dt>
<dd>
<div class="desc"></div>
<div class="desc"><p>Open the debug log file</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def openDebugLog(portName):
&#34;&#34;&#34;Open the debug log file&#34;&#34;&#34;
debugname = &#34;log&#34; + portName.replace(&#34;/&#34;, &#34;_&#34;)
logging.info(f&#34;Writing serial debugging to {debugname}&#34;)
return open(debugname, &#39;w+&#39;, buffering=1)</code></pre>
return open(debugname, &#39;w+&#39;, buffering=1, encoding=&#39;utf8&#39;)</code></pre>
</details>
</dd>
<dt id="meshtastic.test.runTests"><code class="name flex">
<span>def <span class="ident">runTests</span></span>(<span>numTests=50, wantAck=False, maxFailures=0)</span>
</code></dt>
<dd>
<div class="desc"></div>
<div class="desc"><p>Run the tests.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def runTests(numTests=50, wantAck=False, maxFailures=0):
&#34;&#34;&#34;Run the tests.&#34;&#34;&#34;
logging.info(f&#34;Running {numTests} tests with wantAck={wantAck}&#34;)
numFail = 0
numSuccess = 0
for i in range(numTests):
for _ in range(numTests):
global testNumber
testNumber = testNumber + 1
isBroadcast = True
@@ -320,17 +333,12 @@ def testSimulator():
logging.info(
f&#34;Test {testNumber} succeeded {numSuccess} successes {numFail} failures so far&#34;)
# if numFail &gt;= 3:
# for i in interfaces:
# i.close()
# return
time.sleep(1)
if numFail &gt; maxFailures:
logging.error(&#34;Too many failures! Test failed!&#34;)
return numFail</code></pre>
return False
return True</code></pre>
</details>
</dd>
<dt id="meshtastic.test.subscribe"><code class="name flex">
@@ -349,29 +357,24 @@ def testSimulator():
</details>
</dd>
<dt id="meshtastic.test.testAll"><code class="name flex">
<span>def <span class="ident">testAll</span></span>(<span>)</span>
<span>def <span class="ident">testAll</span></span>(<span>numTests=5)</span>
</code></dt>
<dd>
<div class="desc"><p>Run a series of tests using devices we can find.</p>
<h2 id="raises">Raises</h2>
<dl>
<dt><code>Exception</code></dt>
<dd>If not enough devices are found</dd>
</dl></div>
<div class="desc"><p>Run a series of tests using devices we can find.
This is called from the cli with the "&ndash;test" option.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def testAll():
<pre><code class="python">def testAll(numTests=5):
&#34;&#34;&#34;
Run a series of tests using devices we can find.
This is called from the cli with the &#34;--test&#34; option.
Raises:
Exception: If not enough devices are found
&#34;&#34;&#34;
ports = util.findPorts()
if (len(ports) &lt; 2):
raise Exception(&#34;Must have at least two devices connected to USB&#34;)
ports = meshtastic.util.findPorts()
if len(ports) &lt; 2:
meshtastic.util.our_exit(&#34;Warning: Must have at least two devices connected to USB.&#34;)
pub.subscribe(onConnection, &#34;meshtastic.connection&#34;)
pub.subscribe(onReceive, &#34;meshtastic.receive&#34;)
@@ -380,10 +383,12 @@ def testSimulator():
port, debugOut=openDebugLog(port), connectNow=True), ports))
logging.info(&#34;Ports opened, starting test&#34;)
testThread()
result = testThread(numTests)
for i in interfaces:
i.close()</code></pre>
i.close()
return result</code></pre>
</details>
</dd>
<dt id="meshtastic.test.testSend"><code class="name flex">
@@ -429,9 +434,9 @@ toInterface {[type]} &ndash; [description]</p>
else:
fromInterface.sendData((f&#34;Binary {testNumber}&#34;).encode(
&#34;utf-8&#34;), toNode, wantAck=wantAck)
for sec in range(60): # max of 60 secs before we timeout
for _ in range(60): # max of 60 secs before we timeout
time.sleep(1)
if (len(receivedPackets) &gt;= 1):
if len(receivedPackets) &gt;= 1:
return True
return False # Failed to send</code></pre>
</details>
@@ -458,7 +463,7 @@ python3 -c 'from meshtastic.test import testSimulator; testSimulator()'</p></div
Run with
python3 -c &#39;from meshtastic.test import testSimulator; testSimulator()&#39;
&#34;&#34;&#34;
logging.basicConfig(level=logging.DEBUG if False else logging.INFO)
logging.basicConfig(level=logging.DEBUG)
logging.info(&#34;Connecting to simulator on localhost!&#34;)
try:
iface = TCPInterface(&#34;localhost&#34;)
@@ -478,16 +483,20 @@ python3 -c 'from meshtastic.test import testSimulator; testSimulator()'</p></div
<span>def <span class="ident">testThread</span></span>(<span>numTests=50)</span>
</code></dt>
<dd>
<div class="desc"></div>
<div class="desc"><p>Test thread</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def testThread(numTests=50):
&#34;&#34;&#34;Test thread&#34;&#34;&#34;
logging.info(&#34;Found devices, starting tests...&#34;)
runTests(numTests, wantAck=True)
# Allow a few dropped packets
runTests(numTests, wantAck=False, maxFailures=5)</code></pre>
result = runTests(numTests, wantAck=True)
if result:
# Run another test
# Allow a few dropped packets
result = runTests(numTests, wantAck=False, maxFailures=1)
return result</code></pre>
</details>
</dd>
</dl>
@@ -529,7 +538,7 @@ python3 -c 'from meshtastic.test import testSimulator; testSimulator()'</p></div
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc"><cite>pdoc</cite> 0.9.2</a>.</p>
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,80 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.test API documentation</title>
<meta name="description" content="" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.test</code></h1>
</header>
<section id="section-intro">
</section>
<section>
<h2 class="section-title" id="header-submodules">Sub-modules</h2>
<dl>
<dt><code class="name"><a title="meshtastic.test.test_int" href="test_int.html">meshtastic.test.test_int</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic integration tests</p></div>
</dd>
<dt><code class="name"><a title="meshtastic.test.test_mesh_interface" href="test_mesh_interface.html">meshtastic.test.test_mesh_interface</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic unit tests for node.py</p></div>
</dd>
<dt><code class="name"><a title="meshtastic.test.test_smoke1" href="test_smoke1.html">meshtastic.test.test_smoke1</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic smoke tests with a single device</p></div>
</dd>
<dt><code class="name"><a title="meshtastic.test.test_util" href="test_util.html">meshtastic.test.test_util</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic unit tests for node.py</p></div>
</dd>
</dl>
</section>
<section>
</section>
<section>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic" href="../index.html">meshtastic</a></code></li>
</ul>
</li>
<li><h3><a href="#header-submodules">Sub-modules</a></h3>
<ul>
<li><code><a title="meshtastic.test.test_int" href="test_int.html">meshtastic.test.test_int</a></code></li>
<li><code><a title="meshtastic.test.test_mesh_interface" href="test_mesh_interface.html">meshtastic.test.test_mesh_interface</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1" href="test_smoke1.html">meshtastic.test.test_smoke1</a></code></li>
<li><code><a title="meshtastic.test.test_util" href="test_util.html">meshtastic.test.test_util</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,177 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.test.test_int API documentation</title>
<meta name="description" content="Meshtastic integration tests" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.test.test_int</code></h1>
</header>
<section id="section-intro">
<p>Meshtastic integration tests</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Meshtastic integration tests&#34;&#34;&#34;
import re
import subprocess
import pytest
@pytest.mark.int
def test_int_no_args():
&#34;&#34;&#34;Test without any args&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic&#39;)
assert re.match(r&#39;usage: meshtastic&#39;, out)
assert return_value == 1
@pytest.mark.int
def test_int_version():
&#34;&#34;&#34;Test &#39;--version&#39;.&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --version&#39;)
assert re.match(r&#39;[0-9]+\.[0-9]+\.[0-9]&#39;, out)
assert return_value == 0
@pytest.mark.int
def test_int_help():
&#34;&#34;&#34;Test &#39;--help&#39;.&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --help&#39;)
assert re.match(r&#39;usage: meshtastic &#39;, out)
assert return_value == 0
@pytest.mark.int
def test_int_support():
&#34;&#34;&#34;Test &#39;--support&#39;.&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --support&#39;)
assert re.search(r&#39;System&#39;, out)
assert re.search(r&#39;Python&#39;, out)
assert return_value == 0</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.test.test_int.test_int_help"><code class="name flex">
<span>def <span class="ident">test_int_help</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test '&ndash;help'.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.int
def test_int_help():
&#34;&#34;&#34;Test &#39;--help&#39;.&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --help&#39;)
assert re.match(r&#39;usage: meshtastic &#39;, out)
assert return_value == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_int.test_int_no_args"><code class="name flex">
<span>def <span class="ident">test_int_no_args</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test without any args</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.int
def test_int_no_args():
&#34;&#34;&#34;Test without any args&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic&#39;)
assert re.match(r&#39;usage: meshtastic&#39;, out)
assert return_value == 1</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_int.test_int_support"><code class="name flex">
<span>def <span class="ident">test_int_support</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test '&ndash;support'.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.int
def test_int_support():
&#34;&#34;&#34;Test &#39;--support&#39;.&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --support&#39;)
assert re.search(r&#39;System&#39;, out)
assert re.search(r&#39;Python&#39;, out)
assert return_value == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_int.test_int_version"><code class="name flex">
<span>def <span class="ident">test_int_version</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test '&ndash;version'.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.int
def test_int_version():
&#34;&#34;&#34;Test &#39;--version&#39;.&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --version&#39;)
assert re.match(r&#39;[0-9]+\.[0-9]+\.[0-9]&#39;, out)
assert return_value == 0</code></pre>
</details>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic.test" href="index.html">meshtastic.test</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.test.test_int.test_int_help" href="#meshtastic.test.test_int.test_int_help">test_int_help</a></code></li>
<li><code><a title="meshtastic.test.test_int.test_int_no_args" href="#meshtastic.test.test_int.test_int_no_args">test_int_no_args</a></code></li>
<li><code><a title="meshtastic.test.test_int.test_int_support" href="#meshtastic.test.test_int.test_int_support">test_int_support</a></code></li>
<li><code><a title="meshtastic.test.test_int.test_int_version" href="#meshtastic.test.test_int.test_int_version">test_int_version</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,99 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.test.test_mesh_interface API documentation</title>
<meta name="description" content="Meshtastic unit tests for node.py" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.test.test_mesh_interface</code></h1>
</header>
<section id="section-intro">
<p>Meshtastic unit tests for node.py</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Meshtastic unit tests for node.py&#34;&#34;&#34;
import pytest
from meshtastic.mesh_interface import MeshInterface
@pytest.mark.unit
def test_MeshInterface():
&#34;&#34;&#34;Test that we instantiate a MeshInterface&#34;&#34;&#34;
iface = MeshInterface(noProto=True)
iface.showInfo()
iface.localNode.showInfo()
iface.close()</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.test.test_mesh_interface.test_MeshInterface"><code class="name flex">
<span>def <span class="ident">test_MeshInterface</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test that we instantiate a MeshInterface</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_MeshInterface():
&#34;&#34;&#34;Test that we instantiate a MeshInterface&#34;&#34;&#34;
iface = MeshInterface(noProto=True)
iface.showInfo()
iface.localNode.showInfo()
iface.close()</code></pre>
</details>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic.test" href="index.html">meshtastic.test</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.test.test_mesh_interface.test_MeshInterface" href="#meshtastic.test.test_mesh_interface.test_MeshInterface">test_MeshInterface</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,982 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.test.test_smoke1 API documentation</title>
<meta name="description" content="Meshtastic smoke tests with a single device" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.test.test_smoke1</code></h1>
</header>
<section id="section-intro">
<p>Meshtastic smoke tests with a single device</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Meshtastic smoke tests with a single device&#34;&#34;&#34;
import re
import subprocess
import time
import os
# Do not like using hard coded sleeps, but it probably makes
# sense to pause for the radio at apprpriate times
import pytest
import meshtastic
# seconds to pause after running a meshtastic command
PAUSE_AFTER_COMMAND = 2
PAUSE_AFTER_REBOOT = 7
@pytest.mark.smoke1
def test_smoke1_reboot():
&#34;&#34;&#34;Test reboot&#34;&#34;&#34;
return_value, _ = subprocess.getstatusoutput(&#39;meshtastic --reboot&#39;)
assert return_value == 0
# pause for the radio to reset (10 seconds for the pause, and a few more seconds to be back up)
time.sleep(18)
@pytest.mark.smoke1
def test_smoke1_info():
&#34;&#34;&#34;Test --info&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Owner&#39;, out, re.MULTILINE)
assert re.search(r&#39;^My info&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Nodes in mesh&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Preferences&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Channels&#39;, out, re.MULTILINE)
assert re.search(r&#39;^ PRIMARY&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Primary channel URL&#39;, out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_debug():
&#34;&#34;&#34;Test --debug&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info --debug&#39;)
assert re.search(r&#39;^Owner&#39;, out, re.MULTILINE)
assert re.search(r&#39;^DEBUG:root&#39;, out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_seriallog_to_file():
&#34;&#34;&#34;Test --seriallog to a file creates a file&#34;&#34;&#34;
filename = &#39;tmpoutput.txt&#39;
if os.path.exists(f&#34;{filename}&#34;):
os.remove(f&#34;{filename}&#34;)
return_value, _ = subprocess.getstatusoutput(f&#39;meshtastic --info --seriallog {filename}&#39;)
assert os.path.exists(f&#34;{filename}&#34;)
assert return_value == 0
os.remove(f&#34;{filename}&#34;)
@pytest.mark.smoke1
def test_smoke1_qr():
&#34;&#34;&#34;Test --qr&#34;&#34;&#34;
filename = &#39;tmpqr&#39;
if os.path.exists(f&#34;{filename}&#34;):
os.remove(f&#34;{filename}&#34;)
return_value, _ = subprocess.getstatusoutput(f&#39;meshtastic --qr &gt; {filename}&#39;)
assert os.path.exists(f&#34;{filename}&#34;)
# 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&#34;{filename}&#34;).st_size &gt; 20000
assert return_value == 0
os.remove(f&#34;{filename}&#34;)
@pytest.mark.smoke1
def test_smoke1_nodes():
&#34;&#34;&#34;Test --nodes&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --nodes&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^│ N │ User&#39;, out, re.MULTILINE)
assert re.search(r&#39;^│ 1 │&#39;, out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_send_hello():
&#34;&#34;&#34;Test --sendtext hello&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --sendtext hello&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Sending text message hello to \^all&#39;, out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_port():
&#34;&#34;&#34;Test --port&#34;&#34;&#34;
# first, get the ports
ports = meshtastic.util.findPorts()
# hopefully there is just one
assert len(ports) == 1
port = ports[0]
return_value, out = subprocess.getstatusoutput(f&#39;meshtastic --port {port} --info&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Owner&#39;, out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_set_is_router_true():
&#34;&#34;&#34;Test --set is_router true&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --set is_router true&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Set is_router to true&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --get is_router&#39;)
assert re.search(r&#39;^is_router: True&#39;, out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_set_location_info():
&#34;&#34;&#34;Test --setlat, --setlon and --setalt &#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --setlat 32.7767 --setlon -96.7970 --setalt 1337&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Fixing altitude&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Fixing latitude&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Fixing longitude&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out2 = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.search(r&#39;1337&#39;, out2, re.MULTILINE)
assert re.search(r&#39;32.7767&#39;, out2, re.MULTILINE)
assert re.search(r&#39;-96.797&#39;, out2, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_set_is_router_false():
&#34;&#34;&#34;Test --set is_router false&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --set is_router false&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Set is_router to false&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --get is_router&#39;)
assert re.search(r&#39;^is_router: False&#39;, out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_set_owner():
&#34;&#34;&#34;Test --set-owner name&#34;&#34;&#34;
# make sure the owner is not Joe
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --set-owner Bob&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Setting device owner to Bob&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert not re.search(r&#39;Owner: Joe&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --set-owner Joe&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Setting device owner to Joe&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.search(r&#39;Owner: Joe&#39;, out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_set_team():
&#34;&#34;&#34;Test --set-team &#34;&#34;&#34;
# unset the team
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --set-team CLEAR&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Setting team to CLEAR&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --set-team CYAN&#39;)
assert re.search(r&#39;Setting team to CYAN&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.search(r&#39;CYAN&#39;, out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_ch_values():
&#34;&#34;&#34;Test --ch-longslow, --ch-longfast, --ch-mediumslow, --ch-mediumsfast,
--ch-shortslow, and --ch-shortfast arguments
&#34;&#34;&#34;
exp = {
&#39;--ch-longslow&#39;: &#39;Bw125Cr48Sf4096&#39;,
# TODO: not sure why these fail thru tests, but ok manually
#&#39;--ch-longfast&#39;: &#39;Bw31_25Cr48Sf512&#39;,
#&#39;--ch-mediumslow&#39;: &#39;Bw250Cr46Sf2048&#39;,
#&#39;--ch-mediumfast&#39;: &#39;Bw250Cr47Sf1024&#39;,
# TODO &#39;--ch-shortslow&#39;: &#39;?&#39;,
&#39;--ch-shortfast&#39;: &#39;Bw500Cr45Sf128&#39;
}
for key, val in exp.items():
print(key, val)
return_value, out = subprocess.getstatusoutput(f&#39;meshtastic {key}&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;Writing modified channels to device&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio (might reboot)
time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.search(val, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@pytest.mark.smoke1
def test_smoke1_ch_set_name():
&#34;&#34;&#34;Test --ch-set name&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert not re.search(r&#39;MyChannel&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --ch-set name MyChannel&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Set name to MyChannel&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.search(r&#39;MyChannel&#39;, out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_ch_add_and_ch_del():
&#34;&#34;&#34;Test --ch-add&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --ch-add testing&#39;)
assert re.search(r&#39;Writing modified channels to device&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;SECONDARY&#39;, out, re.MULTILINE)
assert re.search(r&#39;testing&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --ch-index 1 --ch-del&#39;)
assert re.search(r&#39;Deleting channel 1&#39;, 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(&#39;meshtastic --info&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert not re.search(r&#39;SECONDARY&#39;, out, re.MULTILINE)
assert not re.search(r&#39;testing&#39;, out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_ch_set_modem_config():
&#34;&#34;&#34;Test --ch-set modem_config&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert not re.search(r&#39;Bw31_25Cr48Sf512&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --ch-set modem_config Bw31_25Cr48Sf512&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Set modem_config to Bw31_25Cr48Sf512&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.search(r&#39;Bw31_25Cr48Sf512&#39;, out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_seturl_default():
&#34;&#34;&#34;Test --seturl with default value&#34;&#34;&#34;
# set some channel value so we no longer have a default channel
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --ch-set name foo&#39;)
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(&#39;meshtastic --info&#39;)
assert not re.search(&#39;CgUYAyIBAQ&#39;, out, re.MULTILINE)
assert return_value == 0
url = &#34;https://www.meshtastic.org/d/#CgUYAyIBAQ&#34;
return_value, out = subprocess.getstatusoutput(f&#34;meshtastic --seturl {url}&#34;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.search(&#39;CgUYAyIBAQ&#39;, out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_seturl_invalid_url():
&#34;&#34;&#34;Test --seturl with invalid url&#34;&#34;&#34;
# Note: This url is no longer a valid url.
url = &#34;https://www.meshtastic.org/c/#GAMiENTxuzogKQdZ8Lz_q89Oab8qB0RlZmF1bHQ=&#34;
return_value, out = subprocess.getstatusoutput(f&#34;meshtastic --seturl {url}&#34;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(&#39;Warning: There were no settings&#39;, out, re.MULTILINE)
assert return_value == 1
@pytest.mark.smoke1
def test_smoke1_configure():
&#34;&#34;&#34;Test --configure&#34;&#34;&#34;
_ , out = subprocess.getstatusoutput(f&#34;meshtastic --configure example_config.yaml&#34;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(&#39;^Setting device owner to Bob TBeam&#39;, out, re.MULTILINE)
assert re.search(&#39;^Fixing altitude at 304 meters&#39;, out, re.MULTILINE)
assert re.search(&#39;^Fixing latitude at 35.8&#39;, out, re.MULTILINE)
assert re.search(&#39;^Fixing longitude at -93.8&#39;, out, re.MULTILINE)
assert re.search(&#39;^Setting device position&#39;, out, re.MULTILINE)
assert re.search(&#39;^Set region to 1&#39;, out, re.MULTILINE)
assert re.search(&#39;^Set is_always_powered to true&#39;, out, re.MULTILINE)
assert re.search(&#39;^Set send_owner_interval to 2&#39;, out, re.MULTILINE)
assert re.search(&#39;^Set screen_on_secs to 31536000&#39;, out, re.MULTILINE)
assert re.search(&#39;^Set wait_bluetooth_secs to 31536000&#39;, out, re.MULTILINE)
assert re.search(&#39;^Writing modified preferences to device&#39;, out, re.MULTILINE)
@pytest.mark.smoke1
def test_smoke1_factory_reset():
&#34;&#34;&#34;Test factory reset&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --set factory_reset true&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Set factory_reset to true&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Writing modified preferences to device&#39;, out, re.MULTILINE)
assert return_value == 0
# NOTE: The radio may not be responsive after this, may need to do a manual reboot
# by pressing the button</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.test.test_smoke1.test_smoke1_ch_add_and_ch_del"><code class="name flex">
<span>def <span class="ident">test_smoke1_ch_add_and_ch_del</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;ch-add</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_ch_add_and_ch_del():
&#34;&#34;&#34;Test --ch-add&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --ch-add testing&#39;)
assert re.search(r&#39;Writing modified channels to device&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;SECONDARY&#39;, out, re.MULTILINE)
assert re.search(r&#39;testing&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --ch-index 1 --ch-del&#39;)
assert re.search(r&#39;Deleting channel 1&#39;, 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(&#39;meshtastic --info&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert not re.search(r&#39;SECONDARY&#39;, out, re.MULTILINE)
assert not re.search(r&#39;testing&#39;, out, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_ch_set_modem_config"><code class="name flex">
<span>def <span class="ident">test_smoke1_ch_set_modem_config</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;ch-set modem_config</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_ch_set_modem_config():
&#34;&#34;&#34;Test --ch-set modem_config&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert not re.search(r&#39;Bw31_25Cr48Sf512&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --ch-set modem_config Bw31_25Cr48Sf512&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Set modem_config to Bw31_25Cr48Sf512&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.search(r&#39;Bw31_25Cr48Sf512&#39;, out, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_ch_set_name"><code class="name flex">
<span>def <span class="ident">test_smoke1_ch_set_name</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;ch-set name</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_ch_set_name():
&#34;&#34;&#34;Test --ch-set name&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert not re.search(r&#39;MyChannel&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --ch-set name MyChannel&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Set name to MyChannel&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.search(r&#39;MyChannel&#39;, out, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_ch_values"><code class="name flex">
<span>def <span class="ident">test_smoke1_ch_values</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;ch-longslow, &ndash;ch-longfast, &ndash;ch-mediumslow, &ndash;ch-mediumsfast,
&ndash;ch-shortslow, and &ndash;ch-shortfast arguments</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_ch_values():
&#34;&#34;&#34;Test --ch-longslow, --ch-longfast, --ch-mediumslow, --ch-mediumsfast,
--ch-shortslow, and --ch-shortfast arguments
&#34;&#34;&#34;
exp = {
&#39;--ch-longslow&#39;: &#39;Bw125Cr48Sf4096&#39;,
# TODO: not sure why these fail thru tests, but ok manually
#&#39;--ch-longfast&#39;: &#39;Bw31_25Cr48Sf512&#39;,
#&#39;--ch-mediumslow&#39;: &#39;Bw250Cr46Sf2048&#39;,
#&#39;--ch-mediumfast&#39;: &#39;Bw250Cr47Sf1024&#39;,
# TODO &#39;--ch-shortslow&#39;: &#39;?&#39;,
&#39;--ch-shortfast&#39;: &#39;Bw500Cr45Sf128&#39;
}
for key, val in exp.items():
print(key, val)
return_value, out = subprocess.getstatusoutput(f&#39;meshtastic {key}&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;Writing modified channels to device&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio (might reboot)
time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.search(val, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_configure"><code class="name flex">
<span>def <span class="ident">test_smoke1_configure</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;configure</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_configure():
&#34;&#34;&#34;Test --configure&#34;&#34;&#34;
_ , out = subprocess.getstatusoutput(f&#34;meshtastic --configure example_config.yaml&#34;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(&#39;^Setting device owner to Bob TBeam&#39;, out, re.MULTILINE)
assert re.search(&#39;^Fixing altitude at 304 meters&#39;, out, re.MULTILINE)
assert re.search(&#39;^Fixing latitude at 35.8&#39;, out, re.MULTILINE)
assert re.search(&#39;^Fixing longitude at -93.8&#39;, out, re.MULTILINE)
assert re.search(&#39;^Setting device position&#39;, out, re.MULTILINE)
assert re.search(&#39;^Set region to 1&#39;, out, re.MULTILINE)
assert re.search(&#39;^Set is_always_powered to true&#39;, out, re.MULTILINE)
assert re.search(&#39;^Set send_owner_interval to 2&#39;, out, re.MULTILINE)
assert re.search(&#39;^Set screen_on_secs to 31536000&#39;, out, re.MULTILINE)
assert re.search(&#39;^Set wait_bluetooth_secs to 31536000&#39;, out, re.MULTILINE)
assert re.search(&#39;^Writing modified preferences to device&#39;, out, re.MULTILINE)</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_debug"><code class="name flex">
<span>def <span class="ident">test_smoke1_debug</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;debug</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_debug():
&#34;&#34;&#34;Test --debug&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info --debug&#39;)
assert re.search(r&#39;^Owner&#39;, out, re.MULTILINE)
assert re.search(r&#39;^DEBUG:root&#39;, out, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_factory_reset"><code class="name flex">
<span>def <span class="ident">test_smoke1_factory_reset</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test factory reset</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_factory_reset():
&#34;&#34;&#34;Test factory reset&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --set factory_reset true&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Set factory_reset to true&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Writing modified preferences to device&#39;, out, re.MULTILINE)
assert return_value == 0
# NOTE: The radio may not be responsive after this, may need to do a manual reboot
# by pressing the button</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_info"><code class="name flex">
<span>def <span class="ident">test_smoke1_info</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;info</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_info():
&#34;&#34;&#34;Test --info&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Owner&#39;, out, re.MULTILINE)
assert re.search(r&#39;^My info&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Nodes in mesh&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Preferences&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Channels&#39;, out, re.MULTILINE)
assert re.search(r&#39;^ PRIMARY&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Primary channel URL&#39;, out, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_nodes"><code class="name flex">
<span>def <span class="ident">test_smoke1_nodes</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;nodes</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_nodes():
&#34;&#34;&#34;Test --nodes&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --nodes&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^│ N │ User&#39;, out, re.MULTILINE)
assert re.search(r&#39;^│ 1 │&#39;, out, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_port"><code class="name flex">
<span>def <span class="ident">test_smoke1_port</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;port</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_port():
&#34;&#34;&#34;Test --port&#34;&#34;&#34;
# first, get the ports
ports = meshtastic.util.findPorts()
# hopefully there is just one
assert len(ports) == 1
port = ports[0]
return_value, out = subprocess.getstatusoutput(f&#39;meshtastic --port {port} --info&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Owner&#39;, out, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_qr"><code class="name flex">
<span>def <span class="ident">test_smoke1_qr</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;qr</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_qr():
&#34;&#34;&#34;Test --qr&#34;&#34;&#34;
filename = &#39;tmpqr&#39;
if os.path.exists(f&#34;{filename}&#34;):
os.remove(f&#34;{filename}&#34;)
return_value, _ = subprocess.getstatusoutput(f&#39;meshtastic --qr &gt; {filename}&#39;)
assert os.path.exists(f&#34;{filename}&#34;)
# 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&#34;{filename}&#34;).st_size &gt; 20000
assert return_value == 0
os.remove(f&#34;{filename}&#34;)</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_reboot"><code class="name flex">
<span>def <span class="ident">test_smoke1_reboot</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test reboot</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_reboot():
&#34;&#34;&#34;Test reboot&#34;&#34;&#34;
return_value, _ = subprocess.getstatusoutput(&#39;meshtastic --reboot&#39;)
assert return_value == 0
# pause for the radio to reset (10 seconds for the pause, and a few more seconds to be back up)
time.sleep(18)</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_send_hello"><code class="name flex">
<span>def <span class="ident">test_smoke1_send_hello</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;sendtext hello</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_send_hello():
&#34;&#34;&#34;Test --sendtext hello&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --sendtext hello&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Sending text message hello to \^all&#39;, out, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_seriallog_to_file"><code class="name flex">
<span>def <span class="ident">test_smoke1_seriallog_to_file</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;seriallog to a file creates a file</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_seriallog_to_file():
&#34;&#34;&#34;Test --seriallog to a file creates a file&#34;&#34;&#34;
filename = &#39;tmpoutput.txt&#39;
if os.path.exists(f&#34;{filename}&#34;):
os.remove(f&#34;{filename}&#34;)
return_value, _ = subprocess.getstatusoutput(f&#39;meshtastic --info --seriallog {filename}&#39;)
assert os.path.exists(f&#34;{filename}&#34;)
assert return_value == 0
os.remove(f&#34;{filename}&#34;)</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_set_is_router_false"><code class="name flex">
<span>def <span class="ident">test_smoke1_set_is_router_false</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;set is_router false</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_set_is_router_false():
&#34;&#34;&#34;Test --set is_router false&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --set is_router false&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Set is_router to false&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --get is_router&#39;)
assert re.search(r&#39;^is_router: False&#39;, out, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_set_is_router_true"><code class="name flex">
<span>def <span class="ident">test_smoke1_set_is_router_true</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;set is_router true</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_set_is_router_true():
&#34;&#34;&#34;Test --set is_router true&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --set is_router true&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Set is_router to true&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --get is_router&#39;)
assert re.search(r&#39;^is_router: True&#39;, out, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_set_location_info"><code class="name flex">
<span>def <span class="ident">test_smoke1_set_location_info</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;setlat, &ndash;setlon and &ndash;setalt</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_set_location_info():
&#34;&#34;&#34;Test --setlat, --setlon and --setalt &#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --setlat 32.7767 --setlon -96.7970 --setalt 1337&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Fixing altitude&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Fixing latitude&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Fixing longitude&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out2 = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.search(r&#39;1337&#39;, out2, re.MULTILINE)
assert re.search(r&#39;32.7767&#39;, out2, re.MULTILINE)
assert re.search(r&#39;-96.797&#39;, out2, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_set_owner"><code class="name flex">
<span>def <span class="ident">test_smoke1_set_owner</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;set-owner name</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_set_owner():
&#34;&#34;&#34;Test --set-owner name&#34;&#34;&#34;
# make sure the owner is not Joe
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --set-owner Bob&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Setting device owner to Bob&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert not re.search(r&#39;Owner: Joe&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --set-owner Joe&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Setting device owner to Joe&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.search(r&#39;Owner: Joe&#39;, out, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_set_team"><code class="name flex">
<span>def <span class="ident">test_smoke1_set_team</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;set-team</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_set_team():
&#34;&#34;&#34;Test --set-team &#34;&#34;&#34;
# unset the team
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --set-team CLEAR&#39;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(r&#39;^Setting team to CLEAR&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --set-team CYAN&#39;)
assert re.search(r&#39;Setting team to CYAN&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.search(r&#39;CYAN&#39;, out, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_seturl_default"><code class="name flex">
<span>def <span class="ident">test_smoke1_seturl_default</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;seturl with default value</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_seturl_default():
&#34;&#34;&#34;Test --seturl with default value&#34;&#34;&#34;
# set some channel value so we no longer have a default channel
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --ch-set name foo&#39;)
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(&#39;meshtastic --info&#39;)
assert not re.search(&#39;CgUYAyIBAQ&#39;, out, re.MULTILINE)
assert return_value == 0
url = &#34;https://www.meshtastic.org/d/#CgUYAyIBAQ&#34;
return_value, out = subprocess.getstatusoutput(f&#34;meshtastic --seturl {url}&#34;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.search(&#39;CgUYAyIBAQ&#39;, out, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_smoke1.test_smoke1_seturl_invalid_url"><code class="name flex">
<span>def <span class="ident">test_smoke1_seturl_invalid_url</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;seturl with invalid url</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke1
def test_smoke1_seturl_invalid_url():
&#34;&#34;&#34;Test --seturl with invalid url&#34;&#34;&#34;
# Note: This url is no longer a valid url.
url = &#34;https://www.meshtastic.org/c/#GAMiENTxuzogKQdZ8Lz_q89Oab8qB0RlZmF1bHQ=&#34;
return_value, out = subprocess.getstatusoutput(f&#34;meshtastic --seturl {url}&#34;)
assert re.match(r&#39;Connected to radio&#39;, out)
assert re.search(&#39;Warning: There were no settings&#39;, out, re.MULTILINE)
assert return_value == 1</code></pre>
</details>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic.test" href="index.html">meshtastic.test</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_ch_add_and_ch_del" href="#meshtastic.test.test_smoke1.test_smoke1_ch_add_and_ch_del">test_smoke1_ch_add_and_ch_del</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_ch_set_modem_config" href="#meshtastic.test.test_smoke1.test_smoke1_ch_set_modem_config">test_smoke1_ch_set_modem_config</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_ch_set_name" href="#meshtastic.test.test_smoke1.test_smoke1_ch_set_name">test_smoke1_ch_set_name</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_ch_values" href="#meshtastic.test.test_smoke1.test_smoke1_ch_values">test_smoke1_ch_values</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_configure" href="#meshtastic.test.test_smoke1.test_smoke1_configure">test_smoke1_configure</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_debug" href="#meshtastic.test.test_smoke1.test_smoke1_debug">test_smoke1_debug</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_factory_reset" href="#meshtastic.test.test_smoke1.test_smoke1_factory_reset">test_smoke1_factory_reset</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_info" href="#meshtastic.test.test_smoke1.test_smoke1_info">test_smoke1_info</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_nodes" href="#meshtastic.test.test_smoke1.test_smoke1_nodes">test_smoke1_nodes</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_port" href="#meshtastic.test.test_smoke1.test_smoke1_port">test_smoke1_port</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_qr" href="#meshtastic.test.test_smoke1.test_smoke1_qr">test_smoke1_qr</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_reboot" href="#meshtastic.test.test_smoke1.test_smoke1_reboot">test_smoke1_reboot</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_send_hello" href="#meshtastic.test.test_smoke1.test_smoke1_send_hello">test_smoke1_send_hello</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_seriallog_to_file" href="#meshtastic.test.test_smoke1.test_smoke1_seriallog_to_file">test_smoke1_seriallog_to_file</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_set_is_router_false" href="#meshtastic.test.test_smoke1.test_smoke1_set_is_router_false">test_smoke1_set_is_router_false</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_set_is_router_true" href="#meshtastic.test.test_smoke1.test_smoke1_set_is_router_true">test_smoke1_set_is_router_true</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_set_location_info" href="#meshtastic.test.test_smoke1.test_smoke1_set_location_info">test_smoke1_set_location_info</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_set_owner" href="#meshtastic.test.test_smoke1.test_smoke1_set_owner">test_smoke1_set_owner</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_set_team" href="#meshtastic.test.test_smoke1.test_smoke1_set_team">test_smoke1_set_team</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_seturl_default" href="#meshtastic.test.test_smoke1.test_smoke1_seturl_default">test_smoke1_seturl_default</a></code></li>
<li><code><a title="meshtastic.test.test_smoke1.test_smoke1_seturl_invalid_url" href="#meshtastic.test.test_smoke1.test_smoke1_seturl_invalid_url">test_smoke1_seturl_invalid_url</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,236 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.test.test_util API documentation</title>
<meta name="description" content="Meshtastic unit tests for node.py" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.test.test_util</code></h1>
</header>
<section id="section-intro">
<p>Meshtastic unit tests for node.py</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Meshtastic unit tests for node.py&#34;&#34;&#34;
import pytest
from meshtastic.util import pskToString, our_exit
@pytest.mark.unit
def test_pskToString_empty_string():
&#34;&#34;&#34;Test pskToString empty string&#34;&#34;&#34;
assert pskToString(&#39;&#39;) == &#39;unencrypted&#39;
@pytest.mark.unit
def test_pskToString_string():
&#34;&#34;&#34;Test pskToString string&#34;&#34;&#34;
assert pskToString(&#39;hunter123&#39;) == &#39;secret&#39;
@pytest.mark.unit
def test_pskToString_one_byte_zero_value():
&#34;&#34;&#34;Test pskToString one byte that is value of 0&#34;&#34;&#34;
assert pskToString(bytes([0x00])) == &#39;unencrypted&#39;
@pytest.mark.unit
def test_pskToString_one_byte_non_zero_value():
&#34;&#34;&#34;Test pskToString one byte that is non-zero&#34;&#34;&#34;
assert pskToString(bytes([0x01])) == &#39;default&#39;
@pytest.mark.unit
def test_pskToString_many_bytes():
&#34;&#34;&#34;Test pskToString many bytes&#34;&#34;&#34;
assert pskToString(bytes([0x02, 0x01])) == &#39;secret&#39;
@pytest.mark.unit
def test_our_exit_zero_return_value():
&#34;&#34;&#34;Test our_exit with a zero return value&#34;&#34;&#34;
with pytest.raises(SystemExit) as pytest_wrapped_e:
our_exit(&#34;Warning: Some message&#34;, 0)
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 0
@pytest.mark.unit
def test_our_exit_non_zero_return_value():
&#34;&#34;&#34;Test our_exit with a non-zero return value&#34;&#34;&#34;
with pytest.raises(SystemExit) as pytest_wrapped_e:
our_exit(&#34;Error: Some message&#34;, 1)
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.test.test_util.test_our_exit_non_zero_return_value"><code class="name flex">
<span>def <span class="ident">test_our_exit_non_zero_return_value</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test our_exit with a non-zero return value</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_our_exit_non_zero_return_value():
&#34;&#34;&#34;Test our_exit with a non-zero return value&#34;&#34;&#34;
with pytest.raises(SystemExit) as pytest_wrapped_e:
our_exit(&#34;Error: Some message&#34;, 1)
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_util.test_our_exit_zero_return_value"><code class="name flex">
<span>def <span class="ident">test_our_exit_zero_return_value</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test our_exit with a zero return value</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_our_exit_zero_return_value():
&#34;&#34;&#34;Test our_exit with a zero return value&#34;&#34;&#34;
with pytest.raises(SystemExit) as pytest_wrapped_e:
our_exit(&#34;Warning: Some message&#34;, 0)
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_util.test_pskToString_empty_string"><code class="name flex">
<span>def <span class="ident">test_pskToString_empty_string</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test pskToString empty string</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_pskToString_empty_string():
&#34;&#34;&#34;Test pskToString empty string&#34;&#34;&#34;
assert pskToString(&#39;&#39;) == &#39;unencrypted&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_util.test_pskToString_many_bytes"><code class="name flex">
<span>def <span class="ident">test_pskToString_many_bytes</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test pskToString many bytes</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_pskToString_many_bytes():
&#34;&#34;&#34;Test pskToString many bytes&#34;&#34;&#34;
assert pskToString(bytes([0x02, 0x01])) == &#39;secret&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_util.test_pskToString_one_byte_non_zero_value"><code class="name flex">
<span>def <span class="ident">test_pskToString_one_byte_non_zero_value</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test pskToString one byte that is non-zero</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_pskToString_one_byte_non_zero_value():
&#34;&#34;&#34;Test pskToString one byte that is non-zero&#34;&#34;&#34;
assert pskToString(bytes([0x01])) == &#39;default&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_util.test_pskToString_one_byte_zero_value"><code class="name flex">
<span>def <span class="ident">test_pskToString_one_byte_zero_value</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test pskToString one byte that is value of 0</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_pskToString_one_byte_zero_value():
&#34;&#34;&#34;Test pskToString one byte that is value of 0&#34;&#34;&#34;
assert pskToString(bytes([0x00])) == &#39;unencrypted&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.test.test_util.test_pskToString_string"><code class="name flex">
<span>def <span class="ident">test_pskToString_string</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test pskToString string</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_pskToString_string():
&#34;&#34;&#34;Test pskToString string&#34;&#34;&#34;
assert pskToString(&#39;hunter123&#39;) == &#39;secret&#39;</code></pre>
</details>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic.test" href="index.html">meshtastic.test</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.test.test_util.test_our_exit_non_zero_return_value" href="#meshtastic.test.test_util.test_our_exit_non_zero_return_value">test_our_exit_non_zero_return_value</a></code></li>
<li><code><a title="meshtastic.test.test_util.test_our_exit_zero_return_value" href="#meshtastic.test.test_util.test_our_exit_zero_return_value">test_our_exit_zero_return_value</a></code></li>
<li><code><a title="meshtastic.test.test_util.test_pskToString_empty_string" href="#meshtastic.test.test_util.test_pskToString_empty_string">test_pskToString_empty_string</a></code></li>
<li><code><a title="meshtastic.test.test_util.test_pskToString_many_bytes" href="#meshtastic.test.test_util.test_pskToString_many_bytes">test_pskToString_many_bytes</a></code></li>
<li><code><a title="meshtastic.test.test_util.test_pskToString_one_byte_non_zero_value" href="#meshtastic.test.test_util.test_pskToString_one_byte_non_zero_value">test_pskToString_one_byte_non_zero_value</a></code></li>
<li><code><a title="meshtastic.test.test_util.test_pskToString_one_byte_zero_value" href="#meshtastic.test.test_util.test_pskToString_one_byte_zero_value">test_pskToString_one_byte_zero_value</a></code></li>
<li><code><a title="meshtastic.test.test_util.test_pskToString_string" href="#meshtastic.test.test_util.test_pskToString_string">test_pskToString_string</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,100 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.tests.conftest API documentation</title>
<meta name="description" content="Common pytest code (place for fixtures)." />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.tests.conftest</code></h1>
</header>
<section id="section-intro">
<p>Common pytest code (place for fixtures).</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Common pytest code (place for fixtures).&#34;&#34;&#34;
import argparse
import pytest
from meshtastic.__main__ import Globals
@pytest.fixture
def reset_globals():
&#34;&#34;&#34;Fixture to reset globals.&#34;&#34;&#34;
parser = None
parser = argparse.ArgumentParser()
Globals.getInstance().reset()
Globals.getInstance().set_parser(parser)</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.tests.conftest.reset_globals"><code class="name flex">
<span>def <span class="ident">reset_globals</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Fixture to reset globals.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.fixture
def reset_globals():
&#34;&#34;&#34;Fixture to reset globals.&#34;&#34;&#34;
parser = None
parser = argparse.ArgumentParser()
Globals.getInstance().reset()
Globals.getInstance().set_parser(parser)</code></pre>
</details>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic.tests" href="index.html">meshtastic.tests</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.tests.conftest.reset_globals" href="#meshtastic.tests.conftest.reset_globals">reset_globals</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,130 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.tests API documentation</title>
<meta name="description" content="" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.tests</code></h1>
</header>
<section id="section-intro">
</section>
<section>
<h2 class="section-title" id="header-submodules">Sub-modules</h2>
<dl>
<dt><code class="name"><a title="meshtastic.tests.conftest" href="conftest.html">meshtastic.tests.conftest</a></code></dt>
<dd>
<div class="desc"><p>Common pytest code (place for fixtures).</p></div>
</dd>
<dt><code class="name"><a title="meshtastic.tests.test_ble_interface" href="test_ble_interface.html">meshtastic.tests.test_ble_interface</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic unit tests for ble_interface.py</p></div>
</dd>
<dt><code class="name"><a title="meshtastic.tests.test_globals" href="test_globals.html">meshtastic.tests.test_globals</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic unit tests for globals.py</p></div>
</dd>
<dt><code class="name"><a title="meshtastic.tests.test_int" href="test_int.html">meshtastic.tests.test_int</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic integration tests</p></div>
</dd>
<dt><code class="name"><a title="meshtastic.tests.test_main" href="test_main.html">meshtastic.tests.test_main</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic unit tests for <strong>main</strong>.py</p></div>
</dd>
<dt><code class="name"><a title="meshtastic.tests.test_mesh_interface" href="test_mesh_interface.html">meshtastic.tests.test_mesh_interface</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic unit tests for mesh_interface.py</p></div>
</dd>
<dt><code class="name"><a title="meshtastic.tests.test_node" href="test_node.html">meshtastic.tests.test_node</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic unit tests for node.py</p></div>
</dd>
<dt><code class="name"><a title="meshtastic.tests.test_serial_interface" href="test_serial_interface.html">meshtastic.tests.test_serial_interface</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic unit tests for serial_interface.py</p></div>
</dd>
<dt><code class="name"><a title="meshtastic.tests.test_smoke1" href="test_smoke1.html">meshtastic.tests.test_smoke1</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic smoke tests with a single device via USB</p></div>
</dd>
<dt><code class="name"><a title="meshtastic.tests.test_smoke2" href="test_smoke2.html">meshtastic.tests.test_smoke2</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic smoke tests with 2 devices connected via USB</p></div>
</dd>
<dt><code class="name"><a title="meshtastic.tests.test_smoke_wifi" href="test_smoke_wifi.html">meshtastic.tests.test_smoke_wifi</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic smoke tests a device setup with wifi …</p></div>
</dd>
<dt><code class="name"><a title="meshtastic.tests.test_stream_interface" href="test_stream_interface.html">meshtastic.tests.test_stream_interface</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic unit tests for stream_interface.py</p></div>
</dd>
<dt><code class="name"><a title="meshtastic.tests.test_tcp_interface" href="test_tcp_interface.html">meshtastic.tests.test_tcp_interface</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic unit tests for tcp_interface.py</p></div>
</dd>
<dt><code class="name"><a title="meshtastic.tests.test_util" href="test_util.html">meshtastic.tests.test_util</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic unit tests for util.py</p></div>
</dd>
</dl>
</section>
<section>
</section>
<section>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic" href="../index.html">meshtastic</a></code></li>
</ul>
</li>
<li><h3><a href="#header-submodules">Sub-modules</a></h3>
<ul>
<li><code><a title="meshtastic.tests.conftest" href="conftest.html">meshtastic.tests.conftest</a></code></li>
<li><code><a title="meshtastic.tests.test_ble_interface" href="test_ble_interface.html">meshtastic.tests.test_ble_interface</a></code></li>
<li><code><a title="meshtastic.tests.test_globals" href="test_globals.html">meshtastic.tests.test_globals</a></code></li>
<li><code><a title="meshtastic.tests.test_int" href="test_int.html">meshtastic.tests.test_int</a></code></li>
<li><code><a title="meshtastic.tests.test_main" href="test_main.html">meshtastic.tests.test_main</a></code></li>
<li><code><a title="meshtastic.tests.test_mesh_interface" href="test_mesh_interface.html">meshtastic.tests.test_mesh_interface</a></code></li>
<li><code><a title="meshtastic.tests.test_node" href="test_node.html">meshtastic.tests.test_node</a></code></li>
<li><code><a title="meshtastic.tests.test_serial_interface" href="test_serial_interface.html">meshtastic.tests.test_serial_interface</a></code></li>
<li><code><a title="meshtastic.tests.test_smoke1" href="test_smoke1.html">meshtastic.tests.test_smoke1</a></code></li>
<li><code><a title="meshtastic.tests.test_smoke2" href="test_smoke2.html">meshtastic.tests.test_smoke2</a></code></li>
<li><code><a title="meshtastic.tests.test_smoke_wifi" href="test_smoke_wifi.html">meshtastic.tests.test_smoke_wifi</a></code></li>
<li><code><a title="meshtastic.tests.test_stream_interface" href="test_stream_interface.html">meshtastic.tests.test_stream_interface</a></code></li>
<li><code><a title="meshtastic.tests.test_tcp_interface" href="test_tcp_interface.html">meshtastic.tests.test_tcp_interface</a></code></li>
<li><code><a title="meshtastic.tests.test_util" href="test_util.html">meshtastic.tests.test_util</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,95 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.tests.test_ble_interface API documentation</title>
<meta name="description" content="Meshtastic unit tests for ble_interface.py" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.tests.test_ble_interface</code></h1>
</header>
<section id="section-intro">
<p>Meshtastic unit tests for ble_interface.py</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Meshtastic unit tests for ble_interface.py&#34;&#34;&#34;
import pytest
from ..ble_interface import BLEInterface
@pytest.mark.unit
def test_BLEInterface():
&#34;&#34;&#34;Test that we can instantiate a BLEInterface&#34;&#34;&#34;
iface = BLEInterface(&#39;foo&#39;, debugOut=True, noProto=True)
iface.close()</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.tests.test_ble_interface.test_BLEInterface"><code class="name flex">
<span>def <span class="ident">test_BLEInterface</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test that we can instantiate a BLEInterface</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_BLEInterface():
&#34;&#34;&#34;Test that we can instantiate a BLEInterface&#34;&#34;&#34;
iface = BLEInterface(&#39;foo&#39;, debugOut=True, noProto=True)
iface.close()</code></pre>
</details>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic.tests" href="index.html">meshtastic.tests</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.tests.test_ble_interface.test_BLEInterface" href="#meshtastic.tests.test_ble_interface.test_BLEInterface">test_BLEInterface</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,130 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.tests.test_globals API documentation</title>
<meta name="description" content="Meshtastic unit tests for globals.py" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.tests.test_globals</code></h1>
</header>
<section id="section-intro">
<p>Meshtastic unit tests for globals.py</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Meshtastic unit tests for globals.py
&#34;&#34;&#34;
import pytest
from ..globals import Globals
@pytest.mark.unit
def test_globals_get_instaance():
&#34;&#34;&#34;Test that we can instantiate a Globals instance&#34;&#34;&#34;
ourglobals = Globals.getInstance()
ourglobals2 = Globals.getInstance()
assert ourglobals == ourglobals2
@pytest.mark.unit
def test_globals_there_can_be_only_one():
&#34;&#34;&#34;Test that we can cannot create two Globals instances&#34;&#34;&#34;
# if we have an instance, delete it
Globals.getInstance()
with pytest.raises(Exception) as pytest_wrapped_e:
# try to create another instance
Globals()
assert pytest_wrapped_e.type == Exception</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.tests.test_globals.test_globals_get_instaance"><code class="name flex">
<span>def <span class="ident">test_globals_get_instaance</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test that we can instantiate a Globals instance</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_globals_get_instaance():
&#34;&#34;&#34;Test that we can instantiate a Globals instance&#34;&#34;&#34;
ourglobals = Globals.getInstance()
ourglobals2 = Globals.getInstance()
assert ourglobals == ourglobals2</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_globals.test_globals_there_can_be_only_one"><code class="name flex">
<span>def <span class="ident">test_globals_there_can_be_only_one</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test that we can cannot create two Globals instances</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_globals_there_can_be_only_one():
&#34;&#34;&#34;Test that we can cannot create two Globals instances&#34;&#34;&#34;
# if we have an instance, delete it
Globals.getInstance()
with pytest.raises(Exception) as pytest_wrapped_e:
# try to create another instance
Globals()
assert pytest_wrapped_e.type == Exception</code></pre>
</details>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic.tests" href="index.html">meshtastic.tests</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.tests.test_globals.test_globals_get_instaance" href="#meshtastic.tests.test_globals.test_globals_get_instaance">test_globals_get_instaance</a></code></li>
<li><code><a title="meshtastic.tests.test_globals.test_globals_there_can_be_only_one" href="#meshtastic.tests.test_globals.test_globals_there_can_be_only_one">test_globals_there_can_be_only_one</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,177 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.tests.test_int API documentation</title>
<meta name="description" content="Meshtastic integration tests" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.tests.test_int</code></h1>
</header>
<section id="section-intro">
<p>Meshtastic integration tests</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Meshtastic integration tests&#34;&#34;&#34;
import re
import subprocess
import pytest
@pytest.mark.int
def test_int_no_args():
&#34;&#34;&#34;Test without any args&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic&#39;)
assert re.match(r&#39;usage: meshtastic&#39;, out)
assert return_value == 1
@pytest.mark.int
def test_int_version():
&#34;&#34;&#34;Test &#39;--version&#39;.&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --version&#39;)
assert re.match(r&#39;[0-9]+\.[0-9]+\.[0-9]&#39;, out)
assert return_value == 0
@pytest.mark.int
def test_int_help():
&#34;&#34;&#34;Test &#39;--help&#39;.&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --help&#39;)
assert re.match(r&#39;usage: meshtastic &#39;, out)
assert return_value == 0
@pytest.mark.int
def test_int_support():
&#34;&#34;&#34;Test &#39;--support&#39;.&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --support&#39;)
assert re.search(r&#39;System&#39;, out)
assert re.search(r&#39;Python&#39;, out)
assert return_value == 0</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.tests.test_int.test_int_help"><code class="name flex">
<span>def <span class="ident">test_int_help</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test '&ndash;help'.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.int
def test_int_help():
&#34;&#34;&#34;Test &#39;--help&#39;.&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --help&#39;)
assert re.match(r&#39;usage: meshtastic &#39;, out)
assert return_value == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_int.test_int_no_args"><code class="name flex">
<span>def <span class="ident">test_int_no_args</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test without any args</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.int
def test_int_no_args():
&#34;&#34;&#34;Test without any args&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic&#39;)
assert re.match(r&#39;usage: meshtastic&#39;, out)
assert return_value == 1</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_int.test_int_support"><code class="name flex">
<span>def <span class="ident">test_int_support</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test '&ndash;support'.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.int
def test_int_support():
&#34;&#34;&#34;Test &#39;--support&#39;.&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --support&#39;)
assert re.search(r&#39;System&#39;, out)
assert re.search(r&#39;Python&#39;, out)
assert return_value == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_int.test_int_version"><code class="name flex">
<span>def <span class="ident">test_int_version</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test '&ndash;version'.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.int
def test_int_version():
&#34;&#34;&#34;Test &#39;--version&#39;.&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --version&#39;)
assert re.match(r&#39;[0-9]+\.[0-9]+\.[0-9]&#39;, out)
assert return_value == 0</code></pre>
</details>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic.tests" href="index.html">meshtastic.tests</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.tests.test_int.test_int_help" href="#meshtastic.tests.test_int.test_int_help">test_int_help</a></code></li>
<li><code><a title="meshtastic.tests.test_int.test_int_no_args" href="#meshtastic.tests.test_int.test_int_no_args">test_int_no_args</a></code></li>
<li><code><a title="meshtastic.tests.test_int.test_int_support" href="#meshtastic.tests.test_int.test_int_support">test_int_support</a></code></li>
<li><code><a title="meshtastic.tests.test_int.test_int_version" href="#meshtastic.tests.test_int.test_int_version">test_int_version</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,117 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.tests.test_mesh_interface API documentation</title>
<meta name="description" content="Meshtastic unit tests for mesh_interface.py" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.tests.test_mesh_interface</code></h1>
</header>
<section id="section-intro">
<p>Meshtastic unit tests for mesh_interface.py</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Meshtastic unit tests for mesh_interface.py&#34;&#34;&#34;
import re
import pytest
from ..mesh_interface import MeshInterface
@pytest.mark.unit
def test_MeshInterface(capsys):
&#34;&#34;&#34;Test that we can instantiate a MeshInterface&#34;&#34;&#34;
iface = MeshInterface(noProto=True)
iface.showInfo()
iface.localNode.showInfo()
iface.showNodes()
iface.close()
out, err = capsys.readouterr()
assert re.search(r&#39;Owner: None \(None\)&#39;, out, re.MULTILINE)
assert re.search(r&#39;Nodes&#39;, out, re.MULTILINE)
assert re.search(r&#39;Preferences&#39;, out, re.MULTILINE)
assert re.search(r&#39;Channels&#39;, out, re.MULTILINE)
assert re.search(r&#39;Primary channel URL&#39;, out, re.MULTILINE)
assert err == &#39;&#39;</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.tests.test_mesh_interface.test_MeshInterface"><code class="name flex">
<span>def <span class="ident">test_MeshInterface</span></span>(<span>capsys)</span>
</code></dt>
<dd>
<div class="desc"><p>Test that we can instantiate a MeshInterface</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_MeshInterface(capsys):
&#34;&#34;&#34;Test that we can instantiate a MeshInterface&#34;&#34;&#34;
iface = MeshInterface(noProto=True)
iface.showInfo()
iface.localNode.showInfo()
iface.showNodes()
iface.close()
out, err = capsys.readouterr()
assert re.search(r&#39;Owner: None \(None\)&#39;, out, re.MULTILINE)
assert re.search(r&#39;Nodes&#39;, out, re.MULTILINE)
assert re.search(r&#39;Preferences&#39;, out, re.MULTILINE)
assert re.search(r&#39;Channels&#39;, out, re.MULTILINE)
assert re.search(r&#39;Primary channel URL&#39;, out, re.MULTILINE)
assert err == &#39;&#39;</code></pre>
</details>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic.tests" href="index.html">meshtastic.tests</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.tests.test_mesh_interface.test_MeshInterface" href="#meshtastic.tests.test_mesh_interface.test_MeshInterface">test_MeshInterface</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,951 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.tests.test_node API documentation</title>
<meta name="description" content="Meshtastic unit tests for node.py" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.tests.test_node</code></h1>
</header>
<section id="section-intro">
<p>Meshtastic unit tests for node.py</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Meshtastic unit tests for node.py&#34;&#34;&#34;
import re
from unittest.mock import patch, MagicMock
import pytest
from ..node import Node
from ..serial_interface import SerialInterface
from ..admin_pb2 import AdminMessage
@pytest.mark.unit
def test_node(capsys):
&#34;&#34;&#34;Test that we can instantiate a Node&#34;&#34;&#34;
anode = Node(&#39;foo&#39;, &#39;bar&#39;)
anode.showChannels()
anode.showInfo()
out, err = capsys.readouterr()
assert re.search(r&#39;Preferences&#39;, out)
assert re.search(r&#39;Channels&#39;, out)
assert re.search(r&#39;Primary channel URL&#39;, out)
assert err == &#39;&#39;
@pytest.mark.unit
def test_node_reqquestConfig():
&#34;&#34;&#34;Test run requestConfig&#34;&#34;&#34;
iface = MagicMock(autospec=SerialInterface)
amesg = MagicMock(autospec=AdminMessage)
with patch(&#39;meshtastic.serial_interface.SerialInterface&#39;, return_value=iface) as mo:
with patch(&#39;meshtastic.admin_pb2.AdminMessage&#39;, return_value=amesg):
anode = Node(mo, &#39;bar&#39;)
anode.requestConfig()</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.tests.test_node.test_node"><code class="name flex">
<span>def <span class="ident">test_node</span></span>(<span>capsys)</span>
</code></dt>
<dd>
<div class="desc"><p>Test that we can instantiate a Node</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_node(capsys):
&#34;&#34;&#34;Test that we can instantiate a Node&#34;&#34;&#34;
anode = Node(&#39;foo&#39;, &#39;bar&#39;)
anode.showChannels()
anode.showInfo()
out, err = capsys.readouterr()
assert re.search(r&#39;Preferences&#39;, out)
assert re.search(r&#39;Channels&#39;, out)
assert re.search(r&#39;Primary channel URL&#39;, out)
assert err == &#39;&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.test_node_reqquestConfig"><code class="name flex">
<span>def <span class="ident">test_node_reqquestConfig</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test run requestConfig</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_node_reqquestConfig():
&#34;&#34;&#34;Test run requestConfig&#34;&#34;&#34;
iface = MagicMock(autospec=SerialInterface)
amesg = MagicMock(autospec=AdminMessage)
with patch(&#39;meshtastic.serial_interface.SerialInterface&#39;, return_value=iface) as mo:
with patch(&#39;meshtastic.admin_pb2.AdminMessage&#39;, return_value=amesg):
anode = Node(mo, &#39;bar&#39;)
anode.requestConfig()</code></pre>
</details>
</dd>
</dl>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="meshtastic.tests.test_node.AdminMessage"><code class="flex name class">
<span>class <span class="ident">AdminMessage</span></span>
<span>(</span><span>**kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>Abstract base class for protocol messages.</p>
<p>Protocol message classes are almost always generated by the protocol
compiler.
These generated types subclass Message and implement the methods
shown below.</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="meshtastic.tests.test_node.AdminMessage.CONFIRM_SET_CHANNEL_FIELD_NUMBER"><code class="name">var <span class="ident">CONFIRM_SET_CHANNEL_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.CONFIRM_SET_RADIO_FIELD_NUMBER"><code class="name">var <span class="ident">CONFIRM_SET_RADIO_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.EXIT_SIMULATOR_FIELD_NUMBER"><code class="name">var <span class="ident">EXIT_SIMULATOR_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.GET_CHANNEL_REQUEST_FIELD_NUMBER"><code class="name">var <span class="ident">GET_CHANNEL_REQUEST_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.GET_CHANNEL_RESPONSE_FIELD_NUMBER"><code class="name">var <span class="ident">GET_CHANNEL_RESPONSE_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.GET_RADIO_REQUEST_FIELD_NUMBER"><code class="name">var <span class="ident">GET_RADIO_REQUEST_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.GET_RADIO_RESPONSE_FIELD_NUMBER"><code class="name">var <span class="ident">GET_RADIO_RESPONSE_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.REBOOT_SECONDS_FIELD_NUMBER"><code class="name">var <span class="ident">REBOOT_SECONDS_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.SET_CHANNEL_FIELD_NUMBER"><code class="name">var <span class="ident">SET_CHANNEL_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.SET_OWNER_FIELD_NUMBER"><code class="name">var <span class="ident">SET_OWNER_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.SET_RADIO_FIELD_NUMBER"><code class="name">var <span class="ident">SET_RADIO_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
<h3>Static methods</h3>
<dl>
<dt id="meshtastic.tests.test_node.AdminMessage.FromString"><code class="name flex">
<span>def <span class="ident">FromString</span></span>(<span>s)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def FromString(s):
message = cls()
message.MergeFromString(s)
return message</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.RegisterExtension"><code class="name flex">
<span>def <span class="ident">RegisterExtension</span></span>(<span>extension_handle)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def RegisterExtension(extension_handle):
extension_handle.containing_type = cls.DESCRIPTOR
# TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
# pylint: disable=protected-access
cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
_AttachFieldHelpers(cls, extension_handle)</code></pre>
</details>
</dd>
</dl>
<h3>Instance variables</h3>
<dl>
<dt id="meshtastic.tests.test_node.AdminMessage.confirm_set_channel"><code class="name">var <span class="ident">confirm_set_channel</span></code></dt>
<dd>
<div class="desc"><p>Getter for confirm_set_channel.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.confirm_set_radio"><code class="name">var <span class="ident">confirm_set_radio</span></code></dt>
<dd>
<div class="desc"><p>Getter for confirm_set_radio.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.exit_simulator"><code class="name">var <span class="ident">exit_simulator</span></code></dt>
<dd>
<div class="desc"><p>Getter for exit_simulator.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.get_channel_request"><code class="name">var <span class="ident">get_channel_request</span></code></dt>
<dd>
<div class="desc"><p>Getter for get_channel_request.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.get_channel_response"><code class="name">var <span class="ident">get_channel_response</span></code></dt>
<dd>
<div class="desc"><p>Getter for get_channel_response.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
field_value = self._fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
# Atomically check if another thread has preempted us and, if not, swap
# in the new object we just created. If someone has preempted us, we
# take that object and discard ours.
# WARNING: We are relying on setdefault() being atomic. This is true
# in CPython but we haven&#39;t investigated others. This warning appears
# in several other locations in this file.
field_value = self._fields.setdefault(field, field_value)
return field_value</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.get_radio_request"><code class="name">var <span class="ident">get_radio_request</span></code></dt>
<dd>
<div class="desc"><p>Getter for get_radio_request.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.get_radio_response"><code class="name">var <span class="ident">get_radio_response</span></code></dt>
<dd>
<div class="desc"><p>Getter for get_radio_response.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
field_value = self._fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
# Atomically check if another thread has preempted us and, if not, swap
# in the new object we just created. If someone has preempted us, we
# take that object and discard ours.
# WARNING: We are relying on setdefault() being atomic. This is true
# in CPython but we haven&#39;t investigated others. This warning appears
# in several other locations in this file.
field_value = self._fields.setdefault(field, field_value)
return field_value</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.reboot_seconds"><code class="name">var <span class="ident">reboot_seconds</span></code></dt>
<dd>
<div class="desc"><p>Getter for reboot_seconds.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
# TODO(protobuf-team): This may be broken since there may not be
# default_value. Combine with has_default_value somehow.
return self._fields.get(field, default_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.set_channel"><code class="name">var <span class="ident">set_channel</span></code></dt>
<dd>
<div class="desc"><p>Getter for set_channel.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
field_value = self._fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
# Atomically check if another thread has preempted us and, if not, swap
# in the new object we just created. If someone has preempted us, we
# take that object and discard ours.
# WARNING: We are relying on setdefault() being atomic. This is true
# in CPython but we haven&#39;t investigated others. This warning appears
# in several other locations in this file.
field_value = self._fields.setdefault(field, field_value)
return field_value</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.set_owner"><code class="name">var <span class="ident">set_owner</span></code></dt>
<dd>
<div class="desc"><p>Getter for set_owner.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
field_value = self._fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
# Atomically check if another thread has preempted us and, if not, swap
# in the new object we just created. If someone has preempted us, we
# take that object and discard ours.
# WARNING: We are relying on setdefault() being atomic. This is true
# in CPython but we haven&#39;t investigated others. This warning appears
# in several other locations in this file.
field_value = self._fields.setdefault(field, field_value)
return field_value</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.set_radio"><code class="name">var <span class="ident">set_radio</span></code></dt>
<dd>
<div class="desc"><p>Getter for set_radio.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getter(self):
field_value = self._fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
# Atomically check if another thread has preempted us and, if not, swap
# in the new object we just created. If someone has preempted us, we
# take that object and discard ours.
# WARNING: We are relying on setdefault() being atomic. This is true
# in CPython but we haven&#39;t investigated others. This warning appears
# in several other locations in this file.
field_value = self._fields.setdefault(field, field_value)
return field_value</code></pre>
</details>
</dd>
</dl>
<h3>Methods</h3>
<dl>
<dt id="meshtastic.tests.test_node.AdminMessage.ByteSize"><code class="name flex">
<span>def <span class="ident">ByteSize</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ByteSize(self):
if not self._cached_byte_size_dirty:
return self._cached_byte_size
size = 0
descriptor = self.DESCRIPTOR
if descriptor.GetOptions().map_entry:
# Fields of map entry should always be serialized.
size = descriptor.fields_by_name[&#39;key&#39;]._sizer(self.key)
size += descriptor.fields_by_name[&#39;value&#39;]._sizer(self.value)
else:
for field_descriptor, field_value in self.ListFields():
size += field_descriptor._sizer(field_value)
for tag_bytes, value_bytes in self._unknown_fields:
size += len(tag_bytes) + len(value_bytes)
self._cached_byte_size = size
self._cached_byte_size_dirty = False
self._listener_for_children.dirty = False
return size</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.Clear"><code class="name flex">
<span>def <span class="ident">Clear</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def _Clear(self):
# Clear fields.
self._fields = {}
self._unknown_fields = ()
# pylint: disable=protected-access
if self._unknown_field_set is not None:
self._unknown_field_set._clear()
self._unknown_field_set = None
self._oneofs = {}
self._Modified()</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.ClearField"><code class="name flex">
<span>def <span class="ident">ClearField</span></span>(<span>self, field_name)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ClearField(self, field_name):
try:
field = message_descriptor.fields_by_name[field_name]
except KeyError:
try:
field = message_descriptor.oneofs_by_name[field_name]
if field in self._oneofs:
field = self._oneofs[field]
else:
return
except KeyError:
raise ValueError(&#39;Protocol message %s has no &#34;%s&#34; field.&#39; %
(message_descriptor.name, field_name))
if field in self._fields:
# To match the C++ implementation, we need to invalidate iterators
# for map fields when ClearField() happens.
if hasattr(self._fields[field], &#39;InvalidateIterators&#39;):
self._fields[field].InvalidateIterators()
# Note: If the field is a sub-message, its listener will still point
# at us. That&#39;s fine, because the worst than can happen is that it
# will call _Modified() and invalidate our byte size. Big deal.
del self._fields[field]
if self._oneofs.get(field.containing_oneof, None) is field:
del self._oneofs[field.containing_oneof]
# Always call _Modified() -- even if nothing was changed, this is
# a mutating method, and thus calling it should cause the field to become
# present in the parent message.
self._Modified()</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.DiscardUnknownFields"><code class="name flex">
<span>def <span class="ident">DiscardUnknownFields</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def _DiscardUnknownFields(self):
self._unknown_fields = []
self._unknown_field_set = None # pylint: disable=protected-access
for field, value in self.ListFields():
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
if _IsMapField(field):
if _IsMessageMapField(field):
for key in value:
value[key].DiscardUnknownFields()
elif field.label == _FieldDescriptor.LABEL_REPEATED:
for sub_message in value:
sub_message.DiscardUnknownFields()
else:
value.DiscardUnknownFields()</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.FindInitializationErrors"><code class="name flex">
<span>def <span class="ident">FindInitializationErrors</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Finds required fields which are not initialized.</p>
<h2 id="returns">Returns</h2>
<p>A list of strings.
Each string is a path to an uninitialized field from
the top-level message, e.g. "foo.bar[5].baz".</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def FindInitializationErrors(self):
&#34;&#34;&#34;Finds required fields which are not initialized.
Returns:
A list of strings. Each string is a path to an uninitialized field from
the top-level message, e.g. &#34;foo.bar[5].baz&#34;.
&#34;&#34;&#34;
errors = [] # simplify things
for field in required_fields:
if not self.HasField(field.name):
errors.append(field.name)
for field, value in self.ListFields():
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
if field.is_extension:
name = &#39;(%s)&#39; % field.full_name
else:
name = field.name
if _IsMapField(field):
if _IsMessageMapField(field):
for key in value:
element = value[key]
prefix = &#39;%s[%s].&#39; % (name, key)
sub_errors = element.FindInitializationErrors()
errors += [prefix + error for error in sub_errors]
else:
# ScalarMaps can&#39;t have any initialization errors.
pass
elif field.label == _FieldDescriptor.LABEL_REPEATED:
for i in range(len(value)):
element = value[i]
prefix = &#39;%s[%d].&#39; % (name, i)
sub_errors = element.FindInitializationErrors()
errors += [prefix + error for error in sub_errors]
else:
prefix = name + &#39;.&#39;
sub_errors = value.FindInitializationErrors()
errors += [prefix + error for error in sub_errors]
return errors</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.HasField"><code class="name flex">
<span>def <span class="ident">HasField</span></span>(<span>self, field_name)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def HasField(self, field_name):
try:
field = hassable_fields[field_name]
except KeyError:
raise ValueError(error_msg % (message_descriptor.full_name, field_name))
if isinstance(field, descriptor_mod.OneofDescriptor):
try:
return HasField(self, self._oneofs[field].name)
except KeyError:
return False
else:
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
value = self._fields.get(field)
return value is not None and value._is_present_in_parent
else:
return field in self._fields</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.IsInitialized"><code class="name flex">
<span>def <span class="ident">IsInitialized</span></span>(<span>self, errors=None)</span>
</code></dt>
<dd>
<div class="desc"><p>Checks if all required fields of a message are set.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>errors</code></strong></dt>
<dd>A list which, if provided, will be populated with the field
paths of all missing required fields.</dd>
</dl>
<h2 id="returns">Returns</h2>
<p>True iff the specified message has all required fields set.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def IsInitialized(self, errors=None):
&#34;&#34;&#34;Checks if all required fields of a message are set.
Args:
errors: A list which, if provided, will be populated with the field
paths of all missing required fields.
Returns:
True iff the specified message has all required fields set.
&#34;&#34;&#34;
# Performance is critical so we avoid HasField() and ListFields().
for field in required_fields:
if (field not in self._fields or
(field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
not self._fields[field]._is_present_in_parent)):
if errors is not None:
errors.extend(self.FindInitializationErrors())
return False
for field, value in list(self._fields.items()): # dict can change size!
if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
if field.label == _FieldDescriptor.LABEL_REPEATED:
if (field.message_type.has_options and
field.message_type.GetOptions().map_entry):
continue
for element in value:
if not element.IsInitialized():
if errors is not None:
errors.extend(self.FindInitializationErrors())
return False
elif value._is_present_in_parent and not value.IsInitialized():
if errors is not None:
errors.extend(self.FindInitializationErrors())
return False
return True</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.ListFields"><code class="name flex">
<span>def <span class="ident">ListFields</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ListFields(self):
all_fields = [item for item in self._fields.items() if _IsPresent(item)]
all_fields.sort(key = lambda item: item[0].number)
return all_fields</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.MergeFrom"><code class="name flex">
<span>def <span class="ident">MergeFrom</span></span>(<span>self, msg)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def MergeFrom(self, msg):
if not isinstance(msg, cls):
raise TypeError(
&#39;Parameter to MergeFrom() must be instance of same class: &#39;
&#39;expected %s got %s.&#39; % (_FullyQualifiedClassName(cls),
_FullyQualifiedClassName(msg.__class__)))
assert msg is not self
self._Modified()
fields = self._fields
for field, value in msg._fields.items():
if field.label == LABEL_REPEATED:
field_value = fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
fields[field] = field_value
field_value.MergeFrom(value)
elif field.cpp_type == CPPTYPE_MESSAGE:
if value._is_present_in_parent:
field_value = fields.get(field)
if field_value is None:
# Construct a new object to represent this field.
field_value = field._default_constructor(self)
fields[field] = field_value
field_value.MergeFrom(value)
else:
self._fields[field] = value
if field.containing_oneof:
self._UpdateOneofState(field)
if msg._unknown_fields:
if not self._unknown_fields:
self._unknown_fields = []
self._unknown_fields.extend(msg._unknown_fields)
# pylint: disable=protected-access
if self._unknown_field_set is None:
self._unknown_field_set = containers.UnknownFieldSet()
self._unknown_field_set._extend(msg._unknown_field_set)</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.MergeFromString"><code class="name flex">
<span>def <span class="ident">MergeFromString</span></span>(<span>self, serialized)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def MergeFromString(self, serialized):
serialized = memoryview(serialized)
length = len(serialized)
try:
if self._InternalParse(serialized, 0, length) != length:
# The only reason _InternalParse would return early is if it
# encountered an end-group tag.
raise message_mod.DecodeError(&#39;Unexpected end-group tag.&#39;)
except (IndexError, TypeError):
# Now ord(buf[p:p+1]) == ord(&#39;&#39;) gets TypeError.
raise message_mod.DecodeError(&#39;Truncated message.&#39;)
except struct.error as e:
raise message_mod.DecodeError(e)
return length # Return this for legacy reasons.</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.SerializePartialToString"><code class="name flex">
<span>def <span class="ident">SerializePartialToString</span></span>(<span>self, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def SerializePartialToString(self, **kwargs):
out = BytesIO()
self._InternalSerialize(out.write, **kwargs)
return out.getvalue()</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.SerializeToString"><code class="name flex">
<span>def <span class="ident">SerializeToString</span></span>(<span>self, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def SerializeToString(self, **kwargs):
# Check if the message has all of its required fields set.
if not self.IsInitialized():
raise message_mod.EncodeError(
&#39;Message %s is missing required fields: %s&#39; % (
self.DESCRIPTOR.full_name, &#39;,&#39;.join(self.FindInitializationErrors())))
return self.SerializePartialToString(**kwargs)</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.SetInParent"><code class="name flex">
<span>def <span class="ident">SetInParent</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Sets the _cached_byte_size_dirty bit to true,
and propagates this to our listener iff this was a state change.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def Modified(self):
&#34;&#34;&#34;Sets the _cached_byte_size_dirty bit to true,
and propagates this to our listener iff this was a state change.
&#34;&#34;&#34;
# Note: Some callers check _cached_byte_size_dirty before calling
# _Modified() as an extra optimization. So, if this method is ever
# changed such that it does stuff even when _cached_byte_size_dirty is
# already true, the callers need to be updated.
if not self._cached_byte_size_dirty:
self._cached_byte_size_dirty = True
self._listener_for_children.dirty = True
self._is_present_in_parent = True
self._listener.Modified()</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.UnknownFields"><code class="name flex">
<span>def <span class="ident">UnknownFields</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def _UnknownFields(self):
if self._unknown_field_set is None: # pylint: disable=protected-access
# pylint: disable=protected-access
self._unknown_field_set = containers.UnknownFieldSet()
return self._unknown_field_set # pylint: disable=protected-access</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_node.AdminMessage.WhichOneof"><code class="name flex">
<span>def <span class="ident">WhichOneof</span></span>(<span>self, oneof_name)</span>
</code></dt>
<dd>
<div class="desc"><p>Returns the name of the currently set field inside a oneof, or None.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def WhichOneof(self, oneof_name):
&#34;&#34;&#34;Returns the name of the currently set field inside a oneof, or None.&#34;&#34;&#34;
try:
field = message_descriptor.oneofs_by_name[oneof_name]
except KeyError:
raise ValueError(
&#39;Protocol message has no oneof &#34;%s&#34; field.&#39; % oneof_name)
nested_field = self._oneofs.get(field, None)
if nested_field is not None and self.HasField(nested_field.name):
return nested_field.name
else:
return None</code></pre>
</details>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic.tests" href="index.html">meshtastic.tests</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.tests.test_node.test_node" href="#meshtastic.tests.test_node.test_node">test_node</a></code></li>
<li><code><a title="meshtastic.tests.test_node.test_node_reqquestConfig" href="#meshtastic.tests.test_node.test_node_reqquestConfig">test_node_reqquestConfig</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="meshtastic.tests.test_node.AdminMessage" href="#meshtastic.tests.test_node.AdminMessage">AdminMessage</a></code></h4>
<ul class="">
<li><code><a title="meshtastic.tests.test_node.AdminMessage.ByteSize" href="#meshtastic.tests.test_node.AdminMessage.ByteSize">ByteSize</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.CONFIRM_SET_CHANNEL_FIELD_NUMBER" href="#meshtastic.tests.test_node.AdminMessage.CONFIRM_SET_CHANNEL_FIELD_NUMBER">CONFIRM_SET_CHANNEL_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.CONFIRM_SET_RADIO_FIELD_NUMBER" href="#meshtastic.tests.test_node.AdminMessage.CONFIRM_SET_RADIO_FIELD_NUMBER">CONFIRM_SET_RADIO_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.Clear" href="#meshtastic.tests.test_node.AdminMessage.Clear">Clear</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.ClearField" href="#meshtastic.tests.test_node.AdminMessage.ClearField">ClearField</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.DESCRIPTOR" href="#meshtastic.tests.test_node.AdminMessage.DESCRIPTOR">DESCRIPTOR</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.DiscardUnknownFields" href="#meshtastic.tests.test_node.AdminMessage.DiscardUnknownFields">DiscardUnknownFields</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.EXIT_SIMULATOR_FIELD_NUMBER" href="#meshtastic.tests.test_node.AdminMessage.EXIT_SIMULATOR_FIELD_NUMBER">EXIT_SIMULATOR_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.FindInitializationErrors" href="#meshtastic.tests.test_node.AdminMessage.FindInitializationErrors">FindInitializationErrors</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.FromString" href="#meshtastic.tests.test_node.AdminMessage.FromString">FromString</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.GET_CHANNEL_REQUEST_FIELD_NUMBER" href="#meshtastic.tests.test_node.AdminMessage.GET_CHANNEL_REQUEST_FIELD_NUMBER">GET_CHANNEL_REQUEST_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.GET_CHANNEL_RESPONSE_FIELD_NUMBER" href="#meshtastic.tests.test_node.AdminMessage.GET_CHANNEL_RESPONSE_FIELD_NUMBER">GET_CHANNEL_RESPONSE_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.GET_RADIO_REQUEST_FIELD_NUMBER" href="#meshtastic.tests.test_node.AdminMessage.GET_RADIO_REQUEST_FIELD_NUMBER">GET_RADIO_REQUEST_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.GET_RADIO_RESPONSE_FIELD_NUMBER" href="#meshtastic.tests.test_node.AdminMessage.GET_RADIO_RESPONSE_FIELD_NUMBER">GET_RADIO_RESPONSE_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.HasField" href="#meshtastic.tests.test_node.AdminMessage.HasField">HasField</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.IsInitialized" href="#meshtastic.tests.test_node.AdminMessage.IsInitialized">IsInitialized</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.ListFields" href="#meshtastic.tests.test_node.AdminMessage.ListFields">ListFields</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.MergeFrom" href="#meshtastic.tests.test_node.AdminMessage.MergeFrom">MergeFrom</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.MergeFromString" href="#meshtastic.tests.test_node.AdminMessage.MergeFromString">MergeFromString</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.REBOOT_SECONDS_FIELD_NUMBER" href="#meshtastic.tests.test_node.AdminMessage.REBOOT_SECONDS_FIELD_NUMBER">REBOOT_SECONDS_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.RegisterExtension" href="#meshtastic.tests.test_node.AdminMessage.RegisterExtension">RegisterExtension</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.SET_CHANNEL_FIELD_NUMBER" href="#meshtastic.tests.test_node.AdminMessage.SET_CHANNEL_FIELD_NUMBER">SET_CHANNEL_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.SET_OWNER_FIELD_NUMBER" href="#meshtastic.tests.test_node.AdminMessage.SET_OWNER_FIELD_NUMBER">SET_OWNER_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.SET_RADIO_FIELD_NUMBER" href="#meshtastic.tests.test_node.AdminMessage.SET_RADIO_FIELD_NUMBER">SET_RADIO_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.SerializePartialToString" href="#meshtastic.tests.test_node.AdminMessage.SerializePartialToString">SerializePartialToString</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.SerializeToString" href="#meshtastic.tests.test_node.AdminMessage.SerializeToString">SerializeToString</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.SetInParent" href="#meshtastic.tests.test_node.AdminMessage.SetInParent">SetInParent</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.UnknownFields" href="#meshtastic.tests.test_node.AdminMessage.UnknownFields">UnknownFields</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.WhichOneof" href="#meshtastic.tests.test_node.AdminMessage.WhichOneof">WhichOneof</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.confirm_set_channel" href="#meshtastic.tests.test_node.AdminMessage.confirm_set_channel">confirm_set_channel</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.confirm_set_radio" href="#meshtastic.tests.test_node.AdminMessage.confirm_set_radio">confirm_set_radio</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.exit_simulator" href="#meshtastic.tests.test_node.AdminMessage.exit_simulator">exit_simulator</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.get_channel_request" href="#meshtastic.tests.test_node.AdminMessage.get_channel_request">get_channel_request</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.get_channel_response" href="#meshtastic.tests.test_node.AdminMessage.get_channel_response">get_channel_response</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.get_radio_request" href="#meshtastic.tests.test_node.AdminMessage.get_radio_request">get_radio_request</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.get_radio_response" href="#meshtastic.tests.test_node.AdminMessage.get_radio_response">get_radio_response</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.reboot_seconds" href="#meshtastic.tests.test_node.AdminMessage.reboot_seconds">reboot_seconds</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.set_channel" href="#meshtastic.tests.test_node.AdminMessage.set_channel">set_channel</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.set_owner" href="#meshtastic.tests.test_node.AdminMessage.set_owner">set_owner</a></code></li>
<li><code><a title="meshtastic.tests.test_node.AdminMessage.set_radio" href="#meshtastic.tests.test_node.AdminMessage.set_radio">set_radio</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,186 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.tests.test_serial_interface API documentation</title>
<meta name="description" content="Meshtastic unit tests for serial_interface.py" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.tests.test_serial_interface</code></h1>
</header>
<section id="section-intro">
<p>Meshtastic unit tests for serial_interface.py</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Meshtastic unit tests for serial_interface.py&#34;&#34;&#34;
import re
from unittest.mock import patch
import pytest
from ..serial_interface import SerialInterface
@pytest.mark.unit
@patch(&#39;serial.Serial&#39;)
@patch(&#39;meshtastic.util.findPorts&#39;, return_value=[&#39;/dev/ttyUSBfake&#39;])
def test_SerialInterface_single_port(mocked_findPorts, mocked_serial):
&#34;&#34;&#34;Test that we can instantiate a SerialInterface with a single port&#34;&#34;&#34;
iface = SerialInterface(noProto=True)
iface.showInfo()
iface.localNode.showInfo()
iface.close()
mocked_findPorts.assert_called()
mocked_serial.assert_called()
@pytest.mark.unit
@patch(&#39;meshtastic.util.findPorts&#39;, return_value=[])
def test_SerialInterface_no_ports(mocked_findPorts, capsys):
&#34;&#34;&#34;Test that we can instantiate a SerialInterface with no ports&#34;&#34;&#34;
with pytest.raises(SystemExit) as pytest_wrapped_e:
SerialInterface(noProto=True)
mocked_findPorts.assert_called()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r&#39;Warning: No Meshtastic devices detected&#39;, out, re.MULTILINE)
assert err == &#39;&#39;
@pytest.mark.unit
@patch(&#39;meshtastic.util.findPorts&#39;, return_value=[&#39;/dev/ttyUSBfake1&#39;, &#39;/dev/ttyUSBfake2&#39;])
def test_SerialInterface_multiple_ports(mocked_findPorts, capsys):
&#34;&#34;&#34;Test that we can instantiate a SerialInterface with two ports&#34;&#34;&#34;
with pytest.raises(SystemExit) as pytest_wrapped_e:
SerialInterface(noProto=True)
mocked_findPorts.assert_called()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r&#39;Warning: Multiple serial ports were detected&#39;, out, re.MULTILINE)
assert err == &#39;&#39;</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.tests.test_serial_interface.test_SerialInterface_multiple_ports"><code class="name flex">
<span>def <span class="ident">test_SerialInterface_multiple_ports</span></span>(<span>mocked_findPorts, capsys)</span>
</code></dt>
<dd>
<div class="desc"><p>Test that we can instantiate a SerialInterface with two ports</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
@patch(&#39;meshtastic.util.findPorts&#39;, return_value=[&#39;/dev/ttyUSBfake1&#39;, &#39;/dev/ttyUSBfake2&#39;])
def test_SerialInterface_multiple_ports(mocked_findPorts, capsys):
&#34;&#34;&#34;Test that we can instantiate a SerialInterface with two ports&#34;&#34;&#34;
with pytest.raises(SystemExit) as pytest_wrapped_e:
SerialInterface(noProto=True)
mocked_findPorts.assert_called()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r&#39;Warning: Multiple serial ports were detected&#39;, out, re.MULTILINE)
assert err == &#39;&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_serial_interface.test_SerialInterface_no_ports"><code class="name flex">
<span>def <span class="ident">test_SerialInterface_no_ports</span></span>(<span>mocked_findPorts, capsys)</span>
</code></dt>
<dd>
<div class="desc"><p>Test that we can instantiate a SerialInterface with no ports</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
@patch(&#39;meshtastic.util.findPorts&#39;, return_value=[])
def test_SerialInterface_no_ports(mocked_findPorts, capsys):
&#34;&#34;&#34;Test that we can instantiate a SerialInterface with no ports&#34;&#34;&#34;
with pytest.raises(SystemExit) as pytest_wrapped_e:
SerialInterface(noProto=True)
mocked_findPorts.assert_called()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r&#39;Warning: No Meshtastic devices detected&#39;, out, re.MULTILINE)
assert err == &#39;&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_serial_interface.test_SerialInterface_single_port"><code class="name flex">
<span>def <span class="ident">test_SerialInterface_single_port</span></span>(<span>mocked_findPorts, mocked_serial)</span>
</code></dt>
<dd>
<div class="desc"><p>Test that we can instantiate a SerialInterface with a single port</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
@patch(&#39;serial.Serial&#39;)
@patch(&#39;meshtastic.util.findPorts&#39;, return_value=[&#39;/dev/ttyUSBfake&#39;])
def test_SerialInterface_single_port(mocked_findPorts, mocked_serial):
&#34;&#34;&#34;Test that we can instantiate a SerialInterface with a single port&#34;&#34;&#34;
iface = SerialInterface(noProto=True)
iface.showInfo()
iface.localNode.showInfo()
iface.close()
mocked_findPorts.assert_called()
mocked_serial.assert_called()</code></pre>
</details>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic.tests" href="index.html">meshtastic.tests</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.tests.test_serial_interface.test_SerialInterface_multiple_ports" href="#meshtastic.tests.test_serial_interface.test_SerialInterface_multiple_ports">test_SerialInterface_multiple_ports</a></code></li>
<li><code><a title="meshtastic.tests.test_serial_interface.test_SerialInterface_no_ports" href="#meshtastic.tests.test_serial_interface.test_SerialInterface_no_ports">test_SerialInterface_no_ports</a></code></li>
<li><code><a title="meshtastic.tests.test_serial_interface.test_SerialInterface_single_port" href="#meshtastic.tests.test_serial_interface.test_SerialInterface_single_port">test_SerialInterface_single_port</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,127 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.tests.test_smoke2 API documentation</title>
<meta name="description" content="Meshtastic smoke tests with 2 devices connected via USB" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.tests.test_smoke2</code></h1>
</header>
<section id="section-intro">
<p>Meshtastic smoke tests with 2 devices connected via USB</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Meshtastic smoke tests with 2 devices connected via USB&#34;&#34;&#34;
import re
import subprocess
import pytest
@pytest.mark.smoke2
def test_smoke2_info():
&#34;&#34;&#34;Test --info with 2 devices connected serially&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.search(r&#39;Warning: Multiple&#39;, out, re.MULTILINE)
assert return_value == 1
@pytest.mark.smoke2
def test_smoke2_test():
&#34;&#34;&#34;Test --test&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --test&#39;)
assert re.search(r&#39;Writing serial debugging&#39;, out, re.MULTILINE)
assert re.search(r&#39;Ports opened&#39;, out, re.MULTILINE)
assert re.search(r&#39;Running 5 tests&#39;, out, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.tests.test_smoke2.test_smoke2_info"><code class="name flex">
<span>def <span class="ident">test_smoke2_info</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;info with 2 devices connected serially</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke2
def test_smoke2_info():
&#34;&#34;&#34;Test --info with 2 devices connected serially&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info&#39;)
assert re.search(r&#39;Warning: Multiple&#39;, out, re.MULTILINE)
assert return_value == 1</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_smoke2.test_smoke2_test"><code class="name flex">
<span>def <span class="ident">test_smoke2_test</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;test</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smoke2
def test_smoke2_test():
&#34;&#34;&#34;Test --test&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --test&#39;)
assert re.search(r&#39;Writing serial debugging&#39;, out, re.MULTILINE)
assert re.search(r&#39;Ports opened&#39;, out, re.MULTILINE)
assert re.search(r&#39;Running 5 tests&#39;, out, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic.tests" href="index.html">meshtastic.tests</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.tests.test_smoke2.test_smoke2_info" href="#meshtastic.tests.test_smoke2.test_smoke2_info">test_smoke2_info</a></code></li>
<li><code><a title="meshtastic.tests.test_smoke2.test_smoke2_test" href="#meshtastic.tests.test_smoke2.test_smoke2_test">test_smoke2_test</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,115 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.tests.test_smoke_wifi API documentation</title>
<meta name="description" content="Meshtastic smoke tests a device setup with wifi …" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.tests.test_smoke_wifi</code></h1>
</header>
<section id="section-intro">
<p>Meshtastic smoke tests a device setup with wifi.</p>
<p>Need to have run the following on an esp32 device:
meshtastic &ndash;set wifi_ssid 'foo' &ndash;set wifi_password 'sekret'</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Meshtastic smoke tests a device setup with wifi.
Need to have run the following on an esp32 device:
meshtastic --set wifi_ssid &#39;foo&#39; --set wifi_password &#39;sekret&#39;
&#34;&#34;&#34;
import re
import subprocess
import pytest
@pytest.mark.smokewifi
def test_smokewifi_info():
&#34;&#34;&#34;Test --info&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info --host meshtastic.local&#39;)
assert re.search(r&#39;^Owner&#39;, out, re.MULTILINE)
assert re.search(r&#39;^My info&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Nodes in mesh&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Preferences&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Channels&#39;, out, re.MULTILINE)
assert re.search(r&#39;^ PRIMARY&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Primary channel URL&#39;, out, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.tests.test_smoke_wifi.test_smokewifi_info"><code class="name flex">
<span>def <span class="ident">test_smokewifi_info</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test &ndash;info</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.smokewifi
def test_smokewifi_info():
&#34;&#34;&#34;Test --info&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --info --host meshtastic.local&#39;)
assert re.search(r&#39;^Owner&#39;, out, re.MULTILINE)
assert re.search(r&#39;^My info&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Nodes in mesh&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Preferences&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Channels&#39;, out, re.MULTILINE)
assert re.search(r&#39;^ PRIMARY&#39;, out, re.MULTILINE)
assert re.search(r&#39;^Primary channel URL&#39;, out, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic.tests" href="index.html">meshtastic.tests</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.tests.test_smoke_wifi.test_smokewifi_info" href="#meshtastic.tests.test_smoke_wifi.test_smokewifi_info">test_smokewifi_info</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,98 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.tests.test_stream_interface API documentation</title>
<meta name="description" content="Meshtastic unit tests for stream_interface.py" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.tests.test_stream_interface</code></h1>
</header>
<section id="section-intro">
<p>Meshtastic unit tests for stream_interface.py</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Meshtastic unit tests for stream_interface.py&#34;&#34;&#34;
import pytest
from ..stream_interface import StreamInterface
@pytest.mark.unit
def test_StreamInterface():
&#34;&#34;&#34;Test that we can instantiate a StreamInterface&#34;&#34;&#34;
with pytest.raises(Exception) as pytest_wrapped_e:
StreamInterface(noProto=True)
assert pytest_wrapped_e.type == Exception</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.tests.test_stream_interface.test_StreamInterface"><code class="name flex">
<span>def <span class="ident">test_StreamInterface</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test that we can instantiate a StreamInterface</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_StreamInterface():
&#34;&#34;&#34;Test that we can instantiate a StreamInterface&#34;&#34;&#34;
with pytest.raises(Exception) as pytest_wrapped_e:
StreamInterface(noProto=True)
assert pytest_wrapped_e.type == Exception</code></pre>
</details>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic.tests" href="index.html">meshtastic.tests</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.tests.test_stream_interface.test_StreamInterface" href="#meshtastic.tests.test_stream_interface.test_StreamInterface">test_StreamInterface</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,120 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.tests.test_tcp_interface API documentation</title>
<meta name="description" content="Meshtastic unit tests for tcp_interface.py" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.tests.test_tcp_interface</code></h1>
</header>
<section id="section-intro">
<p>Meshtastic unit tests for tcp_interface.py</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Meshtastic unit tests for tcp_interface.py&#34;&#34;&#34;
import re
from unittest.mock import patch
import pytest
from ..tcp_interface import TCPInterface
@pytest.mark.unit
def test_TCPInterface(capsys):
&#34;&#34;&#34;Test that we can instantiate a TCPInterface&#34;&#34;&#34;
with patch(&#39;socket.socket&#39;) as mock_socket:
iface = TCPInterface(hostname=&#39;localhost&#39;, noProto=True)
iface.showInfo()
iface.localNode.showInfo()
out, err = capsys.readouterr()
assert re.search(r&#39;Owner: None \(None\)&#39;, out, re.MULTILINE)
assert re.search(r&#39;Nodes&#39;, out, re.MULTILINE)
assert re.search(r&#39;Preferences&#39;, out, re.MULTILINE)
assert re.search(r&#39;Channels&#39;, out, re.MULTILINE)
assert re.search(r&#39;Primary channel URL&#39;, out, re.MULTILINE)
assert err == &#39;&#39;
assert mock_socket.called
iface.close()</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.tests.test_tcp_interface.test_TCPInterface"><code class="name flex">
<span>def <span class="ident">test_TCPInterface</span></span>(<span>capsys)</span>
</code></dt>
<dd>
<div class="desc"><p>Test that we can instantiate a TCPInterface</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_TCPInterface(capsys):
&#34;&#34;&#34;Test that we can instantiate a TCPInterface&#34;&#34;&#34;
with patch(&#39;socket.socket&#39;) as mock_socket:
iface = TCPInterface(hostname=&#39;localhost&#39;, noProto=True)
iface.showInfo()
iface.localNode.showInfo()
out, err = capsys.readouterr()
assert re.search(r&#39;Owner: None \(None\)&#39;, out, re.MULTILINE)
assert re.search(r&#39;Nodes&#39;, out, re.MULTILINE)
assert re.search(r&#39;Preferences&#39;, out, re.MULTILINE)
assert re.search(r&#39;Channels&#39;, out, re.MULTILINE)
assert re.search(r&#39;Primary channel URL&#39;, out, re.MULTILINE)
assert err == &#39;&#39;
assert mock_socket.called
iface.close()</code></pre>
</details>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic.tests" href="index.html">meshtastic.tests</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.tests.test_tcp_interface.test_TCPInterface" href="#meshtastic.tests.test_tcp_interface.test_TCPInterface">test_TCPInterface</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,455 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.tests.test_util API documentation</title>
<meta name="description" content="Meshtastic unit tests for util.py" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.tests.test_util</code></h1>
</header>
<section id="section-intro">
<p>Meshtastic unit tests for util.py</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Meshtastic unit tests for util.py&#34;&#34;&#34;
import re
import pytest
from meshtastic.util import fixme, stripnl, pskToString, our_exit, support_info, genPSK256, fromStr, fromPSK
@pytest.mark.unit
def test_genPSK256():
&#34;&#34;&#34;Test genPSK256&#34;&#34;&#34;
assert genPSK256() != &#39;&#39;
@pytest.mark.unit
def test_fromStr():
&#34;&#34;&#34;Test fromStr&#34;&#34;&#34;
assert fromStr(&#39;&#39;) == b&#39;&#39;
assert fromStr(&#39;0x12&#39;) == b&#39;\x12&#39;
assert fromStr(&#39;t&#39;)
assert fromStr(&#39;T&#39;)
assert fromStr(&#39;true&#39;)
assert fromStr(&#39;True&#39;)
assert fromStr(&#39;yes&#39;)
assert fromStr(&#39;Yes&#39;)
assert fromStr(&#39;f&#39;) is False
assert fromStr(&#39;F&#39;) is False
assert fromStr(&#39;false&#39;) is False
assert fromStr(&#39;False&#39;) is False
assert fromStr(&#39;no&#39;) is False
assert fromStr(&#39;No&#39;) is False
assert fromStr(&#39;100.01&#39;) == 100.01
assert fromStr(&#39;123&#39;) == 123
assert fromStr(&#39;abc&#39;) == &#39;abc&#39;
@pytest.mark.unit
def test_fromPSK():
&#34;&#34;&#34;Test fromPSK&#34;&#34;&#34;
assert fromPSK(&#39;random&#39;) != &#39;&#39;
assert fromPSK(&#39;none&#39;) == b&#39;\x00&#39;
assert fromPSK(&#39;default&#39;) == b&#39;\x01&#39;
assert fromPSK(&#39;simple22&#39;) == b&#39;\x17&#39;
assert fromPSK(&#39;trash&#39;) == &#39;trash&#39;
@pytest.mark.unit
def test_stripnl():
&#34;&#34;&#34;Test stripnl&#34;&#34;&#34;
assert stripnl(&#39;&#39;) == &#39;&#39;
assert stripnl(&#39;a\n&#39;) == &#39;a&#39;
assert stripnl(&#39; a \n &#39;) == &#39;a&#39;
assert stripnl(&#39;a\nb&#39;) == &#39;a b&#39;
@pytest.mark.unit
def test_pskToString_empty_string():
&#34;&#34;&#34;Test pskToString empty string&#34;&#34;&#34;
assert pskToString(&#39;&#39;) == &#39;unencrypted&#39;
@pytest.mark.unit
def test_pskToString_string():
&#34;&#34;&#34;Test pskToString string&#34;&#34;&#34;
assert pskToString(&#39;hunter123&#39;) == &#39;secret&#39;
@pytest.mark.unit
def test_pskToString_one_byte_zero_value():
&#34;&#34;&#34;Test pskToString one byte that is value of 0&#34;&#34;&#34;
assert pskToString(bytes([0x00])) == &#39;unencrypted&#39;
@pytest.mark.unit
def test_pskToString_one_byte_non_zero_value():
&#34;&#34;&#34;Test pskToString one byte that is non-zero&#34;&#34;&#34;
assert pskToString(bytes([0x01])) == &#39;default&#39;
@pytest.mark.unit
def test_pskToString_many_bytes():
&#34;&#34;&#34;Test pskToString many bytes&#34;&#34;&#34;
assert pskToString(bytes([0x02, 0x01])) == &#39;secret&#39;
@pytest.mark.unit
def test_pskToString_simple():
&#34;&#34;&#34;Test pskToString simple&#34;&#34;&#34;
assert pskToString(bytes([0x03])) == &#39;simple2&#39;
@pytest.mark.unit
def test_our_exit_zero_return_value():
&#34;&#34;&#34;Test our_exit with a zero return value&#34;&#34;&#34;
with pytest.raises(SystemExit) as pytest_wrapped_e:
our_exit(&#34;Warning: Some message&#34;, 0)
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 0
@pytest.mark.unit
def test_our_exit_non_zero_return_value():
&#34;&#34;&#34;Test our_exit with a non-zero return value&#34;&#34;&#34;
with pytest.raises(SystemExit) as pytest_wrapped_e:
our_exit(&#34;Error: Some message&#34;, 1)
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
@pytest.mark.unit
def test_fixme():
&#34;&#34;&#34;Test fixme&#34;&#34;&#34;
with pytest.raises(Exception) as pytest_wrapped_e:
fixme(&#34;some exception&#34;)
assert pytest_wrapped_e.type == Exception
@pytest.mark.unit
def test_support_info(capsys):
&#34;&#34;&#34;Test support_info&#34;&#34;&#34;
support_info()
out, err = capsys.readouterr()
assert re.search(r&#39;System&#39;, out, re.MULTILINE)
assert re.search(r&#39;Platform&#39;, out, re.MULTILINE)
assert re.search(r&#39;Machine&#39;, out, re.MULTILINE)
assert re.search(r&#39;Executable&#39;, out, re.MULTILINE)
assert err == &#39;&#39;</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.tests.test_util.test_fixme"><code class="name flex">
<span>def <span class="ident">test_fixme</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test fixme</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_fixme():
&#34;&#34;&#34;Test fixme&#34;&#34;&#34;
with pytest.raises(Exception) as pytest_wrapped_e:
fixme(&#34;some exception&#34;)
assert pytest_wrapped_e.type == Exception</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_util.test_fromPSK"><code class="name flex">
<span>def <span class="ident">test_fromPSK</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test fromPSK</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_fromPSK():
&#34;&#34;&#34;Test fromPSK&#34;&#34;&#34;
assert fromPSK(&#39;random&#39;) != &#39;&#39;
assert fromPSK(&#39;none&#39;) == b&#39;\x00&#39;
assert fromPSK(&#39;default&#39;) == b&#39;\x01&#39;
assert fromPSK(&#39;simple22&#39;) == b&#39;\x17&#39;
assert fromPSK(&#39;trash&#39;) == &#39;trash&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_util.test_fromStr"><code class="name flex">
<span>def <span class="ident">test_fromStr</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test fromStr</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_fromStr():
&#34;&#34;&#34;Test fromStr&#34;&#34;&#34;
assert fromStr(&#39;&#39;) == b&#39;&#39;
assert fromStr(&#39;0x12&#39;) == b&#39;\x12&#39;
assert fromStr(&#39;t&#39;)
assert fromStr(&#39;T&#39;)
assert fromStr(&#39;true&#39;)
assert fromStr(&#39;True&#39;)
assert fromStr(&#39;yes&#39;)
assert fromStr(&#39;Yes&#39;)
assert fromStr(&#39;f&#39;) is False
assert fromStr(&#39;F&#39;) is False
assert fromStr(&#39;false&#39;) is False
assert fromStr(&#39;False&#39;) is False
assert fromStr(&#39;no&#39;) is False
assert fromStr(&#39;No&#39;) is False
assert fromStr(&#39;100.01&#39;) == 100.01
assert fromStr(&#39;123&#39;) == 123
assert fromStr(&#39;abc&#39;) == &#39;abc&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_util.test_genPSK256"><code class="name flex">
<span>def <span class="ident">test_genPSK256</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test genPSK256</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_genPSK256():
&#34;&#34;&#34;Test genPSK256&#34;&#34;&#34;
assert genPSK256() != &#39;&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_util.test_our_exit_non_zero_return_value"><code class="name flex">
<span>def <span class="ident">test_our_exit_non_zero_return_value</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test our_exit with a non-zero return value</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_our_exit_non_zero_return_value():
&#34;&#34;&#34;Test our_exit with a non-zero return value&#34;&#34;&#34;
with pytest.raises(SystemExit) as pytest_wrapped_e:
our_exit(&#34;Error: Some message&#34;, 1)
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_util.test_our_exit_zero_return_value"><code class="name flex">
<span>def <span class="ident">test_our_exit_zero_return_value</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test our_exit with a zero return value</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_our_exit_zero_return_value():
&#34;&#34;&#34;Test our_exit with a zero return value&#34;&#34;&#34;
with pytest.raises(SystemExit) as pytest_wrapped_e:
our_exit(&#34;Warning: Some message&#34;, 0)
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 0</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_util.test_pskToString_empty_string"><code class="name flex">
<span>def <span class="ident">test_pskToString_empty_string</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test pskToString empty string</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_pskToString_empty_string():
&#34;&#34;&#34;Test pskToString empty string&#34;&#34;&#34;
assert pskToString(&#39;&#39;) == &#39;unencrypted&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_util.test_pskToString_many_bytes"><code class="name flex">
<span>def <span class="ident">test_pskToString_many_bytes</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test pskToString many bytes</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_pskToString_many_bytes():
&#34;&#34;&#34;Test pskToString many bytes&#34;&#34;&#34;
assert pskToString(bytes([0x02, 0x01])) == &#39;secret&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_util.test_pskToString_one_byte_non_zero_value"><code class="name flex">
<span>def <span class="ident">test_pskToString_one_byte_non_zero_value</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test pskToString one byte that is non-zero</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_pskToString_one_byte_non_zero_value():
&#34;&#34;&#34;Test pskToString one byte that is non-zero&#34;&#34;&#34;
assert pskToString(bytes([0x01])) == &#39;default&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_util.test_pskToString_one_byte_zero_value"><code class="name flex">
<span>def <span class="ident">test_pskToString_one_byte_zero_value</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test pskToString one byte that is value of 0</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_pskToString_one_byte_zero_value():
&#34;&#34;&#34;Test pskToString one byte that is value of 0&#34;&#34;&#34;
assert pskToString(bytes([0x00])) == &#39;unencrypted&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_util.test_pskToString_simple"><code class="name flex">
<span>def <span class="ident">test_pskToString_simple</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test pskToString simple</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_pskToString_simple():
&#34;&#34;&#34;Test pskToString simple&#34;&#34;&#34;
assert pskToString(bytes([0x03])) == &#39;simple2&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_util.test_pskToString_string"><code class="name flex">
<span>def <span class="ident">test_pskToString_string</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test pskToString string</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_pskToString_string():
&#34;&#34;&#34;Test pskToString string&#34;&#34;&#34;
assert pskToString(&#39;hunter123&#39;) == &#39;secret&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_util.test_stripnl"><code class="name flex">
<span>def <span class="ident">test_stripnl</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test stripnl</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_stripnl():
&#34;&#34;&#34;Test stripnl&#34;&#34;&#34;
assert stripnl(&#39;&#39;) == &#39;&#39;
assert stripnl(&#39;a\n&#39;) == &#39;a&#39;
assert stripnl(&#39; a \n &#39;) == &#39;a&#39;
assert stripnl(&#39;a\nb&#39;) == &#39;a b&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_util.test_support_info"><code class="name flex">
<span>def <span class="ident">test_support_info</span></span>(<span>capsys)</span>
</code></dt>
<dd>
<div class="desc"><p>Test support_info</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_support_info(capsys):
&#34;&#34;&#34;Test support_info&#34;&#34;&#34;
support_info()
out, err = capsys.readouterr()
assert re.search(r&#39;System&#39;, out, re.MULTILINE)
assert re.search(r&#39;Platform&#39;, out, re.MULTILINE)
assert re.search(r&#39;Machine&#39;, out, re.MULTILINE)
assert re.search(r&#39;Executable&#39;, out, re.MULTILINE)
assert err == &#39;&#39;</code></pre>
</details>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic.tests" href="index.html">meshtastic.tests</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.tests.test_util.test_fixme" href="#meshtastic.tests.test_util.test_fixme">test_fixme</a></code></li>
<li><code><a title="meshtastic.tests.test_util.test_fromPSK" href="#meshtastic.tests.test_util.test_fromPSK">test_fromPSK</a></code></li>
<li><code><a title="meshtastic.tests.test_util.test_fromStr" href="#meshtastic.tests.test_util.test_fromStr">test_fromStr</a></code></li>
<li><code><a title="meshtastic.tests.test_util.test_genPSK256" href="#meshtastic.tests.test_util.test_genPSK256">test_genPSK256</a></code></li>
<li><code><a title="meshtastic.tests.test_util.test_our_exit_non_zero_return_value" href="#meshtastic.tests.test_util.test_our_exit_non_zero_return_value">test_our_exit_non_zero_return_value</a></code></li>
<li><code><a title="meshtastic.tests.test_util.test_our_exit_zero_return_value" href="#meshtastic.tests.test_util.test_our_exit_zero_return_value">test_our_exit_zero_return_value</a></code></li>
<li><code><a title="meshtastic.tests.test_util.test_pskToString_empty_string" href="#meshtastic.tests.test_util.test_pskToString_empty_string">test_pskToString_empty_string</a></code></li>
<li><code><a title="meshtastic.tests.test_util.test_pskToString_many_bytes" href="#meshtastic.tests.test_util.test_pskToString_many_bytes">test_pskToString_many_bytes</a></code></li>
<li><code><a title="meshtastic.tests.test_util.test_pskToString_one_byte_non_zero_value" href="#meshtastic.tests.test_util.test_pskToString_one_byte_non_zero_value">test_pskToString_one_byte_non_zero_value</a></code></li>
<li><code><a title="meshtastic.tests.test_util.test_pskToString_one_byte_zero_value" href="#meshtastic.tests.test_util.test_pskToString_one_byte_zero_value">test_pskToString_one_byte_zero_value</a></code></li>
<li><code><a title="meshtastic.tests.test_util.test_pskToString_simple" href="#meshtastic.tests.test_util.test_pskToString_simple">test_pskToString_simple</a></code></li>
<li><code><a title="meshtastic.tests.test_util.test_pskToString_string" href="#meshtastic.tests.test_util.test_pskToString_string">test_pskToString_string</a></code></li>
<li><code><a title="meshtastic.tests.test_util.test_stripnl" href="#meshtastic.tests.test_util.test_stripnl">test_stripnl</a></code></li>
<li><code><a title="meshtastic.tests.test_util.test_support_info" href="#meshtastic.tests.test_util.test_support_info">test_support_info</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -3,9 +3,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.9.2" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.tunnel API documentation</title>
<meta name="description" content="" />
<meta name="description" content="Code for IP tunnel over a mesh …" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
@@ -22,11 +22,25 @@
<h1 class="title">Module <code>meshtastic.tunnel</code></h1>
</header>
<section id="section-intro">
<p>Code for IP tunnel over a mesh</p>
<h1 id="note-python-pytuntap-was-too-buggy">Note python-pytuntap was too buggy</h1>
<h1 id="using-pip3-install-pytap2">using pip3 install pytap2</h1>
<h1 id="make-sure-to-sudo-setcap-cap_net_admineip-usrbinpython38-so-python-can-access-tun-device-without-being-root">make sure to "sudo setcap cap_net_admin+eip /usr/bin/python3.8" so python can access tun device without being root</h1>
<h1 id="sudo-ip-tuntap-del-mode-tun-tun0">sudo ip tuntap del mode tun tun0</h1>
<h1 id="sudo-binrunsh-port-devttyusb0-setch-shortfast">sudo bin/run.sh &ndash;port /dev/ttyUSB0 &ndash;setch-shortfast</h1>
<h1 id="sudo-binrunsh-port-devttyusb0-tunnel-debug">sudo bin/run.sh &ndash;port /dev/ttyUSB0 &ndash;tunnel &ndash;debug</h1>
<h1 id="ssh-y-root19216810151-or-dietpi-default-password-p">ssh -Y root@192.168.10.151 (or dietpi), default password p</h1>
<h1 id="ncat-e-bincat-k-u-l-1235">ncat -e /bin/cat -k -u -l 1235</h1>
<h1 id="ncat-u-1011564152-1235">ncat -u 10.115.64.152 1235</h1>
<h1 id="ping-c-1-w-20-1011564152">ping -c 1 -W 20 10.115.64.152</h1>
<h1 id="ping-i-30-w-30-1011564152">ping -i 30 -W 30 10.115.64.152</h1>
<h1 id="fixme-use-a-more-optimal-mtu">FIXME: use a more optimal MTU</h1>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python"># code for IP tunnel over a mesh
<pre><code class="python">&#34;&#34;&#34;Code for IP tunnel over a mesh
# Note python-pytuntap was too buggy
# using pip3 install pytap2
# make sure to &#34;sudo setcap cap_net_admin+eip /usr/bin/python3.8&#34; so python can access tun device without being root
@@ -40,11 +54,15 @@
# ping -i 30 -W 30 10.115.64.152
# FIXME: use a more optimal MTU
&#34;&#34;&#34;
from . import portnums_pb2
from pubsub import pub
import logging
import threading
from pubsub import pub
from pytap2 import TapDevice
from . import portnums_pb2
# A new non standard log level that is lower level than DEBUG
LOG_TRACE = 5
@@ -111,8 +129,8 @@ class Tunnel:
global tunnelInstance
tunnelInstance = self
logging.info(
&#34;Starting IP to mesh tunnel (you must be root for this *pre-alpha* feature to work). Mesh members:&#34;)
logging.info(&#34;Starting IP to mesh tunnel (you must be root for this *pre-alpha* &#34;\
&#34;feature to work). Mesh members:&#34;)
pub.subscribe(onTunnelReceive, &#34;meshtastic.receive.data.IP_TUNNEL_APP&#34;)
myAddr = self._nodeNumToIp(self.iface.myInfo.my_node_num)
@@ -124,7 +142,6 @@ class Tunnel:
logging.debug(&#34;creating TUN device with MTU=200&#34;)
# FIXME - figure out real max MTU, it should be 240 - the overhead bytes for SubPacket and Data
from pytap2 import TapDevice
self.tun = TapDevice(name=&#34;mesh&#34;)
self.tun.up()
self.tun.ifconfig(address=myAddr, netmask=netmask, mtu=200)
@@ -134,14 +151,15 @@ class Tunnel:
self._rxThread.start()
def onReceive(self, packet):
&#34;&#34;&#34;onReceive&#34;&#34;&#34;
p = packet[&#34;decoded&#34;][&#34;payload&#34;]
if packet[&#34;from&#34;] == self.iface.myInfo.my_node_num:
logging.debug(&#34;Ignoring message we sent&#34;)
else:
logging.debug(
f&#34;Received mesh tunnel message type={type(p)} len={len(p)}&#34;)
# we don&#39;t really need to check for filtering here (sender should have checked), but this provides
# useful debug printing on types of packets received
# we don&#39;t really need to check for filtering here (sender should have checked),
# but this provides useful debug printing on types of packets received
if not self._shouldFilterPacket(p):
self.tun.write(p)
@@ -160,8 +178,8 @@ class Tunnel:
icmpType = p[20]
icmpCode = p[21]
checksum = p[22:24]
logging.debug(
f&#34;forwarding ICMP message src={ipstr(srcaddr)}, dest={ipstr(destAddr)}, type={icmpType}, code={icmpCode}, checksum={checksum}&#34;)
# pylint: disable=line-too-long
logging.debug(f&#34;forwarding ICMP message src={ipstr(srcaddr)}, dest={ipstr(destAddr)}, type={icmpType}, code={icmpCode}, checksum={checksum}&#34;)
# reply to pings (swap src and dest but keep rest of packet unchanged)
#pingback = p[:12]+p[16:20]+p[12:16]+p[20:]
# tap.write(pingback)
@@ -180,14 +198,12 @@ class Tunnel:
destport = readnet_u16(p, subheader + 2)
if destport in tcpBlacklist:
ignore = True
logging.log(
LOG_TRACE, f&#34;ignoring blacklisted TCP port {destport}&#34;)
logging.log(LOG_TRACE, f&#34;ignoring blacklisted TCP port {destport}&#34;)
else:
logging.debug(
f&#34;forwarding tcp srcport={srcport}, destport={destport}&#34;)
logging.debug(f&#34;forwarding tcp srcport={srcport}, destport={destport}&#34;)
else:
logging.warning(
f&#34;forwarding unexpected protocol 0x{protocol:02x}, src={ipstr(srcaddr)}, dest={ipstr(destAddr)}&#34;)
logging.warning(f&#34;forwarding unexpected protocol 0x{protocol:02x}, &#34;\
&#34;src={ipstr(srcaddr)}, dest={ipstr(destAddr)}&#34;)
return ignore
@@ -223,15 +239,14 @@ class Tunnel:
&#34;&#34;&#34;Forward the provided IP packet into the mesh&#34;&#34;&#34;
nodeId = self._ipToNodeId(destAddr)
if nodeId is not None:
logging.debug(
f&#34;Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}&#34;)
logging.debug(f&#34;Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}&#34;)
self.iface.sendData(
p, nodeId, portnums_pb2.IP_TUNNEL_APP, wantAck=False)
else:
logging.warning(
f&#34;Dropping packet because no node found for destIP={ipstr(destAddr)}&#34;)
logging.warning(f&#34;Dropping packet because no node found for destIP={ipstr(destAddr)}&#34;)
def close(self):
&#34;&#34;&#34;Close&#34;&#34;&#34;
self.tun.close()</code></pre>
</details>
</section>
@@ -355,8 +370,8 @@ subnet is used to construct our network number (normally 10.115.x.x)</p></div>
global tunnelInstance
tunnelInstance = self
logging.info(
&#34;Starting IP to mesh tunnel (you must be root for this *pre-alpha* feature to work). Mesh members:&#34;)
logging.info(&#34;Starting IP to mesh tunnel (you must be root for this *pre-alpha* &#34;\
&#34;feature to work). Mesh members:&#34;)
pub.subscribe(onTunnelReceive, &#34;meshtastic.receive.data.IP_TUNNEL_APP&#34;)
myAddr = self._nodeNumToIp(self.iface.myInfo.my_node_num)
@@ -368,7 +383,6 @@ subnet is used to construct our network number (normally 10.115.x.x)</p></div>
logging.debug(&#34;creating TUN device with MTU=200&#34;)
# FIXME - figure out real max MTU, it should be 240 - the overhead bytes for SubPacket and Data
from pytap2 import TapDevice
self.tun = TapDevice(name=&#34;mesh&#34;)
self.tun.up()
self.tun.ifconfig(address=myAddr, netmask=netmask, mtu=200)
@@ -378,14 +392,15 @@ subnet is used to construct our network number (normally 10.115.x.x)</p></div>
self._rxThread.start()
def onReceive(self, packet):
&#34;&#34;&#34;onReceive&#34;&#34;&#34;
p = packet[&#34;decoded&#34;][&#34;payload&#34;]
if packet[&#34;from&#34;] == self.iface.myInfo.my_node_num:
logging.debug(&#34;Ignoring message we sent&#34;)
else:
logging.debug(
f&#34;Received mesh tunnel message type={type(p)} len={len(p)}&#34;)
# we don&#39;t really need to check for filtering here (sender should have checked), but this provides
# useful debug printing on types of packets received
# we don&#39;t really need to check for filtering here (sender should have checked),
# but this provides useful debug printing on types of packets received
if not self._shouldFilterPacket(p):
self.tun.write(p)
@@ -404,8 +419,8 @@ subnet is used to construct our network number (normally 10.115.x.x)</p></div>
icmpType = p[20]
icmpCode = p[21]
checksum = p[22:24]
logging.debug(
f&#34;forwarding ICMP message src={ipstr(srcaddr)}, dest={ipstr(destAddr)}, type={icmpType}, code={icmpCode}, checksum={checksum}&#34;)
# pylint: disable=line-too-long
logging.debug(f&#34;forwarding ICMP message src={ipstr(srcaddr)}, dest={ipstr(destAddr)}, type={icmpType}, code={icmpCode}, checksum={checksum}&#34;)
# reply to pings (swap src and dest but keep rest of packet unchanged)
#pingback = p[:12]+p[16:20]+p[12:16]+p[20:]
# tap.write(pingback)
@@ -424,14 +439,12 @@ subnet is used to construct our network number (normally 10.115.x.x)</p></div>
destport = readnet_u16(p, subheader + 2)
if destport in tcpBlacklist:
ignore = True
logging.log(
LOG_TRACE, f&#34;ignoring blacklisted TCP port {destport}&#34;)
logging.log(LOG_TRACE, f&#34;ignoring blacklisted TCP port {destport}&#34;)
else:
logging.debug(
f&#34;forwarding tcp srcport={srcport}, destport={destport}&#34;)
logging.debug(f&#34;forwarding tcp srcport={srcport}, destport={destport}&#34;)
else:
logging.warning(
f&#34;forwarding unexpected protocol 0x{protocol:02x}, src={ipstr(srcaddr)}, dest={ipstr(destAddr)}&#34;)
logging.warning(f&#34;forwarding unexpected protocol 0x{protocol:02x}, &#34;\
&#34;src={ipstr(srcaddr)}, dest={ipstr(destAddr)}&#34;)
return ignore
@@ -467,15 +480,14 @@ subnet is used to construct our network number (normally 10.115.x.x)</p></div>
&#34;&#34;&#34;Forward the provided IP packet into the mesh&#34;&#34;&#34;
nodeId = self._ipToNodeId(destAddr)
if nodeId is not None:
logging.debug(
f&#34;Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}&#34;)
logging.debug(f&#34;Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}&#34;)
self.iface.sendData(
p, nodeId, portnums_pb2.IP_TUNNEL_APP, wantAck=False)
else:
logging.warning(
f&#34;Dropping packet because no node found for destIP={ipstr(destAddr)}&#34;)
logging.warning(f&#34;Dropping packet because no node found for destIP={ipstr(destAddr)}&#34;)
def close(self):
&#34;&#34;&#34;Close&#34;&#34;&#34;
self.tun.close()</code></pre>
</details>
<h3>Methods</h3>
@@ -484,12 +496,13 @@ subnet is used to construct our network number (normally 10.115.x.x)</p></div>
<span>def <span class="ident">close</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"></div>
<div class="desc"><p>Close</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def close(self):
&#34;&#34;&#34;Close&#34;&#34;&#34;
self.tun.close()</code></pre>
</details>
</dd>
@@ -497,20 +510,21 @@ subnet is used to construct our network number (normally 10.115.x.x)</p></div>
<span>def <span class="ident">onReceive</span></span>(<span>self, packet)</span>
</code></dt>
<dd>
<div class="desc"></div>
<div class="desc"><p>onReceive</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def onReceive(self, packet):
&#34;&#34;&#34;onReceive&#34;&#34;&#34;
p = packet[&#34;decoded&#34;][&#34;payload&#34;]
if packet[&#34;from&#34;] == self.iface.myInfo.my_node_num:
logging.debug(&#34;Ignoring message we sent&#34;)
else:
logging.debug(
f&#34;Received mesh tunnel message type={type(p)} len={len(p)}&#34;)
# we don&#39;t really need to check for filtering here (sender should have checked), but this provides
# useful debug printing on types of packets received
# we don&#39;t really need to check for filtering here (sender should have checked),
# but this provides useful debug printing on types of packets received
if not self._shouldFilterPacket(p):
self.tun.write(p)</code></pre>
</details>
@@ -528,13 +542,11 @@ subnet is used to construct our network number (normally 10.115.x.x)</p></div>
&#34;&#34;&#34;Forward the provided IP packet into the mesh&#34;&#34;&#34;
nodeId = self._ipToNodeId(destAddr)
if nodeId is not None:
logging.debug(
f&#34;Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}&#34;)
logging.debug(f&#34;Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}&#34;)
self.iface.sendData(
p, nodeId, portnums_pb2.IP_TUNNEL_APP, wantAck=False)
else:
logging.warning(
f&#34;Dropping packet because no node found for destIP={ipstr(destAddr)}&#34;)</code></pre>
logging.warning(f&#34;Dropping packet because no node found for destIP={ipstr(destAddr)}&#34;)</code></pre>
</details>
</dd>
</dl>
@@ -545,7 +557,20 @@ subnet is used to construct our network number (normally 10.115.x.x)</p></div>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
<ul>
<li><a href="#note-python-pytuntap-was-too-buggy">Note python-pytuntap was too buggy</a></li>
<li><a href="#using-pip3-install-pytap2">using pip3 install pytap2</a></li>
<li><a href="#make-sure-to-sudo-setcap-cap_net_admineip-usrbinpython38-so-python-can-access-tun-device-without-being-root">make sure to "sudo setcap cap_net_admin+eip /usr/bin/python3.8" so python can access tun device without being root</a></li>
<li><a href="#sudo-ip-tuntap-del-mode-tun-tun0">sudo ip tuntap del mode tun tun0</a></li>
<li><a href="#sudo-binrunsh-port-devttyusb0-setch-shortfast">sudo bin/run.sh &ndash;port /dev/ttyUSB0 &ndash;setch-shortfast</a></li>
<li><a href="#sudo-binrunsh-port-devttyusb0-tunnel-debug">sudo bin/run.sh &ndash;port /dev/ttyUSB0 &ndash;tunnel &ndash;debug</a></li>
<li><a href="#ssh-y-root19216810151-or-dietpi-default-password-p">ssh -Y root@192.168.10.151 (or dietpi), default password p</a></li>
<li><a href="#ncat-e-bincat-k-u-l-1235">ncat -e /bin/cat -k -u -l 1235</a></li>
<li><a href="#ncat-u-1011564152-1235">ncat -u 10.115.64.152 1235</a></li>
<li><a href="#ping-c-1-w-20-1011564152">ping -c 1 -W 20 10.115.64.152</a></li>
<li><a href="#ping-i-30-w-30-1011564152">ping -i 30 -W 30 10.115.64.152</a></li>
<li><a href="#fixme-use-a-more-optimal-mtu">FIXME: use a more optimal MTU</a></li>
</ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
@@ -584,7 +609,7 @@ subnet is used to construct our network number (normally 10.115.x.x)</p></div>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc"><cite>pdoc</cite> 0.9.2</a>.</p>
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -3,9 +3,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.9.2" />
<meta name="generator" content="pdoc 0.10.0" />
<title>meshtastic.util API documentation</title>
<meta name="description" content="" />
<meta name="description" content="Utility functions." />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
@@ -22,27 +22,103 @@
<h1 class="title">Module <code>meshtastic.util</code></h1>
</header>
<section id="section-intro">
<p>Utility functions.</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">from collections import defaultdict
import serial, traceback
import serial.tools.list_ports
<pre><code class="python">&#34;&#34;&#34;Utility functions.
&#34;&#34;&#34;
import traceback
from queue import Queue
import threading, sys, time, logging
import os
import sys
import time
import platform
import logging
import threading
import serial
import serial.tools.list_ports
import pkg_resources
&#34;&#34;&#34;Some devices such as a seger jlink we never want to accidentally open&#34;&#34;&#34;
blacklistVids = dict.fromkeys([0x1366])
def genPSK256():
&#34;&#34;&#34;Generate a random preshared key&#34;&#34;&#34;
return os.urandom(32)
def fromPSK(valstr):
&#34;&#34;&#34;A special version of fromStr that assumes the user is trying to set a PSK.
In that case we also allow &#34;none&#34;, &#34;default&#34; or &#34;random&#34; (to have python generate one), or simpleN
&#34;&#34;&#34;
if valstr == &#34;random&#34;:
return genPSK256()
elif valstr == &#34;none&#34;:
return bytes([0]) # Use the &#39;no encryption&#39; PSK
elif valstr == &#34;default&#34;:
return bytes([1]) # Use default channel psk
elif valstr.startswith(&#34;simple&#34;):
# Use one of the single byte encodings
return bytes([int(valstr[6:]) + 1])
else:
return fromStr(valstr)
def fromStr(valstr):
&#34;&#34;&#34;Try to parse as int, float or bool (and fallback to a string as last resort)
Returns: an int, bool, float, str or byte array (for strings of hex digits)
Args:
valstr (string): A user provided string
&#34;&#34;&#34;
if len(valstr) == 0: # Treat an emptystring as an empty bytes
val = bytes()
elif valstr.startswith(&#39;0x&#39;):
# if needed convert to string with asBytes.decode(&#39;utf-8&#39;)
val = bytes.fromhex(valstr[2:])
elif valstr.lower() in {&#34;t&#34;, &#34;true&#34;, &#34;yes&#34;}:
val = True
elif valstr.lower() in {&#34;f&#34;, &#34;false&#34;, &#34;no&#34;}:
val = False
else:
try:
val = int(valstr)
except ValueError:
try:
val = float(valstr)
except ValueError:
val = valstr # Not a float or an int, assume string
return val
def pskToString(psk: bytes):
&#34;&#34;&#34;Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string&#34;&#34;&#34;
if len(psk) == 0:
return &#34;unencrypted&#34;
elif len(psk) == 1:
b = psk[0]
if b == 0:
return &#34;unencrypted&#34;
elif b == 1:
return &#34;default&#34;
else:
return f&#34;simple{b - 1}&#34;
else:
return &#34;secret&#34;
def stripnl(s):
&#34;&#34;&#34;remove newlines from a string (and remove extra whitespace)&#34;&#34;&#34;
&#34;&#34;&#34;Remove newlines from a string (and remove extra whitespace)&#34;&#34;&#34;
s = str(s).replace(&#34;\n&#34;, &#34; &#34;)
return &#39; &#39;.join(s.split())
def fixme(message):
&#34;&#34;&#34;Raise an exception for things that needs to be fixed&#34;&#34;&#34;
raise Exception(f&#34;FIXME: {message}&#34;)
@@ -61,7 +137,7 @@ def findPorts():
list -- a list of device paths
&#34;&#34;&#34;
l = list(map(lambda port: port.device,
filter(lambda port: port.vid != None and port.vid not in blacklistVids,
filter(lambda port: port.vid is not None and port.vid not in blacklistVids,
serial.tools.list_ports.comports())))
l.sort()
return l
@@ -75,6 +151,7 @@ class dotdict(dict):
class Timeout:
&#34;&#34;&#34;Timeout class&#34;&#34;&#34;
def __init__(self, maxSecs=20):
self.expireTime = 0
self.sleepInterval = 0.1
@@ -104,6 +181,7 @@ class DeferredExecution():
self.thread.start()
def queueWork(self, runnable):
&#34;&#34;&#34; Queue up the work&#34;&#34;&#34;
self.queue.put(runnable)
def _run(self):
@@ -114,7 +192,36 @@ class DeferredExecution():
except:
logging.error(
f&#34;Unexpected error in deferred execution {sys.exc_info()[0]}&#34;)
print(traceback.format_exc())</code></pre>
print(traceback.format_exc())
def our_exit(message, return_value = 1):
&#34;&#34;&#34;Print the message and return a value.
return_value defaults to 1 (non-successful)
&#34;&#34;&#34;
print(message)
sys.exit(return_value)
def support_info():
&#34;&#34;&#34;Print out info that helps troubleshooting of the cli.&#34;&#34;&#34;
print(&#39;&#39;)
print(&#39;If having issues with meshtastic cli or python library&#39;)
print(&#39;or wish to make feature requests, visit:&#39;)
print(&#39;https://github.com/meshtastic/Meshtastic-python/issues&#39;)
print(&#39;When adding an issue, be sure to include the following info:&#39;)
print(&#39; System: {0}&#39;.format(platform.system()))
print(&#39; Platform: {0}&#39;.format(platform.platform()))
print(&#39; Release: {0}&#39;.format(platform.uname().release))
print(&#39; Machine: {0}&#39;.format(platform.uname().machine))
print(&#39; Encoding (stdin): {0}&#39;.format(sys.stdin.encoding))
print(&#39; Encoding (stdout): {0}&#39;.format(sys.stdout.encoding))
print(&#39; meshtastic: v{0}&#39;.format(pkg_resources.require(&#39;meshtastic&#39;)[0].version))
print(&#39; Executable: {0}&#39;.format(sys.argv[0]))
print(&#39; Python: {0} {1} {2}&#39;.format(platform.python_version(),
platform.python_implementation(), platform.python_compiler()))
print(&#39;&#39;)
print(&#39;Please add the output from the command: meshtastic --info&#39;)</code></pre>
</details>
</section>
<section>
@@ -159,7 +266,7 @@ class DeferredExecution():
list -- a list of device paths
&#34;&#34;&#34;
l = list(map(lambda port: port.device,
filter(lambda port: port.vid != None and port.vid not in blacklistVids,
filter(lambda port: port.vid is not None and port.vid not in blacklistVids,
serial.tools.list_ports.comports())))
l.sort()
return l</code></pre>
@@ -169,30 +276,188 @@ class DeferredExecution():
<span>def <span class="ident">fixme</span></span>(<span>message)</span>
</code></dt>
<dd>
<div class="desc"></div>
<div class="desc"><p>Raise an exception for things that needs to be fixed</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def fixme(message):
&#34;&#34;&#34;Raise an exception for things that needs to be fixed&#34;&#34;&#34;
raise Exception(f&#34;FIXME: {message}&#34;)</code></pre>
</details>
</dd>
<dt id="meshtastic.util.fromPSK"><code class="name flex">
<span>def <span class="ident">fromPSK</span></span>(<span>valstr)</span>
</code></dt>
<dd>
<div class="desc"><p>A special version of fromStr that assumes the user is trying to set a PSK.
In that case we also allow "none", "default" or "random" (to have python generate one), or simpleN</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def fromPSK(valstr):
&#34;&#34;&#34;A special version of fromStr that assumes the user is trying to set a PSK.
In that case we also allow &#34;none&#34;, &#34;default&#34; or &#34;random&#34; (to have python generate one), or simpleN
&#34;&#34;&#34;
if valstr == &#34;random&#34;:
return genPSK256()
elif valstr == &#34;none&#34;:
return bytes([0]) # Use the &#39;no encryption&#39; PSK
elif valstr == &#34;default&#34;:
return bytes([1]) # Use default channel psk
elif valstr.startswith(&#34;simple&#34;):
# Use one of the single byte encodings
return bytes([int(valstr[6:]) + 1])
else:
return fromStr(valstr)</code></pre>
</details>
</dd>
<dt id="meshtastic.util.fromStr"><code class="name flex">
<span>def <span class="ident">fromStr</span></span>(<span>valstr)</span>
</code></dt>
<dd>
<div class="desc"><p>Try to parse as int, float or bool (and fallback to a string as last resort)</p>
<p>Returns: an int, bool, float, str or byte array (for strings of hex digits)</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>valstr</code></strong> :&ensp;<code>string</code></dt>
<dd>A user provided string</dd>
</dl></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def fromStr(valstr):
&#34;&#34;&#34;Try to parse as int, float or bool (and fallback to a string as last resort)
Returns: an int, bool, float, str or byte array (for strings of hex digits)
Args:
valstr (string): A user provided string
&#34;&#34;&#34;
if len(valstr) == 0: # Treat an emptystring as an empty bytes
val = bytes()
elif valstr.startswith(&#39;0x&#39;):
# if needed convert to string with asBytes.decode(&#39;utf-8&#39;)
val = bytes.fromhex(valstr[2:])
elif valstr.lower() in {&#34;t&#34;, &#34;true&#34;, &#34;yes&#34;}:
val = True
elif valstr.lower() in {&#34;f&#34;, &#34;false&#34;, &#34;no&#34;}:
val = False
else:
try:
val = int(valstr)
except ValueError:
try:
val = float(valstr)
except ValueError:
val = valstr # Not a float or an int, assume string
return val</code></pre>
</details>
</dd>
<dt id="meshtastic.util.genPSK256"><code class="name flex">
<span>def <span class="ident">genPSK256</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Generate a random preshared key</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def genPSK256():
&#34;&#34;&#34;Generate a random preshared key&#34;&#34;&#34;
return os.urandom(32)</code></pre>
</details>
</dd>
<dt id="meshtastic.util.our_exit"><code class="name flex">
<span>def <span class="ident">our_exit</span></span>(<span>message, return_value=1)</span>
</code></dt>
<dd>
<div class="desc"><p>Print the message and return a value.
return_value defaults to 1 (non-successful)</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def our_exit(message, return_value = 1):
&#34;&#34;&#34;Print the message and return a value.
return_value defaults to 1 (non-successful)
&#34;&#34;&#34;
print(message)
sys.exit(return_value)</code></pre>
</details>
</dd>
<dt id="meshtastic.util.pskToString"><code class="name flex">
<span>def <span class="ident">pskToString</span></span>(<span>psk: bytes)</span>
</code></dt>
<dd>
<div class="desc"><p>Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def pskToString(psk: bytes):
&#34;&#34;&#34;Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string&#34;&#34;&#34;
if len(psk) == 0:
return &#34;unencrypted&#34;
elif len(psk) == 1:
b = psk[0]
if b == 0:
return &#34;unencrypted&#34;
elif b == 1:
return &#34;default&#34;
else:
return f&#34;simple{b - 1}&#34;
else:
return &#34;secret&#34;</code></pre>
</details>
</dd>
<dt id="meshtastic.util.stripnl"><code class="name flex">
<span>def <span class="ident">stripnl</span></span>(<span>s)</span>
</code></dt>
<dd>
<div class="desc"><p>remove newlines from a string (and remove extra whitespace)</p></div>
<div class="desc"><p>Remove newlines from a string (and remove extra whitespace)</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def stripnl(s):
&#34;&#34;&#34;remove newlines from a string (and remove extra whitespace)&#34;&#34;&#34;
&#34;&#34;&#34;Remove newlines from a string (and remove extra whitespace)&#34;&#34;&#34;
s = str(s).replace(&#34;\n&#34;, &#34; &#34;)
return &#39; &#39;.join(s.split())</code></pre>
</details>
</dd>
<dt id="meshtastic.util.support_info"><code class="name flex">
<span>def <span class="ident">support_info</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Print out info that helps troubleshooting of the cli.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def support_info():
&#34;&#34;&#34;Print out info that helps troubleshooting of the cli.&#34;&#34;&#34;
print(&#39;&#39;)
print(&#39;If having issues with meshtastic cli or python library&#39;)
print(&#39;or wish to make feature requests, visit:&#39;)
print(&#39;https://github.com/meshtastic/Meshtastic-python/issues&#39;)
print(&#39;When adding an issue, be sure to include the following info:&#39;)
print(&#39; System: {0}&#39;.format(platform.system()))
print(&#39; Platform: {0}&#39;.format(platform.platform()))
print(&#39; Release: {0}&#39;.format(platform.uname().release))
print(&#39; Machine: {0}&#39;.format(platform.uname().machine))
print(&#39; Encoding (stdin): {0}&#39;.format(sys.stdin.encoding))
print(&#39; Encoding (stdout): {0}&#39;.format(sys.stdout.encoding))
print(&#39; meshtastic: v{0}&#39;.format(pkg_resources.require(&#39;meshtastic&#39;)[0].version))
print(&#39; Executable: {0}&#39;.format(sys.argv[0]))
print(&#39; Python: {0} {1} {2}&#39;.format(platform.python_version(),
platform.python_implementation(), platform.python_compiler()))
print(&#39;&#39;)
print(&#39;Please add the output from the command: meshtastic --info&#39;)</code></pre>
</details>
</dd>
</dl>
</section>
<section>
@@ -218,6 +483,7 @@ class DeferredExecution():
self.thread.start()
def queueWork(self, runnable):
&#34;&#34;&#34; Queue up the work&#34;&#34;&#34;
self.queue.put(runnable)
def _run(self):
@@ -236,12 +502,13 @@ class DeferredExecution():
<span>def <span class="ident">queueWork</span></span>(<span>self, runnable)</span>
</code></dt>
<dd>
<div class="desc"></div>
<div class="desc"><p>Queue up the work</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def queueWork(self, runnable):
&#34;&#34;&#34; Queue up the work&#34;&#34;&#34;
self.queue.put(runnable)</code></pre>
</details>
</dd>
@@ -252,12 +519,13 @@ class DeferredExecution():
<span>(</span><span>maxSecs=20)</span>
</code></dt>
<dd>
<div class="desc"></div>
<div class="desc"><p>Timeout class</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class Timeout:
&#34;&#34;&#34;Timeout class&#34;&#34;&#34;
def __init__(self, maxSecs=20):
self.expireTime = 0
self.sleepInterval = 0.1
@@ -349,11 +617,17 @@ class DeferredExecution():
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<ul class="two-column">
<li><code><a title="meshtastic.util.catchAndIgnore" href="#meshtastic.util.catchAndIgnore">catchAndIgnore</a></code></li>
<li><code><a title="meshtastic.util.findPorts" href="#meshtastic.util.findPorts">findPorts</a></code></li>
<li><code><a title="meshtastic.util.fixme" href="#meshtastic.util.fixme">fixme</a></code></li>
<li><code><a title="meshtastic.util.fromPSK" href="#meshtastic.util.fromPSK">fromPSK</a></code></li>
<li><code><a title="meshtastic.util.fromStr" href="#meshtastic.util.fromStr">fromStr</a></code></li>
<li><code><a title="meshtastic.util.genPSK256" href="#meshtastic.util.genPSK256">genPSK256</a></code></li>
<li><code><a title="meshtastic.util.our_exit" href="#meshtastic.util.our_exit">our_exit</a></code></li>
<li><code><a title="meshtastic.util.pskToString" href="#meshtastic.util.pskToString">pskToString</a></code></li>
<li><code><a title="meshtastic.util.stripnl" href="#meshtastic.util.stripnl">stripnl</a></code></li>
<li><code><a title="meshtastic.util.support_info" href="#meshtastic.util.support_info">support_info</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
@@ -380,7 +654,7 @@ class DeferredExecution():
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc"><cite>pdoc</cite> 0.9.2</a>.</p>
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

View File

@@ -1,2 +1,389 @@
# Python API commands guide
# Python API Commands Guide
The python pip package installs a "meshtastic" command line executable, which displays packets sent over the network as JSON and lets you see serial debugging information from the meshtastic devices. This command is not run inside of python, you run it from your operating system shell prompt directly. If when you type "meshtastic" it doesn't find the command and you are using Windows: Check that the python "scripts" directory is in your path.
## Optional Arguments
### -h or --help
Shows a help message that describes the arguments.
**Usage**
``` shell
meshtastic -h
```
### --port PORT
The port the Meshtastic device is connected to, i.e. /dev/ttyUSB0 or COM4. if unspecified, meshtastic will try to find it. Important to use when multiple devices are connected to ensure you call the command for the correct device.
**Usage**
``` shell
meshtastic --port /dev/ttyUSB0 --info
meshtastic --port COM4 --info
```
### --host HOST
The hostname/ipaddr of the device to connect to (over TCP).
**Usage**
``` shell
meshtastic --host HOST
```
### --seriallog SERIALLOG
Logs device serial output to either 'stdout', 'none' or a filename to append to.
**Usage**
``` shell
meshtastic --port /dev/ttyUSB0 --seriallog
```
### --info
Read and display the radio config information.
**Usage**
``` shell
meshtastic --port /dev/ttyUSB0 --info
```
### --nodes
Prints a node list in a pretty, formatted table.
**Usage**
``` shell
meshtastic --nodes
```
### --qr
Displays the QR code that corresponds to the current channel.
**Usage**
``` shell
meshtastic --qr
```
### --get GET
Gets a preferences field.
**Usage**
``` shell
meshtastic --get modem_config
```
### --set SET SET
Sets a preferences field.
**Usage**
``` shell
meshtastic --set region Unset
```
### --seturl SETURL
Set a channel URL.
**Usage**
``` shell
meshtastic --seturl https://www.meshtastic.org/c/GAMiIE67C6zsNmlWQ-KE1tKt0fRKFciHka-DShI6G7ElvGOiKgZzaGFyZWQ=
```
### --ch-index CH_INDEX
Set the specified channel index
**Usage**
``` shell
meshtastic --ch-index 1 --ch-disable
```
### --ch-add CH_ADD
Add a secondary channel, you must specify a channel name.
**Usage**
``` shell
meshtastic --ch-add testing-channel
```
### --ch-del
Delete the ch-index channel.
**Usage**
``` shell
meshtastic --ch-index 1 --ch-del
```
### --ch-enable
Enable the specified channel.
**Usage**
``` shell
meshtastic --ch-index 1 --ch-enable
```
### --ch-disable
Disable the specified channel.
**Usage**
``` shell
meshtastic --ch-index 1 --ch-disable
```
### --ch-set CH_SET CH_SET
Set a channel parameter.
**Usage**
``` shell
meshtastic --ch-set id 1234
```
### --ch-longslow
Change to the standard long-range (but slow) channel.
**Usage**
``` shell
meshtastic --ch-longslow
```
### --ch-shortfast
Change to the standard fast (but short range) channel.
**Usage**
``` shell
meshtastic --ch-shortfast
```
### --set-owner SET_OWNER
Set device owner name.
**Usage**
``` shell
meshtastic --dest \!28979058 --set-owner "MeshyJohn"
```
### --set-ham SET_HAM
Set licensed HAM ID and turn off encryption.
**Usage**
``` shell
meshtastic --set-ham KI1345
```
### --dest DEST
The destination node id for any sent commands
**Usage**
``` shell
meshtastic --dest \!28979058 --set-owner "MeshyJohn"
```
### --sendtext SENDTEXT
Send a text message.
**Usage**
``` shell
meshtastic --sendtext "Hello Mesh!"
```
### --sendping
Send a ping message (which requests a reply).
**Usage**
``` shell
meshtastic --sendping
```
### --reboot
Tell the destination node to reboot.
**Usage**
``` shell
meshtastic --dest \!28979058 --reboot
```
### --reply
Reply to received messages.
**Usage**
``` shell
meshtastic --reply
```
### --gpio-wrb GPIO_WRB GPIO_WRB
Set a particular GPIO # to 1 or 0.
**Usage**
``` shell
meshtastic --port /dev/ttyUSB0 --gpio-wrb 4 1 --dest \!28979058
```
### --gpio-rd GPIO_RD
Read from a GPIO mask.
**Usage**
``` shell
meshtastic --port /dev/ttyUSB0 --gpio-rd 0x10 --dest \!28979058
```
### --gpio-watch GPIO_WATCH
Start watching a GPIO mask for changes.
**Usage**
``` shell
meshtastic --port /dev/ttyUSB0 --gpio-watch 0x10 --dest \!28979058
```
### --no-time
Suppress sending the current time to the mesh.
**Usage**
``` shell
meshtastic --port /dev/ttyUSB0 --no-time
```
### --setalt SETALT
Set device altitude (allows use without GPS).
**Usage**
``` shell
meshtastic --setalt 120
```
### --setlat SETLAT
Set device latitude (allows use without GPS).
**Usage**
``` shell
meshtastic --setlat 25.2
```
### --setlon SETLON
Set device longitude (allows use without GPS).
**Usage**
``` shell
meshtastic --setlon -16.8
```
### --debug
Show API library debug log messages.
**Usage**
``` shell
meshtastic --debug --info
```
### --test
Run stress test against all connected Meshtastic devices.
**Usage**
``` shell
meshtastic --test
```
### --ble BLE
BLE mac address to connect to (BLE is not yet supported for this tool).
**Usage**
``` shell
meshtastic --ble "83:38:92:32:37:48"
```
### --noproto
Don't start the API, just function as a dumb serial terminal.
**Usage**
``` shell
meshtastic --noproto
```
### --version
Show program's version number and exit.
**Usage**
``` shell
meshtastic --version
```
## Deprecated Arguments
### --setchan
Deprecated - use "--ch-set param value" instead.
### --set-router
Deprecated - use "--set is_router true" instead.
### --unset-router
Deprecated - use "--set is_router false" instead.

15
example_config.yaml Normal file
View File

@@ -0,0 +1,15 @@
owner: Bob TBeam
channel_url: https://www.meshtastic.org/d/#CgUYAyIBAQ
location:
lat: 35.88888
lon: -93.88888
alt: 304
user_prefs:
region: 1
is_always_powered: 'true'
send_owner_interval: 2
screen_on_secs: 31536000
wait_bluetooth_secs: 31536000

View File

@@ -28,10 +28,10 @@ type of packet, you should subscribe to the full topic name. If you want to see
- meshtastic.receive.data.portnum(packet) (where portnum is an integer or well known PortNum enum)
- meshtastic.node.updated(node = NodeInfo) - published when a node in the DB changes (appears, location changed, username changed, etc...)
We receive position, user, or data packets from the mesh. You probably only care about meshtastic.receive.data. The first argument for
that publish will be the packet. Text or binary data packets (from sendData or sendText) will both arrive this way. If you print packet
you'll see the fields in the dictionary. decoded.data.payload will contain the raw bytes that were sent. If the packet was sent with
sendText, decoded.data.text will **also** be populated with the decoded string. For ASCII these two strings will be the same, but for
We receive position, user, or data packets from the mesh. You probably only care about meshtastic.receive.data. The first argument for
that publish will be the packet. Text or binary data packets (from sendData or sendText) will both arrive this way. If you print packet
you'll see the fields in the dictionary. decoded.data.payload will contain the raw bytes that were sent. If the packet was sent with
sendText, decoded.data.text will **also** be populated with the decoded string. For ASCII these two strings will be the same, but for
unicode scripts they can be different.
# Example Usage
@@ -55,39 +55,31 @@ interface = meshtastic.SerialInterface()
"""
import pygatt
import google.protobuf.json_format
import serial
import threading
import base64
import logging
import sys
import os
import platform
import random
import socket
import sys
import stat
import threading
import traceback
import time
import base64
import platform
import socket
from datetime import datetime
from typing import *
import serial
import timeago
import os
import stat
from . import mesh_pb2, portnums_pb2, apponly_pb2, admin_pb2, environmental_measurement_pb2, remote_hardware_pb2, channel_pb2, radioconfig_pb2, util
from .util import fixme, catchAndIgnore, stripnl, DeferredExecution, Timeout
from .node import Node
import google.protobuf.json_format
import pygatt
from pubsub import pub
from dotmap import DotMap
from datetime import datetime
from tabulate import tabulate
from typing import *
from google.protobuf.json_format import MessageToJson
from .util import fixme, catchAndIgnore, stripnl, DeferredExecution, Timeout
from .node import Node
from . import mesh_pb2, portnums_pb2, apponly_pb2, admin_pb2, environmental_measurement_pb2, remote_hardware_pb2, channel_pb2, radioconfig_pb2, util
START1 = 0x94
START2 = 0xc3
HEADER_LEN = 4
MAX_TO_FROM_RADIO_SIZE = 512
defaultHopLimit = 3
"""A special ID that means broadcast"""
BROADCAST_ADDR = "^all"
"""A special ID that means the local node"""
LOCAL_ADDR = "^local"
@@ -95,9 +87,14 @@ LOCAL_ADDR = "^local"
# if using 8 bit nodenums this will be shortend on the target
BROADCAST_NUM = 0xffffffff
"""The numeric buildnumber (shared with android apps) specifying the level of device code we are guaranteed to understand
"""A special ID that means broadcast"""
BROADCAST_ADDR = "^all"
format is Mmmss (where M is 1+the numeric major number. i.e. 20120 means 1.1.20
"""The numeric buildnumber (shared with android apps) specifying the
level of device code we are guaranteed to understand
format is Mmmss (where M is 1+the numeric major number. i.e. 20120 means 1.1.20
"""
OUR_APP_VERSION = 20200
@@ -121,891 +118,6 @@ class KnownProtocol(NamedTuple):
onReceive: Callable = None
class MeshInterface:
"""Interface class for meshtastic devices
Properties:
isConnected
nodes
debugOut
"""
def __init__(self, debugOut=None, noProto=False):
"""Constructor
Keyword Arguments:
noProto -- If True, don't try to run our protocol on the link - just be a dumb serial client.
"""
self.debugOut = debugOut
self.nodes = None # FIXME
self.isConnected = threading.Event()
self.noProto = noProto
self.localNode = Node(self, -1) # We fixup nodenum later
self.myInfo = None # We don't have device info yet
self.responseHandlers = {} # A map from request ID to the handler
self.failure = None # If we've encountered a fatal exception it will be kept here
self._timeout = Timeout()
self.heartbeatTimer = None
random.seed() # FIXME, we should not clobber the random seedval here, instead tell user they must call it
self.currentPacketId = random.randint(0, 0xffffffff)
def close(self):
"""Shutdown this interface"""
if self.heartbeatTimer:
self.heartbeatTimer.cancel()
self._sendDisconnect()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
if exc_type is not None and exc_value is not None:
logging.error(
f'An exception of type {exc_type} with value {exc_value} has occurred')
if traceback is not None:
logging.error(f'Traceback: {traceback}')
self.close()
def showInfo(self, file=sys.stdout):
"""Show human readable summary about this object"""
print(
f"Owner: {self.getLongName()} ({self.getShortName()})", file=file)
print(f"\nMy info: {stripnl(MessageToJson(self.myInfo))}", file=file)
print("\nNodes in mesh:", file=file)
for n in self.nodes.values():
print(f" {stripnl(n)}", file=file)
def showNodes(self, includeSelf=True, file=sys.stdout):
"""Show table summary of nodes in mesh"""
def formatFloat(value, precision=2, unit=''):
return f'{value:.{precision}f}{unit}' if value else None
def getLH(ts):
return datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S') if ts else None
def getTimeAgo(ts):
return timeago.format(datetime.fromtimestamp(ts), datetime.now()) if ts else None
rows = []
for node in self.nodes.values():
if not includeSelf and node['num'] == self.localNode.nodeNum:
continue
row = {"N": 0}
user = node.get('user')
if user:
row.update({
"User": user['longName'],
"AKA": user['shortName'],
"ID": user['id'],
})
pos = node.get('position')
if pos:
row.update({
"Latitude": formatFloat(pos.get("latitude"), 4, "°"),
"Longitude": formatFloat(pos.get("longitude"), 4, "°"),
"Altitude": formatFloat(pos.get("altitude"), 0, " m"),
"Battery": formatFloat(pos.get("batteryLevel"), 2, "%"),
})
row.update({
"SNR": formatFloat(node.get("snr"), 2, " dB"),
"LastHeard": getLH(node.get("lastHeard")),
"Since": getTimeAgo(node.get("lastHeard")),
})
rows.append(row)
# Why doesn't this way work?
#rows.sort(key=lambda r: r.get('LastHeard', '0000'), reverse=True)
rows.sort(key=lambda r: r.get('LastHeard') or '0000', reverse=True)
for i, row in enumerate(rows):
row['N'] = i+1
print(tabulate(rows, headers='keys', missingval='N/A',
tablefmt='fancy_grid'), file=file)
def getNode(self, nodeId):
"""Return a node object which contains device settings and channel info"""
if nodeId == LOCAL_ADDR:
return self.localNode
else:
n = Node(self, nodeId)
n.requestConfig()
if not n.waitForConfig():
raise Exception("Timed out waiting for node config")
return n
def sendText(self, text: AnyStr,
destinationId=BROADCAST_ADDR,
wantAck=False,
wantResponse=False,
hopLimit=defaultHopLimit,
onResponse=None):
"""Send a utf8 string to some other node, if the node has a display it will also be shown on the device.
Arguments:
text {string} -- The text to send
Keyword Arguments:
destinationId {nodeId or nodeNum} -- where to send this message (default: {BROADCAST_ADDR})
portNum -- the application portnum (similar to IP port numbers) of the destination, see portnums.proto for a list
wantAck -- True if you want the message sent in a reliable manner (with retries and ack/nak provided for delivery)
wantResponse -- True if you want the service on the other side to send an application layer response
Returns the sent packet. The id field will be populated in this packet and can be used to track future message acks/naks.
"""
return self.sendData(text.encode("utf-8"), destinationId,
portNum=portnums_pb2.PortNum.TEXT_MESSAGE_APP,
wantAck=wantAck,
wantResponse=wantResponse,
hopLimit=hopLimit,
onResponse=onResponse)
def sendData(self, data, destinationId=BROADCAST_ADDR,
portNum=portnums_pb2.PortNum.PRIVATE_APP, wantAck=False,
wantResponse=False,
hopLimit=defaultHopLimit,
onResponse=None,
channelIndex=0):
"""Send a data packet to some other node
Keyword Arguments:
data -- the data to send, either as an array of bytes or as a protobuf (which will be automatically serialized to bytes)
destinationId {nodeId or nodeNum} -- where to send this message (default: {BROADCAST_ADDR})
portNum -- the application portnum (similar to IP port numbers) of the destination, see portnums.proto for a list
wantAck -- True if you want the message sent in a reliable manner (with retries and ack/nak provided for delivery)
wantResponse -- True if you want the service on the other side to send an application layer response
onResponse -- A closure of the form funct(packet), that will be called when a response packet arrives (or the transaction is NAKed due to non receipt)
Returns the sent packet. The id field will be populated in this packet and can be used to track future message acks/naks.
"""
if getattr(data, "SerializeToString", None):
logging.debug(f"Serializing protobuf as data: {stripnl(data)}")
data = data.SerializeToString()
if len(data) > mesh_pb2.Constants.DATA_PAYLOAD_LEN:
raise Exception("Data payload too big")
if portNum == portnums_pb2.PortNum.UNKNOWN_APP: # we are now more strict wrt port numbers
raise Exception("A non-zero port number must be specified")
meshPacket = mesh_pb2.MeshPacket()
meshPacket.channel = channelIndex
meshPacket.decoded.payload = data
meshPacket.decoded.portnum = portNum
meshPacket.decoded.want_response = wantResponse
p = self._sendPacket(meshPacket, destinationId,
wantAck=wantAck, hopLimit=hopLimit)
if onResponse is not None:
self._addResponseHandler(p.id, onResponse)
return p
def sendPosition(self, latitude=0.0, longitude=0.0, altitude=0, timeSec=0, destinationId=BROADCAST_ADDR, wantAck=False, wantResponse=False):
"""
Send a position packet to some other node (normally a broadcast)
Also, the device software will notice this packet and use it to automatically set its notion of
the local position.
If timeSec is not specified (recommended), we will use the local machine time.
Returns the sent packet. The id field will be populated in this packet and can be used to track future message acks/naks.
"""
p = mesh_pb2.Position()
if(latitude != 0.0):
p.latitude_i = int(latitude / 1e-7)
if(longitude != 0.0):
p.longitude_i = int(longitude / 1e-7)
if(altitude != 0):
p.altitude = int(altitude)
if timeSec == 0:
timeSec = time.time() # returns unix timestamp in seconds
p.time = int(timeSec)
return self.sendData(p, destinationId,
portNum=portnums_pb2.PortNum.POSITION_APP,
wantAck=wantAck,
wantResponse=wantResponse)
def _addResponseHandler(self, requestId, callback):
self.responseHandlers[requestId] = ResponseHandler(callback)
def _sendPacket(self, meshPacket,
destinationId=BROADCAST_ADDR,
wantAck=False, hopLimit=defaultHopLimit):
"""Send a MeshPacket to the specified node (or if unspecified, broadcast).
You probably don't want this - use sendData instead.
Returns the sent packet. The id field will be populated in this packet and
can be used to track future message acks/naks.
"""
# We allow users to talk to the local node before we've completed the full connection flow...
if(self.myInfo is not None and destinationId != self.myInfo.my_node_num):
self._waitConnected()
toRadio = mesh_pb2.ToRadio()
if destinationId is None:
raise Exception("destinationId must not be None")
elif isinstance(destinationId, int):
nodeNum = destinationId
elif destinationId == BROADCAST_ADDR:
nodeNum = BROADCAST_NUM
elif destinationId == LOCAL_ADDR:
nodeNum = self.myInfo.my_node_num
# A simple hex style nodeid - we can parse this without needing the DB
elif destinationId.startswith("!"):
nodeNum = int(destinationId[1:], 16)
else:
node = self.nodes.get(destinationId)
if not node:
raise Exception(f"NodeId {destinationId} not found in DB")
nodeNum = node['num']
meshPacket.to = nodeNum
meshPacket.want_ack = wantAck
meshPacket.hop_limit = hopLimit
# if the user hasn't set an ID for this packet (likely and recommended), we should pick a new unique ID
# so the message can be tracked.
if meshPacket.id == 0:
meshPacket.id = self._generatePacketId()
toRadio.packet.CopyFrom(meshPacket)
#logging.debug(f"Sending packet: {stripnl(meshPacket)}")
self._sendToRadio(toRadio)
return meshPacket
def waitForConfig(self):
"""Block until radio config is received. Returns True if config has been received."""
success = self._timeout.waitForSet(self, attrs=('myInfo', 'nodes')
) and self.localNode.waitForConfig()
if not success:
raise Exception("Timed out waiting for interface config")
def getMyNodeInfo(self):
if self.myInfo is None:
return None
return self.nodesByNum.get(self.myInfo.my_node_num)
def getMyUser(self):
nodeInfo = self.getMyNodeInfo()
if nodeInfo is not None:
return nodeInfo.get('user')
return None
def getLongName(self):
user = self.getMyUser()
if user is not None:
return user.get('longName', None)
return None
def getShortName(self):
user = self.getMyUser()
if user is not None:
return user.get('shortName', None)
return None
def _waitConnected(self):
"""Block until the initial node db download is complete, or timeout
and raise an exception"""
if not self.isConnected.wait(10.0): # timeout after 10 seconds
raise Exception("Timed out waiting for connection completion")
# If we failed while connecting, raise the connection to the client
if self.failure:
raise self.failure
def _generatePacketId(self):
"""Get a new unique packet ID"""
if self.currentPacketId is None:
raise Exception("Not connected yet, can not generate packet")
else:
self.currentPacketId = (self.currentPacketId + 1) & 0xffffffff
return self.currentPacketId
def _disconnected(self):
"""Called by subclasses to tell clients this interface has disconnected"""
self.isConnected.clear()
publishingThread.queueWork(lambda: pub.sendMessage(
"meshtastic.connection.lost", interface=self))
def _startHeartbeat(self):
"""We need to send a heartbeat message to the device every X seconds"""
def callback():
self.heartbeatTimer = None
prefs = self.localNode.radioConfig.preferences
i = prefs.phone_timeout_secs / 2
logging.debug(f"Sending heartbeat, interval {i}")
if i != 0:
self.heartbeatTimer = threading.Timer(i, callback)
self.heartbeatTimer.start()
p = mesh_pb2.ToRadio()
self._sendToRadio(p)
callback() # run our periodic callback now, it will make another timer if necessary
def _connected(self):
"""Called by this class to tell clients we are now fully connected to a node
"""
# (because I'm lazy) _connected might be called when remote Node
# objects complete their config reads, don't generate redundant isConnected
# for the local interface
if not self.isConnected.is_set():
self.isConnected.set()
self._startHeartbeat()
publishingThread.queueWork(lambda: pub.sendMessage(
"meshtastic.connection.established", interface=self))
def _startConfig(self):
"""Start device packets flowing"""
self.myInfo = None
self.nodes = {} # nodes keyed by ID
self.nodesByNum = {} # nodes keyed by nodenum
startConfig = mesh_pb2.ToRadio()
self.configId = random.randint(0, 0xffffffff)
startConfig.want_config_id = self.configId
self._sendToRadio(startConfig)
def _sendDisconnect(self):
"""Tell device we are done using it"""
m = mesh_pb2.ToRadio()
m.disconnect = True
self._sendToRadio(m)
def _sendToRadio(self, toRadio):
"""Send a ToRadio protobuf to the device"""
if self.noProto:
logging.warn(
f"Not sending packet because protocol use is disabled by noProto")
else:
#logging.debug(f"Sending toRadio: {stripnl(toRadio)}")
self._sendToRadioImpl(toRadio)
def _sendToRadioImpl(self, toRadio):
"""Send a ToRadio protobuf to the device"""
logging.error(f"Subclass must provide toradio: {toRadio}")
def _handleConfigComplete(self):
"""
Done with initial config messages, now send regular MeshPackets to ask for settings and channels
"""
self.localNode.requestConfig()
def _handleFromRadio(self, fromRadioBytes):
"""
Handle a packet that arrived from the radio(update model and publish events)
Called by subclasses."""
fromRadio = mesh_pb2.FromRadio()
fromRadio.ParseFromString(fromRadioBytes)
asDict = google.protobuf.json_format.MessageToDict(fromRadio)
#logging.debug(f"Received from radio: {fromRadio}")
if fromRadio.HasField("my_info"):
self.myInfo = fromRadio.my_info
self.localNode.nodeNum = self.myInfo.my_node_num
logging.debug(f"Received myinfo: {stripnl(fromRadio.my_info)}")
failmsg = None
# Check for app too old
if self.myInfo.min_app_version > OUR_APP_VERSION:
failmsg = "This device needs a newer python client, please \"pip install --upgrade meshtastic\". For more information see https://tinyurl.com/5bjsxu32"
# check for firmware too old
if self.myInfo.max_channels == 0:
failmsg = "This version of meshtastic-python requires device firmware version 1.2 or later. For more information see https://tinyurl.com/5bjsxu32"
if failmsg:
self.failure = Exception(failmsg)
self.isConnected.set() # let waitConnected return this exception
self.close()
elif fromRadio.HasField("node_info"):
node = asDict["nodeInfo"]
try:
self._fixupPosition(node["position"])
except:
logging.debug("Node without position")
logging.debug(f"Received nodeinfo: {node}")
self.nodesByNum[node["num"]] = node
if "user" in node: # Some nodes might not have user/ids assigned yet
self.nodes[node["user"]["id"]] = node
publishingThread.queueWork(lambda: pub.sendMessage("meshtastic.node.updated",
node=node, interface=self))
elif fromRadio.config_complete_id == self.configId:
# we ignore the config_complete_id, it is unneeded for our stream API fromRadio.config_complete_id
logging.debug(f"Config complete ID {self.configId}")
self._handleConfigComplete()
elif fromRadio.HasField("packet"):
self._handlePacketFromRadio(fromRadio.packet)
elif fromRadio.rebooted:
# Tell clients the device went away. Careful not to call the overridden subclass version that closes the serial port
MeshInterface._disconnected(self)
self._startConfig() # redownload the node db etc...
else:
logging.debug("Unexpected FromRadio payload")
def _fixupPosition(self, position):
"""Convert integer lat/lon into floats
Arguments:
position {Position dictionary} -- object ot fix up
"""
if "latitudeI" in position:
position["latitude"] = position["latitudeI"] * 1e-7
if "longitudeI" in position:
position["longitude"] = position["longitudeI"] * 1e-7
def _nodeNumToId(self, num):
"""Map a node node number to a node ID
Arguments:
num {int} -- Node number
Returns:
string -- Node ID
"""
if num == BROADCAST_NUM:
return BROADCAST_ADDR
try:
return self.nodesByNum[num]["user"]["id"]
except:
logging.debug(f"Node {num} not found for fromId")
return None
def _getOrCreateByNum(self, nodeNum):
"""Given a nodenum find the NodeInfo in the DB (or create if necessary)"""
if nodeNum == BROADCAST_NUM:
raise Exception("Can not create/find nodenum by the broadcast num")
if nodeNum in self.nodesByNum:
return self.nodesByNum[nodeNum]
else:
n = {"num": nodeNum} # Create a minimial node db entry
self.nodesByNum[nodeNum] = n
return n
def _handlePacketFromRadio(self, meshPacket):
"""Handle a MeshPacket that just arrived from the radio
Will publish one of the following events:
- meshtastic.receive.text(packet = MeshPacket dictionary)
- meshtastic.receive.position(packet = MeshPacket dictionary)
- meshtastic.receive.user(packet = MeshPacket dictionary)
- meshtastic.receive.data(packet = MeshPacket dictionary)
"""
asDict = google.protobuf.json_format.MessageToDict(meshPacket)
# We normally decompose the payload into a dictionary so that the client
# doesn't need to understand protobufs. But advanced clients might
# want the raw protobuf, so we provide it in "raw"
asDict["raw"] = meshPacket
# from might be missing if the nodenum was zero.
if not "from" in asDict:
asDict["from"] = 0
logging.error(
f"Device returned a packet we sent, ignoring: {stripnl(asDict)}")
return
if not "to" in asDict:
asDict["to"] = 0
# /add fromId and toId fields based on the node ID
try:
asDict["fromId"] = self._nodeNumToId(asDict["from"])
except Exception as ex:
logging.warn(f"Not populating fromId {ex}")
try:
asDict["toId"] = self._nodeNumToId(asDict["to"])
except Exception as ex:
logging.warn(f"Not populating toId {ex}")
# We could provide our objects as DotMaps - which work with . notation or as dictionaries
# asObj = DotMap(asDict)
topic = "meshtastic.receive" # Generic unknown packet type
decoded = asDict["decoded"]
# The default MessageToDict converts byte arrays into base64 strings.
# We don't want that - it messes up data payload. So slam in the correct
# byte array.
decoded["payload"] = meshPacket.decoded.payload
# UNKNOWN_APP is the default protobuf portnum value, and therefore if not set it will not be populated at all
# to make API usage easier, set it to prevent confusion
if not "portnum" in decoded:
decoded["portnum"] = portnums_pb2.PortNum.Name(
portnums_pb2.PortNum.UNKNOWN_APP)
portnum = decoded["portnum"]
topic = f"meshtastic.receive.data.{portnum}"
# decode position protobufs and update nodedb, provide decoded version as "position" in the published msg
# move the following into a 'decoders' API that clients could register?
portNumInt = meshPacket.decoded.portnum # we want portnum as an int
handler = protocols.get(portNumInt)
# The decoded protobuf as a dictionary (if we understand this message)
p = None
if handler is not None:
topic = f"meshtastic.receive.{handler.name}"
# Convert to protobuf if possible
if handler.protobufFactory is not None:
pb = handler.protobufFactory()
pb.ParseFromString(meshPacket.decoded.payload)
p = google.protobuf.json_format.MessageToDict(pb)
asDict["decoded"][handler.name] = p
# Also provide the protobuf raw
asDict["decoded"][handler.name]["raw"] = pb
# Call specialized onReceive if necessary
if handler.onReceive is not None:
handler.onReceive(self, asDict)
# Is this message in response to a request, if so, look for a handler
requestId = decoded.get("requestId")
if requestId is not None:
# We ignore ACK packets, but send NAKs and data responses to the handlers
routing = decoded.get("routing")
isAck = routing is not None and ("errorReason" not in routing)
if not isAck:
# we keep the responseHandler in dict until we get a non ack
handler = self.responseHandlers.pop(requestId, None)
if handler is not None:
handler.callback(asDict)
logging.debug(f"Publishing {topic}: packet={stripnl(asDict)} ")
publishingThread.queueWork(lambda: pub.sendMessage(
topic, packet=asDict, interface=self))
# Our standard BLE characteristics
TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7"
FROMRADIO_UUID = "8ba2bcc2-ee02-4a55-a531-c525c5e454d5"
FROMNUM_UUID = "ed9da18c-a800-4f66-a670-aa7547e34453"
class BLEInterface(MeshInterface):
"""A not quite ready - FIXME - BLE interface to devices"""
def __init__(self, address, debugOut=None):
self.address = address
self.adapter = pygatt.GATTToolBackend() # BGAPIBackend()
self.adapter.start()
logging.debug(f"Connecting to {self.address}")
self.device = self.adapter.connect(address)
logging.debug("Connected to device")
# fromradio = self.device.char_read(FROMRADIO_UUID)
MeshInterface.__init__(self, debugOut=debugOut)
self._readFromRadio() # read the initial responses
def handle_data(handle, data):
self._handleFromRadio(data)
self.device.subscribe(FROMNUM_UUID, callback=handle_data)
def _sendToRadioImpl(self, toRadio):
"""Send a ToRadio protobuf to the device"""
#logging.debug(f"Sending: {stripnl(toRadio)}")
b = toRadio.SerializeToString()
self.device.char_write(TORADIO_UUID, b)
def close(self):
MeshInterface.close(self)
self.adapter.stop()
def _readFromRadio(self):
wasEmpty = False
while not wasEmpty:
b = self.device.char_read(FROMRADIO_UUID)
wasEmpty = len(b) == 0
if not wasEmpty:
self._handleFromRadio(b)
class StreamInterface(MeshInterface):
"""Interface class for meshtastic devices over a stream link (serial, TCP, etc)"""
def __init__(self, debugOut=None, noProto=False, connectNow=True):
"""Constructor, opens a connection to self.stream
Keyword Arguments:
devPath {string} -- A filepath to a device, i.e. /dev/ttyUSB0 (default: {None})
debugOut {stream} -- If a stream is provided, any debug serial output from the device will be emitted to that stream. (default: {None})
Raises:
Exception: [description]
Exception: [description]
"""
if not hasattr(self, 'stream'):
raise Exception(
"StreamInterface is now abstract (to update existing code create SerialInterface instead)")
self._rxBuf = bytes() # empty
self._wantExit = False
# FIXME, figure out why daemon=True causes reader thread to exit too early
self._rxThread = threading.Thread(
target=self.__reader, args=(), daemon=True)
MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto)
# Start the reader thread after superclass constructor completes init
if connectNow:
self.connect()
if not noProto:
self.waitForConfig()
def connect(self):
"""Connect to our radio
Normally this is called automatically by the constructor, but if you passed in connectNow=False you can manually
start the reading thread later.
"""
# Send some bogus UART characters to force a sleeping device to wake, and if the reading statemachine was parsing a bad packet make sure
# we write enought start bytes to force it to resync (we don't use START1 because we want to ensure it is looking for START1)
p = bytearray([START2] * 32)
self._writeBytes(p)
time.sleep(0.1) # wait 100ms to give device time to start running
self._rxThread.start()
self._startConfig()
if not self.noProto: # Wait for the db download if using the protocol
self._waitConnected()
def _disconnected(self):
"""We override the superclass implementation to close our port"""
MeshInterface._disconnected(self)
logging.debug("Closing our port")
if not self.stream is None:
self.stream.close()
self.stream = None
def _writeBytes(self, b):
"""Write an array of bytes to our stream and flush"""
if self.stream: # ignore writes when stream is closed
self.stream.write(b)
self.stream.flush()
def _readBytes(self, len):
"""Read an array of bytes from our stream"""
return self.stream.read(len)
def _sendToRadioImpl(self, toRadio):
"""Send a ToRadio protobuf to the device"""
logging.debug(f"Sending: {stripnl(toRadio)}")
b = toRadio.SerializeToString()
bufLen = len(b)
# We convert into a string, because the TCP code doesn't work with byte arrays
header = bytes([START1, START2, (bufLen >> 8) & 0xff, bufLen & 0xff])
self._writeBytes(header + b)
def close(self):
"""Close a connection to the device"""
logging.debug("Closing stream")
MeshInterface.close(self)
# pyserial cancel_read doesn't seem to work, therefore we ask the reader thread to close things for us
self._wantExit = True
if self._rxThread != threading.current_thread():
self._rxThread.join() # wait for it to exit
def __reader(self):
"""The reader thread that reads bytes from our stream"""
empty = bytes()
try:
while not self._wantExit:
# logging.debug("reading character")
b = self._readBytes(1)
# logging.debug("In reader loop")
# logging.debug(f"read returned {b}")
if len(b) > 0:
c = b[0]
ptr = len(self._rxBuf)
# Assume we want to append this byte, fixme use bytearray instead
self._rxBuf = self._rxBuf + b
if ptr == 0: # looking for START1
if c != START1:
self._rxBuf = empty # failed to find start
if self.debugOut != None:
try:
self.debugOut.write(b.decode("utf-8"))
except:
self.debugOut.write('?')
elif ptr == 1: # looking for START2
if c != START2:
self._rxBuf = empty # failed to find start2
elif ptr >= HEADER_LEN - 1: # we've at least got a header
# big endian length follos header
packetlen = (self._rxBuf[2] << 8) + self._rxBuf[3]
if ptr == HEADER_LEN - 1: # we _just_ finished reading the header, validate length
if packetlen > MAX_TO_FROM_RADIO_SIZE:
self._rxBuf = empty # length ws out out bounds, restart
if len(self._rxBuf) != 0 and ptr + 1 >= packetlen + HEADER_LEN:
try:
self._handleFromRadio(self._rxBuf[HEADER_LEN:])
except Exception as ex:
logging.error(
f"Error while handling message from radio {ex}")
traceback.print_exc()
self._rxBuf = empty
else:
# logging.debug(f"timeout")
pass
except serial.SerialException as ex:
if not self._wantExit: # We might intentionally get an exception during shutdown
logging.warn(
f"Meshtastic serial port disconnected, disconnecting... {ex}")
except OSError as ex:
if not self._wantExit: # We might intentionally get an exception during shutdown
logging.error(
f"Unexpected OSError, terminating meshtastic reader... {ex}")
except Exception as ex:
logging.error(
f"Unexpected exception, terminating meshtastic reader... {ex}")
finally:
logging.debug("reader is exiting")
self._disconnected()
class SerialInterface(StreamInterface):
"""Interface class for meshtastic devices over a serial link"""
def __init__(self, devPath=None, debugOut=None, noProto=False, connectNow=True):
"""Constructor, opens a connection to a specified serial port, or if unspecified try to
find one Meshtastic device by probing
Keyword Arguments:
devPath {string} -- A filepath to a device, i.e. /dev/ttyUSB0 (default: {None})
debugOut {stream} -- If a stream is provided, any debug serial output from the device will be emitted to that stream. (default: {None})
"""
if devPath is None:
ports = util.findPorts()
if len(ports) == 0:
raise Exception("No Meshtastic devices detected")
elif len(ports) > 1:
raise Exception(
f"Multiple ports detected, you must specify a device, such as {ports[0]}")
else:
devPath = ports[0]
logging.debug(f"Connecting to {devPath}")
# Note: we provide None for port here, because we will be opening it later
self.stream = serial.Serial(
None, 921600, exclusive=True, timeout=0.5, write_timeout=0)
# rts=False Needed to prevent TBEAMs resetting on OSX, because rts is connected to reset
self.stream.port = devPath
# HACK: If the platform driving the serial port is unable to leave the RTS pin in high-impedance
# 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__(
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow)
"""true if platform driving the serial port is Windows Subsystem for Linux 1."""
def _isWsl1(self):
# WSL1 identifies itself as Linux, but has a special char device at /dev/lxss for use with session control,
# e.g. /init. We should treat WSL1 as Windows for the RTS-driving hack because the underlying platfrom
# serial driver for the CP21xx still exhibits the buggy behavior.
# 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();
class TCPInterface(StreamInterface):
"""Interface class for meshtastic devices over a TCP link"""
def __init__(self, hostname: AnyStr, debugOut=None, noProto=False, connectNow=True, portNumber=4403):
"""Constructor, opens a connection to a specified IP address/hostname
Keyword Arguments:
hostname {string} -- Hostname/IP address of the device to connect to
"""
logging.debug(f"Connecting to {hostname}")
server_address = (hostname, portNumber)
sock = socket.create_connection(server_address)
# Instead of wrapping as a stream, we use the native socket API
# self.stream = sock.makefile('rw')
self.stream = None
self.socket = sock
StreamInterface.__init__(
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow)
def close(self):
"""Close a connection to the device"""
logging.debug("Closing TCP stream")
StreamInterface.close(self)
# Sometimes the socket read might be blocked in the reader thread. Therefore we force the shutdown by closing
# the socket here
self._wantExit = True
if not self.socket is None:
try:
self.socket.shutdown(socket.SHUT_RDWR)
except:
pass # Ignore errors in shutdown, because we might have a race with the server
self.socket.close()
def _writeBytes(self, b):
"""Write an array of bytes to our stream and flush"""
self.socket.send(b)
def _readBytes(self, len):
"""Read an array of bytes from our stream"""
return self.socket.recv(len)
def _onTextReceive(iface, asDict):
"""Special text auto parsing for received messages"""
# We don't throw if the utf8 is invalid in the text message. Instead we just don't populate

View File

@@ -1,36 +1,30 @@
#!python3
""" Main Meshtastic
"""
import argparse
import platform
import logging
import sys
import codecs
import time
import base64
import os
from . import SerialInterface, TCPInterface, BLEInterface, test, remote_hardware
import yaml
from pubsub import pub
from . import mesh_pb2, portnums_pb2, channel_pb2
from .util import stripnl
import google.protobuf.json_format
import pyqrcode
import traceback
import pkg_resources
import meshtastic.util
import meshtastic.test
from . import remote_hardware
from . import portnums_pb2, channel_pb2, radioconfig_pb2
from .globals import Globals
"""We only import the tunnel code if we are on a platform that can run it"""
"""We only import the tunnel code if we are on a platform that can run it. """
have_tunnel = platform.system() == 'Linux'
"""The command line arguments"""
args = None
"""The parser for arguments"""
parser = argparse.ArgumentParser()
channelIndex = 0
def onReceive(packet, interface):
"""Callback invoked when a packet arrives"""
our_globals = Globals.getInstance()
args = our_globals.get_args()
try:
d = packet.get('decoded')
@@ -60,81 +54,26 @@ def onConnection(interface, topic=pub.AUTO_TOPIC):
print(f"Connection changed: {topic.getName()}")
trueTerms = {"t", "true", "yes"}
falseTerms = {"f", "false", "no"}
def genPSK256():
return os.urandom(32)
def fromPSK(valstr):
"""A special version of fromStr that assumes the user is trying to set a PSK.
In that case we also allow "none", "default" or "random" (to have python generate one), or simpleN
"""
if valstr == "random":
return genPSK256()
elif valstr == "none":
return bytes([0]) # Use the 'no encryption' PSK
elif valstr == "default":
return bytes([1]) # Use default channel psk
elif valstr.startswith("simple"):
# Use one of the single byte encodings
return bytes([int(valstr[6:]) + 1])
else:
return fromStr(valstr)
def fromStr(valstr):
"""try to parse as int, float or bool (and fallback to a string as last resort)
Returns: an int, bool, float, str or byte array (for strings of hex digits)
Args:
valstr (string): A user provided string
"""
if(len(valstr) == 0): # Treat an emptystring as an empty bytes
val = bytes()
elif(valstr.startswith('0x')):
# if needed convert to string with asBytes.decode('utf-8')
val = bytes.fromhex(valstr[2:])
elif valstr in trueTerms:
val = True
elif valstr in falseTerms:
val = False
else:
try:
val = int(valstr)
except ValueError:
try:
val = float(valstr)
except ValueError:
val = valstr # Not a float or an int, assume string
return val
never = 0xffffffff
oneday = 24 * 60 * 60
def getPref(attributes, name):
"""Get a channel or preferences value"""
objDesc = attributes.DESCRIPTOR
field = objDesc.fields_by_name.get(name)
if not field:
print(f"{attributes.__class__.__name__} doesn't have an attribute called {name}, so you can not get it.")
print(f"Choices are:")
print(f"{attributes.__class__.__name__} does not have an attribute called {name}, so you can not get it.")
print(f"Choices in sorted order are:")
names = []
for f in objDesc.fields:
print(f" {f.name}")
names.append(f'{f.name}')
for temp_name in sorted(names):
print(f" {temp_name}")
return
# okay - try to read the value
try:
try:
val = getattr(attributes, name)
except TypeError as ex:
except TypeError:
# The getter didn't like our arg type guess try again as a string
val = getattr(attributes, name)
@@ -150,32 +89,39 @@ def setPref(attributes, name, valStr):
objDesc = attributes.DESCRIPTOR
field = objDesc.fields_by_name.get(name)
if not field:
print(f"{attributes.__class__.__name__} doesn't have an attribute called {name}, so you can not set it.")
print(f"Choices are:")
print(f"{attributes.__class__.__name__} does not have an attribute called {name}, so you can not set it.")
print(f"Choices in sorted order are:")
names = []
for f in objDesc.fields:
print(f" {f.name}")
names.append(f'{f.name}')
for temp_name in sorted(names):
print(f" {temp_name}")
return
val = fromStr(valStr)
val = meshtastic.util.fromStr(valStr)
enumType = field.enum_type
# pylint: disable=C0123
if enumType and type(val) == str:
# We've failed so far to convert this string into an enum, try to find it by reflection
e = enumType.values_by_name.get(val)
if e:
val = e.number
else:
print(f"{name} doesn't have an enum called {val}, so you can not set it.")
print(f"Choices are:")
print(f"{name} does not have an enum called {val}, so you can not set it.")
print(f"Choices in sorted order are:")
names = []
for f in enumType.values:
print(f" {f.name}")
names.append(f'{f.name}')
for temp_name in sorted(names):
print(f" {temp_name}")
return
# okay - try to read the value
try:
try:
setattr(attributes, name, val)
except TypeError as ex:
except TypeError:
# The setter didn't like our arg type guess try again as a string
setattr(attributes, name, valStr)
@@ -185,21 +131,21 @@ def setPref(attributes, name, valStr):
print(f"Can't set {name} due to {ex}")
targetNode = None
def onConnected(interface):
"""Callback invoked when we connect to a radio"""
closeNow = False # Should we drop the connection after we finish?
try:
global args
our_globals = Globals.getInstance()
args = our_globals.get_args()
print("Connected to radio")
def getNode():
"""This operation could be expensive, so we try to cache the results"""
global targetNode
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:
@@ -208,7 +154,6 @@ def onConnected(interface):
alt = 0
lat = 0.0
lon = 0.0
time = 0 # always set time, but based on the local clock
prefs = interface.localNode.radioConfig.preferences
if args.setalt:
alt = int(args.setalt)
@@ -225,7 +170,7 @@ def onConnected(interface):
print("Setting device position")
# can include lat/long/alt etc: latitude = 37.5, longitude = -122.1
interface.sendPosition(lat, lon, alt, time)
interface.sendPosition(lat, lon, alt)
interface.localNode.writeConfig()
elif not args.no_time:
# We normally provide a current time to the mesh when we connect
@@ -236,16 +181,58 @@ def onConnected(interface):
print(f"Setting device owner to {args.set_owner}")
getNode().setOwner(args.set_owner)
if args.pos_fields:
# If --pos-fields invoked with args, set position fields
closeNow = True
prefs = getNode().radioConfig.preferences
allFields = 0
try:
for field in args.pos_fields:
v_field = radioconfig_pb2.PositionFlags.Value(field)
allFields |= v_field
except ValueError:
print("ERROR: supported position fields are:")
print(radioconfig_pb2.PositionFlags.keys())
print("If no fields are specified, will read and display current value.")
else:
print(f"Setting position fields to {allFields}")
setPref(prefs, 'position_flags', ('%d' % allFields))
print("Writing modified preferences to device")
getNode().writeConfig()
elif args.pos_fields is not None:
# If --pos-fields invoked without args, read and display current value
closeNow = True
prefs = getNode().radioConfig.preferences
fieldNames = []
for bit in radioconfig_pb2.PositionFlags.values():
if prefs.position_flags & bit:
fieldNames.append(radioconfig_pb2.PositionFlags.Name(bit))
print(' '.join(fieldNames))
if args.set_team:
closeNow = True
try:
v_team = meshtastic.mesh_pb2.Team.Value(args.set_team.upper())
except ValueError:
v_team = 0
print(f"ERROR: Team \'{args.set_team}\' not found.")
print("Try a team name from the sorted list below, or use 'CLEAR' for unaffiliated:")
print(sorted(meshtastic.mesh_pb2.Team.keys()))
else:
print(f"Setting team to {meshtastic.mesh_pb2.Team.Name(v_team)}")
getNode().setOwner(team=v_team)
if args.set_ham:
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)
# Must turn off crypt on primary channel
ch = getNode().channels[0]
ch.settings.psk = fromPSK("none")
print(f"Writing modified channels to device")
getNode().writeChannel(0)
# Must turn off encryption on primary channel
getNode().turnOffEncryptionOnPrimaryChannel()
if args.reboot:
closeNow = True
@@ -301,12 +288,52 @@ def onConnected(interface):
# Handle the int/float/bool arguments
for pref in args.set:
setPref(
prefs, pref[0], pref[1])
setPref(prefs, pref[0], pref[1])
print("Writing modified preferences to device")
getNode().writeConfig()
if args.configure:
with open(args.configure[0], encoding='utf8') as file:
configuration = yaml.safe_load(file)
closeNow = True
if 'owner' in configuration:
print(f"Setting device owner to {configuration['owner']}")
getNode().setOwner(configuration['owner'])
if 'channel_url' in configuration:
print("Setting channel url to", configuration['channel_url'])
getNode().setURL(configuration['channel_url'])
if 'location' in configuration:
alt = 0
lat = 0.0
lon = 0.0
prefs = interface.localNode.radioConfig.preferences
if 'alt' in configuration['location']:
alt = int(configuration['location']['alt'])
prefs.fixed_position = True
print(f"Fixing altitude at {alt} meters")
if 'lat' in configuration['location']:
lat = float(configuration['location']['lat'])
prefs.fixed_position = True
print(f"Fixing latitude at {lat} degrees")
if 'lon' in configuration['location']:
lon = float(configuration['location']['lon'])
prefs.fixed_position = True
print(f"Fixing longitude at {lon} degrees")
print("Setting device position")
interface.sendPosition(lat, lon, alt)
interface.localNode.writeConfig()
if 'user_prefs' in configuration:
prefs = getNode().radioConfig.preferences
for pref in configuration['user_prefs']:
setPref(prefs, pref, str(configuration['user_prefs'][pref]))
print("Writing modified preferences to device")
getNode().writeConfig()
if args.seturl:
closeNow = True
@@ -316,17 +343,19 @@ def onConnected(interface):
if args.ch_add:
closeNow = True
if len(args.ch_add) > 10:
meshtastic.util.our_exit("Warning: Channel name must be shorter. Channel not added.")
n = getNode()
ch = n.getChannelByName(args.ch_add)
if ch:
logging.error(
f"This node already has a '{args.ch_add}' channel - no changes.")
meshtastic.util.our_exit(f"Warning: This node already has a '{args.ch_add}' channel. No changes were made.")
else:
# get the first channel that is disabled (i.e., available)
ch = n.getDisabledChannel()
if not ch:
raise Exception("No free channels were found")
meshtastic.util.our_exit("Warning: No free channels were found")
chs = channel_pb2.ChannelSettings()
chs.psk = genPSK256()
chs.psk = meshtastic.util.genPSK256()
chs.name = args.ch_add
ch.settings.CopyFrom(chs)
ch.role = channel_pb2.Channel.Role.SECONDARY
@@ -336,22 +365,46 @@ def onConnected(interface):
if args.ch_del:
closeNow = True
print(f"Deleting channel {channelIndex}")
ch = getNode().deleteChannel(channelIndex)
channelIndex = our_globals.get_channel_index()
if channelIndex is None:
meshtastic.util.our_exit("Warning: Need to specify '--ch-index' for '--ch-del'.", 1)
else:
if channelIndex == 0:
meshtastic.util.our_exit("Warning: Cannot delete primary channel.", 1)
else:
print(f"Deleting channel {channelIndex}")
ch = getNode().deleteChannel(channelIndex)
if args.ch_set or args.ch_longslow or args.ch_shortfast:
ch_changes = [args.ch_longslow, args.ch_longfast,
args.ch_mediumslow, args.ch_mediumfast,
args.ch_shortslow, args.ch_shortfast]
any_primary_channel_changes = any(x for x in ch_changes)
if args.ch_set or any_primary_channel_changes or args.ch_enable or args.ch_disable:
closeNow = True
channelIndex = our_globals.get_channel_index()
if channelIndex is None:
if any_primary_channel_changes:
# we assume that they want the primary channel if they're setting range values
channelIndex = 0
else:
meshtastic.util.our_exit("Warning: Need to specify '--ch-index'.", 1)
ch = getNode().channels[channelIndex]
enable = args.ch_enable # should we enable this channel?
if any_primary_channel_changes or args.ch_enable or args.ch_disable:
if channelIndex == 0 and not any_primary_channel_changes:
meshtastic.util.our_exit("Warning: Cannot enable/disable PRIMARY channel.")
if args.ch_longslow or args.ch_shortfast:
if channelIndex != 0:
raise Exception(
"standard channel settings can only be applied to the PRIMARY channel")
if any_primary_channel_changes:
meshtastic.util.our_exit("Warning: Standard channel settings can only be applied to the PRIMARY channel")
enable = True # force enable
enable = True # default to enable
if args.ch_enable:
enable = True
if args.ch_disable:
enable = False
def setSimpleChannel(modem_config):
"""Set one of the simple modem_config only based channels"""
@@ -365,17 +418,27 @@ def onConnected(interface):
# handle the simple channel set commands
if args.ch_longslow:
setSimpleChannel(
channel_pb2.ChannelSettings.ModemConfig.Bw125Cr48Sf4096)
setSimpleChannel(channel_pb2.ChannelSettings.ModemConfig.Bw125Cr48Sf4096)
if args.ch_longfast:
setSimpleChannel(channel_pb2.ChannelSettings.ModemConfig.Bw31_25Cr48Sf512)
if args.ch_mediumslow:
setSimpleChannel(channel_pb2.ChannelSettings.ModemConfig.Bw250Cr46Sf2048)
if args.ch_mediumfast:
setSimpleChannel(channel_pb2.ChannelSettings.ModemConfig.Bw250Cr47Sf1024)
if args.ch_shortslow:
setSimpleChannel(channel_pb2.ChannelSettings.ModemConfig.Bw125Cr45Sf128)
if args.ch_shortfast:
setSimpleChannel(
channel_pb2.ChannelSettings.ModemConfig.Bw500Cr45Sf128)
setSimpleChannel(channel_pb2.ChannelSettings.ModemConfig.Bw500Cr45Sf128)
# Handle the channel settings
for pref in (args.ch_set or []):
if pref[0] == "psk":
ch.settings.psk = fromPSK(pref[1])
ch.settings.psk = meshtastic.util.fromPSK(pref[1])
else:
setPref(ch.settings, pref[0], pref[1])
enable = True # If we set any pref, assume the user wants to enable the channel
@@ -405,8 +468,7 @@ def onConnected(interface):
# Handle the int/float/bool arguments
for pref in args.get:
getPref(
prefs, pref[0])
getPref(prefs, pref[0])
print("Completed getting preferences")
@@ -422,6 +484,7 @@ def onConnected(interface):
print(qr.terminal())
if have_tunnel and args.tunnel:
# pylint: disable=C0415
from . import tunnel
# Even if others said we could close, stay open if the user asked for a tunnel
closeNow = False
@@ -454,16 +517,22 @@ def subscribe():
def common():
"""Shared code for all of our command line wrappers"""
global args
our_globals = Globals.getInstance()
args = our_globals.get_args()
parser = our_globals.get_parser()
logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO)
if len(sys.argv) == 1:
parser.print_help(sys.stderr)
sys.exit(1)
meshtastic.util.our_exit("", 1)
else:
if args.support:
meshtastic.util.support_info()
meshtastic.util.our_exit("", 0)
if args.ch_index is not None:
global channelIndex
channelIndex = int(args.ch_index)
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:
@@ -479,13 +548,17 @@ def common():
else:
args.seriallog = "none" # assume no debug output in this case
if args.deprecated != None:
if args.deprecated is not None:
logging.error(
'This option has been deprecated, see help below for the correct replacement...')
parser.print_help(sys.stderr)
sys.exit(1)
meshtastic.util.our_exit('', 1)
elif args.test:
test.testAll()
result = meshtastic.test.testAll()
if not result:
meshtastic.util.our_exit("Warning: Test was not successful.")
else:
meshtastic.util.our_exit("Test was a success.", 0)
else:
if args.seriallog == "stdout":
logfile = sys.stdout
@@ -495,23 +568,25 @@ def common():
logfile = None
else:
logging.info(f"Logging serial output to {args.seriallog}")
# Note: using "line buffering"
# pylint: disable=R1732
logfile = open(args.seriallog, 'w+',
buffering=1) # line buffering
buffering=1, encoding='utf8')
subscribe()
if args.ble:
client = BLEInterface(args.ble, debugOut=logfile)
client = meshtastic.ble_interface.BLEInterface(args.ble, debugOut=logfile, noProto=args.noproto)
elif args.host:
client = TCPInterface(
client = meshtastic.tcp_interface.TCPInterface(
args.host, debugOut=logfile, noProto=args.noproto)
else:
client = SerialInterface(
client = meshtastic.serial_interface.SerialInterface(
args.port, debugOut=logfile, noProto=args.noproto)
# We assume client is fully connected now
onConnected(client)
if args.noproto: # loop until someone presses ctrlc
if args.noproto or (have_tunnel and args.tunnel): # loop until someone presses ctrlc
while True:
time.sleep(1000)
@@ -520,7 +595,15 @@ def common():
def initParser():
global parser, args
"""Initialize the command line argument parsing."""
our_globals = Globals.getInstance()
parser = our_globals.get_parser()
args = our_globals.get_args()
parser.add_argument(
"--configure",
help="Specify a path to a yaml(.yml) file containing the desired settings for the connected device.",
action='append')
parser.add_argument(
"--port",
@@ -546,7 +629,7 @@ def initParser():
action="store_true")
parser.add_argument(
"--get", help="Get a preferences field", nargs=1, action='append')
"--get", help="Get a preferences field. Use an invalid field such as '0' to get a list of all fields.", nargs=1, action='append')
parser.add_argument(
"--set", help="Set a preferences field", nargs=2, action='append')
@@ -564,23 +647,40 @@ def initParser():
"--ch-del", help="Delete the ch-index channel", action='store_true')
parser.add_argument(
"--ch-enable", help="Enable the specified channel", action="store_true", dest="ch_enable")
"--ch-enable", help="Enable the specified channel", action="store_true", dest="ch_enable", default=False)
# Note: We are doing a double negative here (Do we want to disable? If ch_disable==True, then disable.)
parser.add_argument(
"--ch-disable", help="Disable the specified channel", action="store_false", dest="ch_enable")
"--ch-disable", help="Disable the specified channel", action="store_true", dest="ch_disable", default=False)
parser.add_argument(
"--ch-set", help="Set a channel parameter", nargs=2, action='append')
parser.add_argument(
"--ch-longslow", help="Change to the standard long-range (but slow) channel", action='store_true')
"--ch-longslow", help="Change to the long-range and slow channel", action='store_true')
parser.add_argument(
"--ch-shortfast", help="Change to the standard fast (but short range) channel", action='store_true')
"--ch-longfast", help="Change to the long-range and fast channel", action='store_true')
parser.add_argument(
"--ch-mediumslow", help="Change to the medium-range and slow channel", action='store_true')
parser.add_argument(
"--ch-mediumfast", help="Change to the medium-range and fast channel", action='store_true')
parser.add_argument(
"--ch-shortslow", help="Change to the short-range and slow channel", action='store_true')
parser.add_argument(
"--ch-shortfast", help="Change to the short-range and fast channel", action='store_true')
parser.add_argument(
"--set-owner", help="Set device owner name", action="store")
parser.add_argument(
"--set-team", help="Set team affiliation (an invalid team will list valid values)", action="store")
parser.add_argument(
"--set-ham", help="Set licensed HAM ID and turn off encryption", action="store")
@@ -624,6 +724,12 @@ def initParser():
parser.add_argument(
"--setlon", help="Set device longitude (allows use without GPS)")
parser.add_argument(
"--pos-fields", help="Specify fields to send when sending a position. Use no argument for a list of valid values. "\
"Can pass multiple values as a space separated list like "\
"this: '--pos-fields POS_ALTITUDE POS_ALT_MSL'",
nargs="*", action="store")
parser.add_argument("--debug", help="Show API library debug log messages",
action="store_true")
@@ -654,20 +760,32 @@ def initParser():
parser.add_argument('--version', action='version',
version=f"{pkg_resources.require('meshtastic')[0].version}")
parser.add_argument(
"--support", action='store_true', help="Show support info (useful when troubleshooting an issue)")
args = parser.parse_args()
our_globals.set_args(args)
our_globals.set_parser(parser)
def main():
"""Perform command line meshtastic operations"""
our_globals = Globals.getInstance()
parser = argparse.ArgumentParser()
our_globals.set_parser(parser)
initParser()
common()
def tunnelMain():
"""Run a meshtastic IP tunnel"""
global args
our_globals = Globals.getInstance()
parser = argparse.ArgumentParser()
our_globals.set_parser(parser)
initParser()
args = our_globals.get_args()
args.tunnel = True
our_globals.set_args(args)
common()

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: admin.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
@@ -11,9 +11,9 @@ from google.protobuf import symbol_database as _symbol_database
_sym_db = _symbol_database.Default()
from . import channel_pb2 as channel__pb2
from . import mesh_pb2 as mesh__pb2
from . import radioconfig_pb2 as radioconfig__pb2
from . import channel_pb2 as channel__pb2
DESCRIPTOR = _descriptor.FileDescriptor(
@@ -21,10 +21,9 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\013AdminProtosH\003Z!github.com/meshtastic/gomeshproto',
create_key=_descriptor._internal_create_key,
serialized_pb=b'\n\x0b\x61\x64min.proto\x1a\nmesh.proto\x1a\x11radioconfig.proto\x1a\rchannel.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'
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=[mesh__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,channel__pb2.DESCRIPTOR,])
dependencies=[channel__pb2.DESCRIPTOR,mesh__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,])
@@ -35,7 +34,6 @@ _ADMINMESSAGE = _descriptor.Descriptor(
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='set_radio', full_name='AdminMessage.set_radio', index=0,
@@ -43,77 +41,77 @@ _ADMINMESSAGE = _descriptor.Descriptor(
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
@@ -127,9 +125,7 @@ _ADMINMESSAGE = _descriptor.Descriptor(
oneofs=[
_descriptor.OneofDescriptor(
name='variant', full_name='AdminMessage.variant',
index=0, containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[]),
index=0, containing_type=None, fields=[]),
],
serialized_start=62,
serialized_end=441,

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: apponly.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
@@ -19,7 +19,6 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\rAppOnlyProtosH\003Z!github.com/meshtastic/gomeshproto',
create_key=_descriptor._internal_create_key,
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,])
@@ -33,7 +32,6 @@ _CHANNELSET = _descriptor.Descriptor(
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='settings', full_name='ChannelSet.settings', index=0,
@@ -41,7 +39,7 @@ _CHANNELSET = _descriptor.Descriptor(
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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],

View File

@@ -0,0 +1,59 @@
"""Bluetooth interface
"""
import logging
import pygatt
from .mesh_interface import MeshInterface
# Our standard BLE characteristics
TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7"
FROMRADIO_UUID = "8ba2bcc2-ee02-4a55-a531-c525c5e454d5"
FROMNUM_UUID = "ed9da18c-a800-4f66-a670-aa7547e34453"
class BLEInterface(MeshInterface):
"""A not quite ready - FIXME - BLE interface to devices"""
def __init__(self, address, noProto=False, debugOut=None):
self.address = address
if not noProto:
self.adapter = pygatt.GATTToolBackend() # BGAPIBackend()
self.adapter.start()
logging.debug(f"Connecting to {self.address}")
self.device = self.adapter.connect(address)
else:
self.adapter = None
self.device = None
logging.debug("Connected to device")
# fromradio = self.device.char_read(FROMRADIO_UUID)
MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto)
self._readFromRadio() # read the initial responses
def handle_data(handle, data):
self._handleFromRadio(data)
if self.device:
self.device.subscribe(FROMNUM_UUID, callback=handle_data)
def _sendToRadioImpl(self, toRadio):
"""Send a ToRadio protobuf to the device"""
#logging.debug(f"Sending: {stripnl(toRadio)}")
b = toRadio.SerializeToString()
self.device.char_write(TORADIO_UUID, b)
def close(self):
MeshInterface.close(self)
if self.adapter:
self.adapter.stop()
def _readFromRadio(self):
if not self.noProto:
wasEmpty = False
while not wasEmpty:
if self.device:
b = self.device.char_read(FROMRADIO_UUID)
wasEmpty = len(b) == 0
if not wasEmpty:
self._handleFromRadio(b)

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: channel.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
@@ -18,8 +18,7 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\rChannelProtosH\003Z!github.com/meshtastic/gomeshproto',
create_key=_descriptor._internal_create_key,
serialized_pb=b'\n\rchannel.proto\"\xe6\x02\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\"`\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\"\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'
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'
)
@@ -29,33 +28,36 @@ _CHANNELSETTINGS_MODEMCONFIG = _descriptor.EnumDescriptor(
full_name='ChannelSettings.ModemConfig',
filename=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key,
values=[
_descriptor.EnumValueDescriptor(
name='Bw125Cr45Sf128', index=0, number=0,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='Bw500Cr45Sf128', index=1, number=1,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='Bw31_25Cr48Sf512', index=2, number=2,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='Bw125Cr48Sf4096', index=3, number=3,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
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=280,
serialized_end=376,
serialized_start=281,
serialized_end=419,
)
_sym_db.RegisterEnumDescriptor(_CHANNELSETTINGS_MODEMCONFIG)
@@ -64,28 +66,24 @@ _CHANNEL_ROLE = _descriptor.EnumDescriptor(
full_name='Channel.Role',
filename=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key,
values=[
_descriptor.EnumValueDescriptor(
name='DISABLED', index=0, number=0,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='PRIMARY', index=1, number=1,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='SECONDARY', index=2, number=2,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
],
containing_type=None,
serialized_options=None,
serialized_start=470,
serialized_end=518,
serialized_start=513,
serialized_end=561,
)
_sym_db.RegisterEnumDescriptor(_CHANNEL_ROLE)
@@ -96,7 +94,6 @@ _CHANNELSETTINGS = _descriptor.Descriptor(
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='tx_power', full_name='ChannelSettings.tx_power', index=0,
@@ -104,77 +101,77 @@ _CHANNELSETTINGS = _descriptor.Descriptor(
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
@@ -189,7 +186,7 @@ _CHANNELSETTINGS = _descriptor.Descriptor(
oneofs=[
],
serialized_start=18,
serialized_end=376,
serialized_end=419,
)
@@ -199,7 +196,6 @@ _CHANNEL = _descriptor.Descriptor(
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='index', full_name='Channel.index', index=0,
@@ -207,21 +203,21 @@ _CHANNEL = _descriptor.Descriptor(
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
@@ -235,8 +231,8 @@ _CHANNEL = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=379,
serialized_end=518,
serialized_start=422,
serialized_end=561,
)
_CHANNELSETTINGS.fields_by_name['modem_config'].enum_type = _CHANNELSETTINGS_MODEMCONFIG

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: deviceonly.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
@@ -11,8 +11,8 @@ from google.protobuf import symbol_database as _symbol_database
_sym_db = _symbol_database.Default()
from . import mesh_pb2 as mesh__pb2
from . import channel_pb2 as channel__pb2
from . import mesh_pb2 as mesh__pb2
from . import radioconfig_pb2 as radioconfig__pb2
@@ -21,10 +21,9 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\nDeviceOnlyH\003Z!github.com/meshtastic/gomeshproto',
create_key=_descriptor._internal_create_key,
serialized_pb=b'\n\x10\x64\x65viceonly.proto\x1a\nmesh.proto\x1a\rchannel.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'
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=[mesh__pb2.DESCRIPTOR,channel__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,])
dependencies=[channel__pb2.DESCRIPTOR,mesh__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,])
@@ -35,7 +34,6 @@ _LEGACYRADIOCONFIG_LEGACYPREFERENCES = _descriptor.Descriptor(
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='region', full_name='LegacyRadioConfig.LegacyPreferences.region', index=0,
@@ -43,7 +41,7 @@ _LEGACYRADIOCONFIG_LEGACYPREFERENCES = _descriptor.Descriptor(
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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
@@ -66,7 +64,6 @@ _LEGACYRADIOCONFIG = _descriptor.Descriptor(
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='preferences', full_name='LegacyRadioConfig.preferences', index=0,
@@ -74,7 +71,7 @@ _LEGACYRADIOCONFIG = _descriptor.Descriptor(
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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
@@ -98,7 +95,6 @@ _DEVICESTATE = _descriptor.Descriptor(
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='legacyRadio', full_name='DeviceState.legacyRadio', index=0,
@@ -106,63 +102,63 @@ _DEVICESTATE = _descriptor.Descriptor(
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
@@ -186,7 +182,6 @@ _CHANNELFILE = _descriptor.Descriptor(
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='channels', full_name='ChannelFile.channels', index=0,
@@ -194,7 +189,7 @@ _CHANNELFILE = _descriptor.Descriptor(
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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: environmental_measurement.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
@@ -18,7 +18,6 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package='',
syntax='proto3',
serialized_options=b'Z!github.com/meshtastic/gomeshproto',
create_key=_descriptor._internal_create_key,
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'
)
@@ -31,7 +30,6 @@ _ENVIRONMENTALMEASUREMENT = _descriptor.Descriptor(
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='temperature', full_name='EnvironmentalMeasurement.temperature', index=0,
@@ -39,21 +37,21 @@ _ENVIRONMENTALMEASUREMENT = _descriptor.Descriptor(
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],

70
meshtastic/globals.py Normal file
View File

@@ -0,0 +1,70 @@
"""Globals singleton class.
Instead of using a global, stuff your variables in this "trash can".
This is not much better than using python's globals, but it allows
us to better test meshtastic. Plus, there are some weird python
global issues/gotcha that we can hopefully avoid by using this
class instead.
"""
class Globals:
"""Globals class is a Singleton."""
__instance = None
@staticmethod
def getInstance():
"""Get an instance of the Globals class."""
if Globals.__instance is None:
Globals()
return Globals.__instance
def __init__(self):
"""Constructor for the Globals CLass"""
if Globals.__instance is not None:
raise Exception("This class is a singleton")
else:
Globals.__instance = self
self.args = None
self.parser = None
self.target_node = None
self.channel_index = None
def reset(self):
"""Reset all of our globals. If you add a member, add it to this method, too."""
self.args = None
self.parser = None
self.target_node = None
self.channel_index = None
def set_args(self, args):
"""Set the args"""
self.args = args
def set_parser(self, parser):
"""Set the 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):
"""Set the channel_index"""
self.channel_index = channel_index
def get_args(self):
"""Get args"""
return self.args
def get_parser(self):
"""Get parser"""
return self.parser
def get_target_node(self):
"""Get target_node"""
return self.target_node
def get_channel_index(self):
"""Get channel_index"""
return self.channel_index

View File

@@ -0,0 +1,640 @@
""" Mesh Interface class
"""
import sys
import random
import time
import logging
from typing import AnyStr
import threading
from datetime import datetime
import timeago
from tabulate import tabulate
import google.protobuf.json_format
from pubsub import pub
from google.protobuf.json_format import MessageToJson
from . import portnums_pb2, mesh_pb2
from .util import stripnl, Timeout, our_exit
from .node import Node
from .__init__ import LOCAL_ADDR, BROADCAST_NUM, BROADCAST_ADDR, ResponseHandler, publishingThread, OUR_APP_VERSION, protocols
defaultHopLimit = 3
class MeshInterface:
"""Interface class for meshtastic devices
Properties:
isConnected
nodes
debugOut
"""
def __init__(self, debugOut=None, noProto=False):
"""Constructor
Keyword Arguments:
noProto -- If True, don't try to run our protocol on the
link - just be a dumb serial client.
"""
self.debugOut = debugOut
self.nodes = None # FIXME
self.isConnected = threading.Event()
self.noProto = noProto
self.localNode = Node(self, -1) # We fixup nodenum later
self.myInfo = None # We don't have device info yet
self.responseHandlers = {} # A map from request ID to the handler
self.failure = None # If we've encountered a fatal exception it will be kept here
self._timeout = Timeout()
self.heartbeatTimer = None
random.seed() # FIXME, we should not clobber the random seedval here, instead tell user they must call it
self.currentPacketId = random.randint(0, 0xffffffff)
self.nodesByNum = None
self.configId = None
def close(self):
"""Shutdown this interface"""
if self.heartbeatTimer:
self.heartbeatTimer.cancel()
self._sendDisconnect()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
if exc_type is not None and exc_value is not None:
logging.error(
f'An exception of type {exc_type} with value {exc_value} has occurred')
if traceback is not None:
logging.error(f'Traceback: {traceback}')
self.close()
def showInfo(self, file=sys.stdout):
"""Show human readable summary about this object"""
owner = f"Owner: {self.getLongName()} ({self.getShortName()})"
myinfo = ''
if self.myInfo:
myinfo = f"\nMy info: {stripnl(MessageToJson(self.myInfo))}"
mesh = "\nNodes in mesh:"
nodes = ""
if self.nodes:
for n in self.nodes.values():
nodes = nodes + f" {stripnl(n)}"
infos = owner + myinfo + mesh + nodes
print(infos)
return infos
def showNodes(self, includeSelf=True, file=sys.stdout):
"""Show table summary of nodes in mesh"""
def formatFloat(value, precision=2, unit=''):
"""Format a float value with precsion."""
return f'{value:.{precision}f}{unit}' if value else None
def getLH(ts):
"""Format last heard"""
return datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S') if ts else None
def getTimeAgo(ts):
"""Format how long ago have we heard from this node (aka timeago)."""
return timeago.format(datetime.fromtimestamp(ts), datetime.now()) if ts else None
rows = []
if self.nodes:
for node in self.nodes.values():
if not includeSelf and node['num'] == self.localNode.nodeNum:
continue
row = {"N": 0}
user = node.get('user')
if user:
row.update({
"User": user['longName'],
"AKA": user['shortName'],
"ID": user['id'],
})
pos = node.get('position')
if pos:
row.update({
"Latitude": formatFloat(pos.get("latitude"), 4, "°"),
"Longitude": formatFloat(pos.get("longitude"), 4, "°"),
"Altitude": formatFloat(pos.get("altitude"), 0, " m"),
"Battery": formatFloat(pos.get("batteryLevel"), 2, "%"),
})
row.update({
"SNR": formatFloat(node.get("snr"), 2, " dB"),
"LastHeard": getLH(node.get("lastHeard")),
"Since": getTimeAgo(node.get("lastHeard")),
})
rows.append(row)
rows.sort(key=lambda r: r.get('LastHeard') or '0000', reverse=True)
for i, row in enumerate(rows):
row['N'] = i+1
table = tabulate(rows, headers='keys', missingval='N/A', tablefmt='fancy_grid')
print(table)
return table
def getNode(self, nodeId):
"""Return a node object which contains device settings and channel info"""
if nodeId == LOCAL_ADDR:
return self.localNode
else:
n = Node(self, nodeId)
n.requestConfig()
if not n.waitForConfig():
our_exit("Error: Timed out waiting for node config")
return n
def sendText(self, text: AnyStr,
destinationId=BROADCAST_ADDR,
wantAck=False,
wantResponse=False,
hopLimit=defaultHopLimit,
onResponse=None,
channelIndex=0):
"""Send a utf8 string to some other node, if the node has a display it
will also be shown on the device.
Arguments:
text {string} -- The text to send
Keyword Arguments:
destinationId {nodeId or nodeNum} -- where to send this
message (default: {BROADCAST_ADDR})
portNum -- the application portnum (similar to IP port numbers)
of the destination, see portnums.proto for a list
wantAck -- True if you want the message sent in a reliable manner
(with retries and ack/nak provided for delivery)
wantResponse -- True if you want the service on the other side to
send an application layer response
Returns the sent packet. The id field will be populated in this packet
and can be used to track future message acks/naks.
"""
return self.sendData(text.encode("utf-8"), destinationId,
portNum=portnums_pb2.PortNum.TEXT_MESSAGE_APP,
wantAck=wantAck,
wantResponse=wantResponse,
hopLimit=hopLimit,
onResponse=onResponse,
channelIndex=channelIndex)
def sendData(self, data, destinationId=BROADCAST_ADDR,
portNum=portnums_pb2.PortNum.PRIVATE_APP, wantAck=False,
wantResponse=False,
hopLimit=defaultHopLimit,
onResponse=None,
channelIndex=0):
"""Send a data packet to some other node
Keyword Arguments:
data -- the data to send, either as an array of bytes or
as a protobuf (which will be automatically
serialized to bytes)
destinationId {nodeId or nodeNum} -- where to send this
message (default: {BROADCAST_ADDR})
portNum -- the application portnum (similar to IP port numbers)
of the destination, see portnums.proto for a list
wantAck -- True if you want the message sent in a reliable
manner (with retries and ack/nak provided for delivery)
wantResponse -- True if you want the service on the other
side to send an application layer response
onResponse -- A closure of the form funct(packet), that will be
called when a response packet arrives (or the transaction
is NAKed due to non receipt)
Returns the sent packet. The id field will be populated in this packet
and can be used to track future message acks/naks.
"""
if getattr(data, "SerializeToString", None):
logging.debug(f"Serializing protobuf as data: {stripnl(data)}")
data = data.SerializeToString()
if len(data) > mesh_pb2.Constants.DATA_PAYLOAD_LEN:
Exception("Data payload too big")
if portNum == portnums_pb2.PortNum.UNKNOWN_APP: # we are now more strict wrt port numbers
our_exit("Warning: A non-zero port number must be specified")
meshPacket = mesh_pb2.MeshPacket()
meshPacket.channel = channelIndex
meshPacket.decoded.payload = data
meshPacket.decoded.portnum = portNum
meshPacket.decoded.want_response = wantResponse
p = self._sendPacket(meshPacket, destinationId,
wantAck=wantAck, hopLimit=hopLimit)
if onResponse is not None:
self._addResponseHandler(p.id, onResponse)
return p
def sendPosition(self, latitude=0.0, longitude=0.0, altitude=0, timeSec=0,
destinationId=BROADCAST_ADDR, wantAck=False, wantResponse=False):
"""
Send a position packet to some other node (normally a broadcast)
Also, the device software will notice this packet and use it to automatically
set its notion of the local position.
If timeSec is not specified (recommended), we will use the local machine time.
Returns the sent packet. The id field will be populated in this packet and
can be used to track future message acks/naks.
"""
p = mesh_pb2.Position()
if latitude != 0.0:
p.latitude_i = int(latitude / 1e-7)
if longitude != 0.0:
p.longitude_i = int(longitude / 1e-7)
if altitude != 0:
p.altitude = int(altitude)
if timeSec == 0:
timeSec = time.time() # returns unix timestamp in seconds
p.time = int(timeSec)
return self.sendData(p, destinationId,
portNum=portnums_pb2.PortNum.POSITION_APP,
wantAck=wantAck,
wantResponse=wantResponse)
def _addResponseHandler(self, requestId, callback):
self.responseHandlers[requestId] = ResponseHandler(callback)
def _sendPacket(self, meshPacket,
destinationId=BROADCAST_ADDR,
wantAck=False, hopLimit=defaultHopLimit):
"""Send a MeshPacket to the specified node (or if unspecified, broadcast).
You probably don't want this - use sendData instead.
Returns the sent packet. The id field will be populated in this packet and
can be used to track future message acks/naks.
"""
# We allow users to talk to the local node before we've completed the full connection flow...
if(self.myInfo is not None and destinationId != self.myInfo.my_node_num):
self._waitConnected()
toRadio = mesh_pb2.ToRadio()
if destinationId is None:
our_exit("Warning: destinationId must not be None")
elif isinstance(destinationId, int):
nodeNum = destinationId
elif destinationId == BROADCAST_ADDR:
nodeNum = BROADCAST_NUM
elif destinationId == LOCAL_ADDR:
nodeNum = self.myInfo.my_node_num
# A simple hex style nodeid - we can parse this without needing the DB
elif destinationId.startswith("!"):
nodeNum = int(destinationId[1:], 16)
else:
node = self.nodes.get(destinationId)
if not node:
our_exit(f"Warning: NodeId {destinationId} not found in DB")
nodeNum = node['num']
meshPacket.to = nodeNum
meshPacket.want_ack = wantAck
meshPacket.hop_limit = hopLimit
# if the user hasn't set an ID for this packet (likely and recommended),
# we should pick a new unique ID so the message can be tracked.
if meshPacket.id == 0:
meshPacket.id = self._generatePacketId()
toRadio.packet.CopyFrom(meshPacket)
#logging.debug(f"Sending packet: {stripnl(meshPacket)}")
self._sendToRadio(toRadio)
return meshPacket
def waitForConfig(self):
"""Block until radio config is received. Returns True if config has been received."""
success = self._timeout.waitForSet(self, attrs=('myInfo', 'nodes')) and self.localNode.waitForConfig()
if not success:
raise Exception("Timed out waiting for interface config")
def getMyNodeInfo(self):
"""Get info about my node."""
if self.myInfo is None:
return None
return self.nodesByNum.get(self.myInfo.my_node_num)
def getMyUser(self):
"""Get user"""
nodeInfo = self.getMyNodeInfo()
if nodeInfo is not None:
return nodeInfo.get('user')
return None
def getLongName(self):
"""Get long name"""
user = self.getMyUser()
if user is not None:
return user.get('longName', None)
return None
def getShortName(self):
"""Get short name"""
user = self.getMyUser()
if user is not None:
return user.get('shortName', None)
return None
def _waitConnected(self):
"""Block until the initial node db download is complete, or timeout
and raise an exception"""
if not self.isConnected.wait(10.0): # timeout after 10 seconds
raise Exception("Timed out waiting for connection completion")
# If we failed while connecting, raise the connection to the client
if self.failure:
raise self.failure
def _generatePacketId(self):
"""Get a new unique packet ID"""
if self.currentPacketId is None:
raise Exception("Not connected yet, can not generate packet")
else:
self.currentPacketId = (self.currentPacketId + 1) & 0xffffffff
return self.currentPacketId
def _disconnected(self):
"""Called by subclasses to tell clients this interface has disconnected"""
self.isConnected.clear()
publishingThread.queueWork(lambda: pub.sendMessage(
"meshtastic.connection.lost", interface=self))
def _startHeartbeat(self):
"""We need to send a heartbeat message to the device every X seconds"""
def callback():
self.heartbeatTimer = None
prefs = self.localNode.radioConfig.preferences
i = prefs.phone_timeout_secs / 2
logging.debug(f"Sending heartbeat, interval {i}")
if i != 0:
self.heartbeatTimer = threading.Timer(i, callback)
self.heartbeatTimer.start()
p = mesh_pb2.ToRadio()
self._sendToRadio(p)
callback() # run our periodic callback now, it will make another timer if necessary
def _connected(self):
"""Called by this class to tell clients we are now fully connected to a node
"""
# (because I'm lazy) _connected might be called when remote Node
# objects complete their config reads, don't generate redundant isConnected
# for the local interface
if not self.isConnected.is_set():
self.isConnected.set()
self._startHeartbeat()
publishingThread.queueWork(lambda: pub.sendMessage(
"meshtastic.connection.established", interface=self))
def _startConfig(self):
"""Start device packets flowing"""
self.myInfo = None
self.nodes = {} # nodes keyed by ID
self.nodesByNum = {} # nodes keyed by nodenum
startConfig = mesh_pb2.ToRadio()
self.configId = random.randint(0, 0xffffffff)
startConfig.want_config_id = self.configId
self._sendToRadio(startConfig)
def _sendDisconnect(self):
"""Tell device we are done using it"""
m = mesh_pb2.ToRadio()
m.disconnect = True
self._sendToRadio(m)
def _sendToRadio(self, toRadio):
"""Send a ToRadio protobuf to the device"""
if self.noProto:
logging.warning(f"Not sending packet because protocol use is disabled by noProto")
else:
#logging.debug(f"Sending toRadio: {stripnl(toRadio)}")
self._sendToRadioImpl(toRadio)
def _sendToRadioImpl(self, toRadio):
"""Send a ToRadio protobuf to the device"""
logging.error(f"Subclass must provide toradio: {toRadio}")
def _handleConfigComplete(self):
"""
Done with initial config messages, now send regular MeshPackets
to ask for settings and channels
"""
self.localNode.requestConfig()
def _handleFromRadio(self, fromRadioBytes):
"""
Handle a packet that arrived from the radio(update model and publish events)
Called by subclasses."""
fromRadio = mesh_pb2.FromRadio()
fromRadio.ParseFromString(fromRadioBytes)
asDict = google.protobuf.json_format.MessageToDict(fromRadio)
#logging.debug(f"Received from radio: {fromRadio}")
if fromRadio.HasField("my_info"):
self.myInfo = fromRadio.my_info
self.localNode.nodeNum = self.myInfo.my_node_num
logging.debug(f"Received myinfo: {stripnl(fromRadio.my_info)}")
failmsg = None
# Check for app too old
if self.myInfo.min_app_version > OUR_APP_VERSION:
failmsg = "This device needs a newer python client, run 'pip install --upgrade meshtastic'."\
"For more information see https://tinyurl.com/5bjsxu32"
# check for firmware too old
if self.myInfo.max_channels == 0:
failmsg = "This version of meshtastic-python requires device firmware version 1.2 or later. "\
"For more information see https://tinyurl.com/5bjsxu32"
if failmsg:
self.failure = Exception(failmsg)
self.isConnected.set() # let waitConnected return this exception
self.close()
elif fromRadio.HasField("node_info"):
node = asDict["nodeInfo"]
try:
self._fixupPosition(node["position"])
except:
logging.debug("Node without position")
logging.debug(f"Received nodeinfo: {node}")
self.nodesByNum[node["num"]] = node
if "user" in node: # Some nodes might not have user/ids assigned yet
self.nodes[node["user"]["id"]] = node
publishingThread.queueWork(lambda: pub.sendMessage("meshtastic.node.updated",
node=node, interface=self))
elif fromRadio.config_complete_id == self.configId:
# we ignore the config_complete_id, it is unneeded for our
# stream API fromRadio.config_complete_id
logging.debug(f"Config complete ID {self.configId}")
self._handleConfigComplete()
elif fromRadio.HasField("packet"):
self._handlePacketFromRadio(fromRadio.packet)
elif fromRadio.rebooted:
# Tell clients the device went away. Careful not to call the overridden
# subclass version that closes the serial port
MeshInterface._disconnected(self)
self._startConfig() # redownload the node db etc...
else:
logging.debug("Unexpected FromRadio payload")
def _fixupPosition(self, position):
"""Convert integer lat/lon into floats
Arguments:
position {Position dictionary} -- object ot fix up
"""
if "latitudeI" in position:
position["latitude"] = position["latitudeI"] * 1e-7
if "longitudeI" in position:
position["longitude"] = position["longitudeI"] * 1e-7
def _nodeNumToId(self, num):
"""Map a node node number to a node ID
Arguments:
num {int} -- Node number
Returns:
string -- Node ID
"""
if num == BROADCAST_NUM:
return BROADCAST_ADDR
try:
return self.nodesByNum[num]["user"]["id"]
except:
logging.debug(f"Node {num} not found for fromId")
return None
def _getOrCreateByNum(self, nodeNum):
"""Given a nodenum find the NodeInfo in the DB (or create if necessary)"""
if nodeNum == BROADCAST_NUM:
raise Exception("Can not create/find nodenum by the broadcast num")
if nodeNum in self.nodesByNum:
return self.nodesByNum[nodeNum]
else:
n = {"num": nodeNum} # Create a minimial node db entry
self.nodesByNum[nodeNum] = n
return n
def _handlePacketFromRadio(self, meshPacket):
"""Handle a MeshPacket that just arrived from the radio
Will publish one of the following events:
- meshtastic.receive.text(packet = MeshPacket dictionary)
- meshtastic.receive.position(packet = MeshPacket dictionary)
- meshtastic.receive.user(packet = MeshPacket dictionary)
- meshtastic.receive.data(packet = MeshPacket dictionary)
"""
asDict = google.protobuf.json_format.MessageToDict(meshPacket)
# We normally decompose the payload into a dictionary so that the client
# doesn't need to understand protobufs. But advanced clients might
# want the raw protobuf, so we provide it in "raw"
asDict["raw"] = meshPacket
# from might be missing if the nodenum was zero.
if "from" not in asDict:
asDict["from"] = 0
logging.error(
f"Device returned a packet we sent, ignoring: {stripnl(asDict)}")
return
if "to" not in asDict:
asDict["to"] = 0
# /add fromId and toId fields based on the node ID
try:
asDict["fromId"] = self._nodeNumToId(asDict["from"])
except Exception as ex:
logging.warning(f"Not populating fromId {ex}")
try:
asDict["toId"] = self._nodeNumToId(asDict["to"])
except Exception as ex:
logging.warning(f"Not populating toId {ex}")
# We could provide our objects as DotMaps - which work with . notation or as dictionaries
# asObj = DotMap(asDict)
topic = "meshtastic.receive" # Generic unknown packet type
decoded = asDict["decoded"]
# The default MessageToDict converts byte arrays into base64 strings.
# We don't want that - it messes up data payload. So slam in the correct
# byte array.
decoded["payload"] = meshPacket.decoded.payload
# UNKNOWN_APP is the default protobuf portnum value, and therefore if not
# set it will not be populated at all to make API usage easier, set
# it to prevent confusion
if not "portnum" in decoded:
decoded["portnum"] = portnums_pb2.PortNum.Name(
portnums_pb2.PortNum.UNKNOWN_APP)
portnum = decoded["portnum"]
topic = f"meshtastic.receive.data.{portnum}"
# decode position protobufs and update nodedb, provide decoded version
# as "position" in the published msg move the following into a 'decoders'
# API that clients could register?
portNumInt = meshPacket.decoded.portnum # we want portnum as an int
handler = protocols.get(portNumInt)
# The decoded protobuf as a dictionary (if we understand this message)
p = None
if handler is not None:
topic = f"meshtastic.receive.{handler.name}"
# Convert to protobuf if possible
if handler.protobufFactory is not None:
pb = handler.protobufFactory()
pb.ParseFromString(meshPacket.decoded.payload)
p = google.protobuf.json_format.MessageToDict(pb)
asDict["decoded"][handler.name] = p
# Also provide the protobuf raw
asDict["decoded"][handler.name]["raw"] = pb
# Call specialized onReceive if necessary
if handler.onReceive is not None:
handler.onReceive(self, asDict)
# Is this message in response to a request, if so, look for a handler
requestId = decoded.get("requestId")
if requestId is not None:
# We ignore ACK packets, but send NAKs and data responses to the handlers
routing = decoded.get("routing")
isAck = routing is not None and ("errorReason" not in routing)
if not isAck:
# we keep the responseHandler in dict until we get a non ack
handler = self.responseHandlers.pop(requestId, None)
if handler is not None:
handler.callback(asDict)
logging.debug(f"Publishing {topic}: packet={stripnl(asDict)} ")
publishingThread.queueWork(lambda: pub.sendMessage(
topic, packet=asDict, interface=self))

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: mqtt.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
@@ -19,7 +19,6 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\nMQTTProtosH\003Z!github.com/meshtastic/gomeshproto',
create_key=_descriptor._internal_create_key,
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,])
@@ -33,7 +32,6 @@ _SERVICEENVELOPE = _descriptor.Descriptor(
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='packet', full_name='ServiceEnvelope.packet', index=0,
@@ -41,21 +39,21 @@ _SERVICEENVELOPE = _descriptor.Descriptor(
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],

View File

@@ -1,95 +1,11 @@
"""
# an API for Meshtastic devices
Primary class: SerialInterface
Install with pip: "[pip3 install meshtastic](https://pypi.org/project/meshtastic/)"
Source code on [github](https://github.com/meshtastic/Meshtastic-python)
properties of SerialInterface:
- radioConfig - Current radio configuration and device settings, if you write to this the new settings will be applied to
the device.
- nodes - The database of received nodes. Includes always up-to-date location and username information for each
node in the mesh. This is a read-only datastructure.
- nodesByNum - like "nodes" but keyed by nodeNum instead of nodeId
- myInfo - Contains read-only information about the local radio device (software version, hardware version, etc)
# Published PubSub topics
We use a [publish-subscribe](https://pypubsub.readthedocs.io/en/v4.0.3/) model to communicate asynchronous events. Available
topics:
- meshtastic.connection.established - published once we've successfully connected to the radio and downloaded the node DB
- meshtastic.connection.lost - published once we've lost our link to the radio
- meshtastic.receive.text(packet) - delivers a received packet as a dictionary, if you only care about a particular
type of packet, you should subscribe to the full topic name. If you want to see all packets, simply subscribe to "meshtastic.receive".
- meshtastic.receive.position(packet)
- meshtastic.receive.user(packet)
- meshtastic.receive.data.portnum(packet) (where portnum is an integer or well known PortNum enum)
- meshtastic.node.updated(node = NodeInfo) - published when a node in the DB changes (appears, location changed, username changed, etc...)
We receive position, user, or data packets from the mesh. You probably only care about meshtastic.receive.data. The first argument for
that publish will be the packet. Text or binary data packets (from sendData or sendText) will both arrive this way. If you print packet
you'll see the fields in the dictionary. decoded.data.payload will contain the raw bytes that were sent. If the packet was sent with
sendText, decoded.data.text will **also** be populated with the decoded string. For ASCII these two strings will be the same, but for
unicode scripts they can be different.
# Example Usage
```
import meshtastic
from pubsub import pub
def onReceive(packet, interface): # called when a packet arrives
print(f"Received: {packet}")
def onConnection(interface, topic=pub.AUTO_TOPIC): # called when we (re)connect to the radio
# defaults to broadcast, specify a destination ID if you wish
interface.sendText("hello mesh")
pub.subscribe(onReceive, "meshtastic.receive")
pub.subscribe(onConnection, "meshtastic.connection.established")
# By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0
interface = meshtastic.SerialInterface()
```
"""Node class
"""
import pygatt
import google.protobuf.json_format
import serial
import threading
import logging
import sys
import random
import traceback
import time
import base64
import platform
import socket
from . import mesh_pb2, portnums_pb2, apponly_pb2, admin_pb2, environmental_measurement_pb2, remote_hardware_pb2, channel_pb2, radioconfig_pb2, util
from .util import fixme, catchAndIgnore, stripnl, DeferredExecution, Timeout
from pubsub import pub
from dotmap import DotMap
from typing import *
from google.protobuf.json_format import MessageToJson
def pskToString(psk: bytes):
"""Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string"""
if len(psk) == 0:
return "unencrypted"
elif len(psk) == 1:
b = psk[0]
if b == 0:
return "unencrypted"
elif b == 1:
return "default"
else:
return f"simple{b - 1}"
else:
return "secret"
from . import portnums_pb2, apponly_pb2, admin_pb2, channel_pb2
from .util import pskToString, stripnl, Timeout, our_exit, fromPSK
class Node:
@@ -105,15 +21,17 @@ class Node:
self.radioConfig = None
self.channels = None
self._timeout = Timeout(maxSecs=60)
self.partialChannels = None
def showChannels(self):
"""Show human readable description of our channels"""
"""Show human readable description of our channels."""
print("Channels:")
for c in self.channels:
if c.role != channel_pb2.Channel.Role.DISABLED:
if self.channels:
for c in self.channels:
cStr = stripnl(MessageToJson(c.settings))
print(
f" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}")
# only show if there is no psk (meaning disabled channel)
if c.settings.psk:
print(f" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}")
publicURL = self.getURL(includeAll=False)
adminURL = self.getURL(includeAll=True)
print(f"\nPrimary channel URL: {publicURL}")
@@ -122,28 +40,34 @@ class Node:
def showInfo(self):
"""Show human readable description of our node"""
print(
f"Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n")
prefs = ""
if self.radioConfig and self.radioConfig.preferences:
prefs = stripnl(MessageToJson(self.radioConfig.preferences))
print(f"Preferences: {prefs}\n")
self.showChannels()
def requestConfig(self):
"""
Send regular MeshPackets to ask for settings and channels
"""
"""Send regular MeshPackets to ask for settings and channels."""
self.radioConfig = None
self.channels = None
self.partialChannels = [] # We keep our channels in a temp array until finished
self._requestSettings()
def turnOffEncryptionOnPrimaryChannel(self):
"""Turn off encryption on primary channel."""
self.channels[0].settings.psk = fromPSK("none")
print("Writing modified channels to device")
self.writeChannel(0)
def waitForConfig(self):
"""Block until radio config is received. Returns True if config has been received."""
return self._timeout.waitForSet(self, attrs=('radioConfig', 'channels'))
def writeConfig(self):
"""Write the current (edited) radioConfig to the device"""
if self.radioConfig == None:
raise Exception("No RadioConfig has been read")
if self.radioConfig is None:
our_exit("Error: No RadioConfig has been read")
p = admin_pb2.AdminMessage()
p.set_radio.CopyFrom(self.radioConfig)
@@ -163,8 +87,8 @@ class Node:
def deleteChannel(self, channelIndex):
"""Delete the specifed channelIndex and shift other channels up"""
ch = self.channels[channelIndex]
if ch.role != channel_pb2.Channel.Role.SECONDARY:
raise Exception("Only SECONDARY channels can be deleted")
if ch.role not in (channel_pb2.Channel.Role.SECONDARY, channel_pb2.Channel.Role.DISABLED):
our_exit("Warning: Only SECONDARY channels can be deleted")
# we are careful here because if we move the "admin" channel the channelIndex we need to use
# for sending admin channels will also change
@@ -178,9 +102,11 @@ class Node:
self.writeChannel(index, adminIndex=adminIndex)
index += 1
# if we are updating the local node, we might end up *moving* the admin channel index as we are writing
# if we are updating the local node, we might end up
# *moving* the admin channel index as we are writing
if (self.iface.localNode == self) and index >= adminIndex:
# We've now passed the old location for admin index (and writen it), so we can start finding it by name again
# We've now passed the old location for admin index
# (and writen it), so we can start finding it by name again
adminIndex = 0
def getChannelByName(self, name):
@@ -205,7 +131,7 @@ class Node:
else:
return 0
def setOwner(self, long_name, short_name=None, is_licensed=False):
def setOwner(self, long_name=None, short_name=None, is_licensed=False, team=None):
"""Set device owner name"""
nChars = 3
minChars = 2
@@ -233,25 +159,27 @@ class Node:
short_name = short_name[:nChars]
p.set_owner.short_name = short_name
p.set_owner.is_licensed = is_licensed
if team is not None:
p.set_owner.team = team
return self._sendAdmin(p)
def getURL(self, includeAll: bool = True):
"""The sharable URL that describes the current channel
"""
"""The sharable URL that describes the current channel"""
# Only keep the primary/secondary channels, assume primary is first
channelSet = apponly_pb2.ChannelSet()
for c in self.channels:
if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY):
channelSet.settings.append(c.settings)
bytes = channelSet.SerializeToString()
s = base64.urlsafe_b64encode(bytes).decode('ascii')
if self.channels:
for c in self.channels:
if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY):
channelSet.settings.append(c.settings)
some_bytes = channelSet.SerializeToString()
s = base64.urlsafe_b64encode(some_bytes).decode('ascii')
return f"https://www.meshtastic.org/d/#{s}".replace("=", "")
def setURL(self, url):
"""Set mesh network URL"""
if self.radioConfig == None:
raise Exception("No RadioConfig has been read")
if self.radioConfig is None:
our_exit("Warning: No RadioConfig has been read")
# URLs are of the form https://www.meshtastic.org/d/#{base64_channel_set}
# Split on '/#' to find the base64 encoded channel settings
@@ -269,6 +197,9 @@ class Node:
channelSet = apponly_pb2.ChannelSet()
channelSet.ParseFromString(decodedURL)
if len(channelSet.settings) == 0:
our_exit("Warning: There were no settings.")
i = 0
for chs in channelSet.settings:
ch = channel_pb2.Channel()
@@ -280,41 +211,40 @@ class Node:
i = i + 1
def _requestSettings(self):
"""
Done with initial config messages, now send regular MeshPackets to ask for settings
"""
"""Done with initial config messages, now send regular
MeshPackets to ask for settings."""
p = admin_pb2.AdminMessage()
p.get_radio_request = True
def onResponse(p):
"""A closure to handle the response packet"""
self.radioConfig = p["decoded"]["admin"]["raw"].get_radio_response
logging.debug("Received radio config, now fetching channels...")
self._timeout.reset() # We made foreward progress
self._requestChannel(0) # now start fetching channels
errorFound = False
if 'routing' in p["decoded"]:
if p["decoded"]["routing"]["errorReason"] != "NONE":
errorFound = True
print(f'Error on response: {p["decoded"]["routing"]["errorReason"]}')
if errorFound is False:
self.radioConfig = p["decoded"]["admin"]["raw"].get_radio_response
logging.debug("Received radio config, now fetching channels...")
self._timeout.reset() # We made foreward progress
self._requestChannel(0) # now start fetching channels
# Show progress message for super slow operations
if self != self.iface.localNode:
logging.info(
"Requesting preferences from remote node (this could take a while)")
print("Requesting preferences from remote node (this could take a while)")
return self._sendAdmin(p,
wantResponse=True,
onResponse=onResponse)
return self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
def exitSimulator(self):
"""
Tell a simulator node to exit (this message is ignored for other nodes)
"""
"""Tell a simulator node to exit (this message
is ignored for other nodes)"""
p = admin_pb2.AdminMessage()
p.exit_simulator = True
return self._sendAdmin(p)
def reboot(self, secs: int = 10):
"""
Tell the node to reboot
"""
"""Tell the node to reboot."""
p = admin_pb2.AdminMessage()
p.reboot_seconds = secs
logging.info(f"Telling node to reboot in {secs} seconds")
@@ -325,6 +255,7 @@ class Node:
"""Fixup indexes and add disabled channels as needed"""
# Add extra disabled channels as needed
# TODO: These 2 lines seem to not do anything.
for index, ch in enumerate(self.channels):
ch.index = index # fixup indexes
@@ -343,21 +274,19 @@ class Node:
index += 1
def _requestChannel(self, channelNum: int):
"""
Done with initial config messages, now send regular MeshPackets to ask for settings
"""
"""Done with initial config messages, now send regular
MeshPackets to ask for settings"""
p = admin_pb2.AdminMessage()
p.get_channel_request = channelNum + 1
# Show progress message for super slow operations
if self != self.iface.localNode:
logging.info(
f"Requesting channel {channelNum} info from remote node (this could take a while)")
logging.info(f"Requesting channel {channelNum} info from remote node (this could take a while)")
else:
logging.debug(f"Requesting channel {channelNum}")
def onResponse(p):
"""A closure to handle the response packet"""
"""A closure to handle the response packet for requesting a channel"""
c = p["decoded"]["admin"]["raw"].get_channel_response
self.partialChannels.append(c)
self._timeout.reset() # We made foreward progress
@@ -367,9 +296,9 @@ class Node:
# for stress testing, we can always download all channels
fastChannelDownload = True
# Once we see a response that has NO settings, assume we are at the end of channels and stop fetching
quitEarly = (
c.role == channel_pb2.Channel.Role.DISABLED) and fastChannelDownload
# Once we see a response that has NO settings, assume
# we are at the end of channels and stop fetching
quitEarly = (c.role == channel_pb2.Channel.Role.DISABLED) and fastChannelDownload
if quitEarly or index >= self.iface.myInfo.max_channels - 1:
logging.debug("Finished downloading channels")
@@ -382,13 +311,10 @@ class Node:
else:
self._requestChannel(index + 1)
return self._sendAdmin(p,
wantResponse=True,
onResponse=onResponse)
return self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
def _sendAdmin(self, p: admin_pb2.AdminMessage, wantResponse=False,
onResponse=None,
adminIndex=0):
onResponse=None, adminIndex=0):
"""Send an admin message to the specified node (or the local node if destNodeNum is zero)"""
if adminIndex == 0: # unless a special channel index was used, we want to use the admin index
@@ -400,5 +326,3 @@ class Node:
wantResponse=wantResponse,
onResponse=onResponse,
channelIndex=adminIndex)

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: portnums.proto
"""Generated protocol buffer code."""
from google.protobuf.internal import enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
@@ -19,8 +19,7 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\010PortnumsH\003Z!github.com/meshtastic/gomeshproto',
create_key=_descriptor._internal_create_key,
serialized_pb=b'\n\x0eportnums.proto*\xbe\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\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'
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(
@@ -28,93 +27,80 @@ _PORTNUM = _descriptor.EnumDescriptor(
full_name='PortNum',
filename=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key,
values=[
_descriptor.EnumValueDescriptor(
name='UNKNOWN_APP', index=0, number=0,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='TEXT_MESSAGE_APP', index=1, number=1,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='REMOTE_HARDWARE_APP', index=2, number=2,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='POSITION_APP', index=3, number=3,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='NODEINFO_APP', index=4, number=4,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='ROUTING_APP', index=5, number=5,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='ADMIN_APP', index=6, number=6,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='REPLY_APP', index=7, number=32,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='IP_TUNNEL_APP', index=8, number=33,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='SERIAL_APP', index=9, number=64,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='STORE_FORWARD_APP', index=10, number=65,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='RANGE_TEST_APP', index=11, number=66,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='ENVIRONMENTAL_MEASUREMENT_APP', index=12, number=67,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='PRIVATE_APP', index=13, number=256,
name='ZPS_APP', index=13, number=68,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='ATAK_FORWARDER', index=14, number=257,
name='PRIVATE_APP', index=14, number=256,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='MAX', index=15, number=511,
name='ATAK_FORWARDER', index=15, number=257,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
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=337,
serialized_end=350,
)
_sym_db.RegisterEnumDescriptor(_PORTNUM)
@@ -132,6 +118,7 @@ SERIAL_APP = 64
STORE_FORWARD_APP = 65
RANGE_TEST_APP = 66
ENVIRONMENTAL_MEASUREMENT_APP = 67
ZPS_APP = 68
PRIVATE_APP = 256
ATAK_FORWARDER = 257
MAX = 511

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,7 @@
from . import portnums_pb2, remote_hardware_pb2
""" Remote hardware
"""
from pubsub import pub
from . import portnums_pb2, remote_hardware_pb2
def onGPIOreceive(packet, interface):
@@ -13,7 +14,7 @@ def onGPIOreceive(packet, interface):
class RemoteHardwareClient:
"""
This is the client code to control/monitor simple hardware built into the
This is the client code to control/monitor simple hardware built into the
meshtastic devices. It is intended to be both a useful API/service and example
code for how you can connect to your own custom meshtastic services
"""
@@ -28,7 +29,8 @@ class RemoteHardwareClient:
ch = iface.localNode.getChannelByName("gpio")
if not ch:
raise Exception(
"No gpio channel found, please create on the sending and receive nodes to use this (secured) service (--ch-add gpio --info then --seturl)")
"No gpio channel found, please create on the sending and receive nodes "\
"to use this (secured) service (--ch-add gpio --info then --seturl)")
self.channelIndex = ch.index
pub.subscribe(
@@ -37,7 +39,7 @@ class RemoteHardwareClient:
def _sendHardware(self, nodeid, r, wantResponse=False, onResponse=None):
if not nodeid:
raise Exception(
"You must set a destination node ID for this operation (use --dest \!xxxxxxxxx)")
r"You must set a destination node ID for this operation (use --dest \!xxxxxxxxx)")
return self.iface.sendData(r, nodeid, portnums_pb2.REMOTE_HARDWARE_APP,
wantAck=True, channelIndex=self.channelIndex, wantResponse=wantResponse, onResponse=onResponse)

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: remote_hardware.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
@@ -18,7 +18,6 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\016RemoteHardwareH\003Z!github.com/meshtastic/gomeshproto',
create_key=_descriptor._internal_create_key,
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'
)
@@ -29,38 +28,31 @@ _HARDWAREMESSAGE_TYPE = _descriptor.EnumDescriptor(
full_name='HardwareMessage.Type',
filename=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key,
values=[
_descriptor.EnumValueDescriptor(
name='UNSET', index=0, number=0,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='WRITE_GPIOS', index=1, number=1,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='WATCH_GPIOS', index=2, number=2,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='GPIOS_CHANGED', index=3, number=3,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='READ_GPIOS', index=4, number=4,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
_descriptor.EnumValueDescriptor(
name='READ_GPIOS_REPLY', index=5, number=5,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
type=None),
],
containing_type=None,
serialized_options=None,
@@ -76,7 +68,6 @@ _HARDWAREMESSAGE = _descriptor.Descriptor(
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='typ', full_name='HardwareMessage.typ', index=0,
@@ -84,21 +75,21 @@ _HARDWAREMESSAGE = _descriptor.Descriptor(
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
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, create_key=_descriptor._internal_create_key),
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],

View File

@@ -0,0 +1,77 @@
""" Serial interface class
"""
import logging
import platform
import os
import stat
import serial
import meshtastic.util
from .stream_interface import StreamInterface
class SerialInterface(StreamInterface):
"""Interface class for meshtastic devices over a serial link"""
def __init__(self, devPath=None, debugOut=None, noProto=False, connectNow=True):
"""Constructor, opens a connection to a specified serial port, or if unspecified try to
find one Meshtastic device by probing
Keyword Arguments:
devPath {string} -- A filepath to a device, i.e. /dev/ttyUSB0 (default: {None})
debugOut {stream} -- If a stream is provided, any debug serial output from the device will be emitted to that stream. (default: {None})
"""
if devPath is None:
ports = meshtastic.util.findPorts()
logging.debug(f"ports:{ports}")
if len(ports) == 0:
meshtastic.util.our_exit("Warning: No Meshtastic devices detected.")
elif len(ports) > 1:
message = "Warning: Multiple serial ports were detected so one serial port must be specified with the '--port'.\n"
message += f" Ports detected:{ports}"
meshtastic.util.our_exit(message)
else:
devPath = ports[0]
logging.debug(f"Connecting to {devPath}")
# Note: we provide None for port here, because we will be opening it later
self.stream = serial.Serial(
None, 921600, exclusive=True, timeout=0.5, write_timeout=0)
# rts=False Needed to prevent TBEAMs resetting on OSX, because rts is connected to reset
self.stream.port = devPath
# HACK: If the platform driving the serial port is unable to leave the RTS pin in high-impedance
# 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__(
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow)
"""true if platform driving the serial port is Windows Subsystem for Linux 1."""
def _isWsl1(self):
# WSL1 identifies itself as Linux, but has a special char device at /dev/lxss for use with session control,
# e.g. /init. We should treat WSL1 as Windows for the RTS-driving hack because the underlying platfrom
# serial driver for the CP21xx still exhibits the buggy behavior.
# 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

@@ -0,0 +1,287 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: storeforward.proto
from google.protobuf import descriptor as _descriptor
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.FileDescriptor(
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(
name='RequestResponse',
full_name='StoreAndForward.RequestResponse',
filename=None,
file=DESCRIPTOR,
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,), {
'Statistics' : _reflection.GeneratedProtocolMessageType('Statistics', (_message.Message,), {
'DESCRIPTOR' : _STOREANDFORWARD_STATISTICS,
'__module__' : 'storeforward_pb2'
# @@protoc_insertion_point(class_scope:StoreAndForward.Statistics)
})
,
'History' : _reflection.GeneratedProtocolMessageType('History', (_message.Message,), {
'DESCRIPTOR' : _STOREANDFORWARD_HISTORY,
'__module__' : 'storeforward_pb2'
# @@protoc_insertion_point(class_scope:StoreAndForward.History)
})
,
'DESCRIPTOR' : _STOREANDFORWARD,
'__module__' : 'storeforward_pb2'
# @@protoc_insertion_point(class_scope:StoreAndForward)
})
_sym_db.RegisterMessage(StoreAndForward)
_sym_db.RegisterMessage(StoreAndForward.Statistics)
_sym_db.RegisterMessage(StoreAndForward.History)
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

View File

@@ -0,0 +1,174 @@
"""Stream Interface base class
"""
import logging
import threading
import time
import traceback
import serial
from .mesh_interface import MeshInterface
from .util import stripnl
START1 = 0x94
START2 = 0xc3
HEADER_LEN = 4
MAX_TO_FROM_RADIO_SIZE = 512
class StreamInterface(MeshInterface):
"""Interface class for meshtastic devices over a stream link (serial, TCP, etc)"""
def __init__(self, debugOut=None, noProto=False, connectNow=True):
"""Constructor, opens a connection to self.stream
Keyword Arguments:
devPath {string} -- A filepath to a device, i.e. /dev/ttyUSB0 (default: {None})
debugOut {stream} -- If a stream is provided, any debug serial output from the
device will be emitted to that stream. (default: {None})
Raises:
Exception: [description]
Exception: [description]
"""
if not hasattr(self, 'stream'):
raise Exception(
"StreamInterface is now abstract (to update existing code create SerialInterface instead)")
self._rxBuf = bytes() # empty
self._wantExit = False
# FIXME, figure out why daemon=True causes reader thread to exit too early
self._rxThread = threading.Thread(
target=self.__reader, args=(), daemon=True)
MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto)
# Start the reader thread after superclass constructor completes init
if connectNow:
self.connect()
if not noProto:
self.waitForConfig()
def connect(self):
"""Connect to our radio
Normally this is called automatically by the constructor, but if you
passed in connectNow=False you can manually start the reading thread later.
"""
# Send some bogus UART characters to force a sleeping device to wake, and
# if the reading statemachine was parsing a bad packet make sure
# we write enought start bytes to force it to resync (we don't use START1
# because we want to ensure it is looking for START1)
p = bytearray([START2] * 32)
self._writeBytes(p)
time.sleep(0.1) # wait 100ms to give device time to start running
self._rxThread.start()
self._startConfig()
if not self.noProto: # Wait for the db download if using the protocol
self._waitConnected()
def _disconnected(self):
"""We override the superclass implementation to close our port"""
MeshInterface._disconnected(self)
logging.debug("Closing our port")
# pylint: disable=E0203
if not self.stream is None:
# pylint: disable=E0203
self.stream.close()
# pylint: disable=W0201
self.stream = None
def _writeBytes(self, b):
"""Write an array of bytes to our stream and flush"""
if self.stream: # ignore writes when stream is closed
self.stream.write(b)
self.stream.flush()
def _readBytes(self, length):
"""Read an array of bytes from our stream"""
return self.stream.read(length)
def _sendToRadioImpl(self, toRadio):
"""Send a ToRadio protobuf to the device"""
logging.debug(f"Sending: {stripnl(toRadio)}")
b = toRadio.SerializeToString()
bufLen = len(b)
# We convert into a string, because the TCP code doesn't work with byte arrays
header = bytes([START1, START2, (bufLen >> 8) & 0xff, bufLen & 0xff])
self._writeBytes(header + b)
def close(self):
"""Close a connection to the device"""
logging.debug("Closing stream")
MeshInterface.close(self)
# pyserial cancel_read doesn't seem to work, therefore we ask the
# reader thread to close things for us
self._wantExit = True
if self._rxThread != threading.current_thread():
self._rxThread.join() # wait for it to exit
def __reader(self):
"""The reader thread that reads bytes from our stream"""
empty = bytes()
try:
while not self._wantExit:
# logging.debug("reading character")
b = self._readBytes(1)
# logging.debug("In reader loop")
# logging.debug(f"read returned {b}")
if len(b) > 0:
c = b[0]
ptr = len(self._rxBuf)
# Assume we want to append this byte, fixme use bytearray instead
self._rxBuf = self._rxBuf + b
if ptr == 0: # looking for START1
if c != START1:
self._rxBuf = empty # failed to find start
if self.debugOut is not None:
try:
self.debugOut.write(b.decode("utf-8"))
except:
self.debugOut.write('?')
elif ptr == 1: # looking for START2
if c != START2:
self._rxBuf = empty # failed to find start2
elif ptr >= HEADER_LEN - 1: # we've at least got a header
# big endian length follos header
packetlen = (self._rxBuf[2] << 8) + self._rxBuf[3]
if ptr == HEADER_LEN - 1: # we _just_ finished reading the header, validate length
if packetlen > MAX_TO_FROM_RADIO_SIZE:
self._rxBuf = empty # length ws out out bounds, restart
if len(self._rxBuf) != 0 and ptr + 1 >= packetlen + HEADER_LEN:
try:
self._handleFromRadio(self._rxBuf[HEADER_LEN:])
except Exception as ex:
logging.error(f"Error while handling message from radio {ex}")
traceback.print_exc()
self._rxBuf = empty
else:
# logging.debug(f"timeout")
pass
except serial.SerialException as ex:
if not self._wantExit: # We might intentionally get an exception during shutdown
logging.warning(f"Meshtastic serial port disconnected, disconnecting... {ex}")
except OSError as ex:
if not self._wantExit: # We might intentionally get an exception during shutdown
logging.error(f"Unexpected OSError, terminating meshtastic reader... {ex}")
except Exception as ex:
logging.error(f"Unexpected exception, terminating meshtastic reader... {ex}")
finally:
logging.debug("reader is exiting")
self._disconnected()

View File

@@ -0,0 +1,53 @@
"""TCPInterface class for interfacing with http endpoint
"""
import logging
import socket
from typing import AnyStr
from .stream_interface import StreamInterface
class TCPInterface(StreamInterface):
"""Interface class for meshtastic devices over a TCP link"""
def __init__(self, hostname: AnyStr, debugOut=None, noProto=False,
connectNow=True, portNumber=4403):
"""Constructor, opens a connection to a specified IP address/hostname
Keyword Arguments:
hostname {string} -- Hostname/IP address of the device to connect to
"""
logging.debug(f"Connecting to {hostname}")
server_address = (hostname, portNumber)
sock = socket.create_connection(server_address)
# Instead of wrapping as a stream, we use the native socket API
# self.stream = sock.makefile('rw')
self.stream = None
self.socket = sock
StreamInterface.__init__(
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow)
def close(self):
"""Close a connection to the device"""
logging.debug("Closing TCP stream")
StreamInterface.close(self)
# Sometimes the socket read might be blocked in the reader thread.
# Therefore we force the shutdown by closing the socket here
self._wantExit = True
if not self.socket is None:
try:
self.socket.shutdown(socket.SHUT_RDWR)
except:
pass # Ignore errors in shutdown, because we might have a race with the server
self.socket.close()
def _writeBytes(self, b):
"""Write an array of bytes to our stream and flush"""
self.socket.send(b)
def _readBytes(self, length):
"""Read an array of bytes from our stream"""
return self.socket.recv(length)

View File

@@ -1,11 +1,17 @@
"""With two radios connected serially, send and receive test
messages and report back if successful.
"""
import logging
from . import util
from . import SerialInterface, TCPInterface, BROADCAST_NUM
from pubsub import pub
import time
import sys
import threading, traceback
import traceback
from dotmap import DotMap
from pubsub import pub
import meshtastic.util
from .__init__ import BROADCAST_NUM
from .serial_interface import SerialInterface
from .tcp_interface import TCPInterface
"""The interfaces we are using for our tests"""
interfaces = None
@@ -31,7 +37,7 @@ def onReceive(packet, interface):
if p.decoded.portnum == "TEXT_MESSAGE_APP":
# We only care a about clear text packets
if receivedPackets != None:
if receivedPackets is not None:
receivedPackets.append(p)
@@ -75,18 +81,19 @@ def testSend(fromInterface, toInterface, isBroadcast=False, asBinary=False, want
else:
fromInterface.sendData((f"Binary {testNumber}").encode(
"utf-8"), toNode, wantAck=wantAck)
for sec in range(60): # max of 60 secs before we timeout
for _ in range(60): # max of 60 secs before we timeout
time.sleep(1)
if (len(receivedPackets) >= 1):
if len(receivedPackets) >= 1:
return True
return False # Failed to send
def runTests(numTests=50, wantAck=False, maxFailures=0):
"""Run the tests."""
logging.info(f"Running {numTests} tests with wantAck={wantAck}")
numFail = 0
numSuccess = 0
for i in range(numTests):
for _ in range(numTests):
global testNumber
testNumber = testNumber + 1
isBroadcast = True
@@ -102,24 +109,23 @@ def runTests(numTests=50, wantAck=False, maxFailures=0):
logging.info(
f"Test {testNumber} succeeded {numSuccess} successes {numFail} failures so far")
# if numFail >= 3:
# for i in interfaces:
# i.close()
# return
time.sleep(1)
if numFail > maxFailures:
logging.error("Too many failures! Test failed!")
return numFail
return False
return True
def testThread(numTests=50):
"""Test thread"""
logging.info("Found devices, starting tests...")
runTests(numTests, wantAck=True)
# Allow a few dropped packets
runTests(numTests, wantAck=False, maxFailures=5)
result = runTests(numTests, wantAck=True)
if result:
# Run another test
# Allow a few dropped packets
result = runTests(numTests, wantAck=False, maxFailures=1)
return result
def onConnection(topic=pub.AUTO_TOPIC):
@@ -128,21 +134,21 @@ def onConnection(topic=pub.AUTO_TOPIC):
def openDebugLog(portName):
"""Open the debug log file"""
debugname = "log" + portName.replace("/", "_")
logging.info(f"Writing serial debugging to {debugname}")
return open(debugname, 'w+', buffering=1)
return open(debugname, 'w+', buffering=1, encoding='utf8')
def testAll():
def testAll(numTests=5):
"""
Run a series of tests using devices we can find.
This is called from the cli with the "--test" option.
Raises:
Exception: If not enough devices are found
"""
ports = util.findPorts()
if (len(ports) < 2):
raise Exception("Must have at least two devices connected to USB")
ports = meshtastic.util.findPorts()
if len(ports) < 2:
meshtastic.util.our_exit("Warning: Must have at least two devices connected to USB.")
pub.subscribe(onConnection, "meshtastic.connection")
pub.subscribe(onReceive, "meshtastic.receive")
@@ -151,11 +157,13 @@ def testAll():
port, debugOut=openDebugLog(port), connectNow=True), ports))
logging.info("Ports opened, starting test")
testThread()
result = testThread(numTests)
for i in interfaces:
i.close()
return result
def testSimulator():
"""
@@ -166,7 +174,7 @@ def testSimulator():
Run with
python3 -c 'from meshtastic.test import testSimulator; testSimulator()'
"""
logging.basicConfig(level=logging.DEBUG if False else logging.INFO)
logging.basicConfig(level=logging.DEBUG)
logging.info("Connecting to simulator on localhost!")
try:
iface = TCPInterface("localhost")

View File

View File

@@ -0,0 +1,15 @@
"""Common pytest code (place for fixtures)."""
import argparse
import pytest
from meshtastic.__main__ import Globals
@pytest.fixture
def reset_globals():
"""Fixture to reset globals."""
parser = None
parser = argparse.ArgumentParser()
Globals.getInstance().reset()
Globals.getInstance().set_parser(parser)

View File

@@ -0,0 +1,12 @@
"""Meshtastic unit tests for ble_interface.py"""
import pytest
from ..ble_interface import BLEInterface
@pytest.mark.unit
def test_BLEInterface():
"""Test that we can instantiate a BLEInterface"""
iface = BLEInterface('foo', debugOut=True, noProto=True)
iface.close()

View File

@@ -0,0 +1,25 @@
"""Meshtastic unit tests for globals.py
"""
import pytest
from ..globals import Globals
@pytest.mark.unit
def test_globals_get_instaance():
"""Test that we can instantiate a Globals instance"""
ourglobals = Globals.getInstance()
ourglobals2 = Globals.getInstance()
assert ourglobals == ourglobals2
@pytest.mark.unit
def test_globals_there_can_be_only_one():
"""Test that we can cannot create two Globals instances"""
# if we have an instance, delete it
Globals.getInstance()
with pytest.raises(Exception) as pytest_wrapped_e:
# try to create another instance
Globals()
assert pytest_wrapped_e.type == Exception

View File

@@ -0,0 +1,38 @@
"""Meshtastic integration tests"""
import re
import subprocess
import pytest
@pytest.mark.int
def test_int_no_args():
"""Test without any args"""
return_value, out = subprocess.getstatusoutput('meshtastic')
assert re.match(r'usage: meshtastic', out)
assert return_value == 1
@pytest.mark.int
def test_int_version():
"""Test '--version'."""
return_value, out = subprocess.getstatusoutput('meshtastic --version')
assert re.match(r'[0-9]+\.[0-9]+\.[0-9]', out)
assert return_value == 0
@pytest.mark.int
def test_int_help():
"""Test '--help'."""
return_value, out = subprocess.getstatusoutput('meshtastic --help')
assert re.match(r'usage: meshtastic ', out)
assert return_value == 0
@pytest.mark.int
def test_int_support():
"""Test '--support'."""
return_value, out = subprocess.getstatusoutput('meshtastic --support')
assert re.search(r'System', out)
assert re.search(r'Python', out)
assert return_value == 0

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
"""Meshtastic unit tests for mesh_interface.py"""
import re
import pytest
from ..mesh_interface import MeshInterface
@pytest.mark.unit
def test_MeshInterface(capsys):
"""Test that we can instantiate a MeshInterface"""
iface = MeshInterface(noProto=True)
iface.showInfo()
iface.localNode.showInfo()
iface.showNodes()
iface.close()
out, err = capsys.readouterr()
assert re.search(r'Owner: None \(None\)', out, re.MULTILINE)
assert re.search(r'Nodes', 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 URL', out, re.MULTILINE)
assert err == ''

View File

@@ -0,0 +1,34 @@
"""Meshtastic unit tests for node.py"""
import re
from unittest.mock import patch, MagicMock
import pytest
from ..node import Node
from ..serial_interface import SerialInterface
from ..admin_pb2 import AdminMessage
@pytest.mark.unit
def test_node(capsys):
"""Test that we can instantiate a Node"""
anode = Node('foo', 'bar')
anode.showChannels()
anode.showInfo()
out, err = capsys.readouterr()
assert re.search(r'Preferences', out)
assert re.search(r'Channels', out)
assert re.search(r'Primary channel URL', out)
assert err == ''
@pytest.mark.unit
def test_node_reqquestConfig():
"""Test run requestConfig"""
iface = MagicMock(autospec=SerialInterface)
amesg = MagicMock(autospec=AdminMessage)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
with patch('meshtastic.admin_pb2.AdminMessage', return_value=amesg):
anode = Node(mo, 'bar')
anode.requestConfig()

View File

@@ -0,0 +1,49 @@
"""Meshtastic unit tests for serial_interface.py"""
import re
from unittest.mock import patch
import pytest
from ..serial_interface import SerialInterface
@pytest.mark.unit
@patch('serial.Serial')
@patch('meshtastic.util.findPorts', return_value=['/dev/ttyUSBfake'])
def test_SerialInterface_single_port(mocked_findPorts, mocked_serial):
"""Test that we can instantiate a SerialInterface with a single port"""
iface = SerialInterface(noProto=True)
iface.showInfo()
iface.localNode.showInfo()
iface.close()
mocked_findPorts.assert_called()
mocked_serial.assert_called()
@pytest.mark.unit
@patch('meshtastic.util.findPorts', return_value=[])
def test_SerialInterface_no_ports(mocked_findPorts, capsys):
"""Test that we can instantiate a SerialInterface with no ports"""
with pytest.raises(SystemExit) as pytest_wrapped_e:
SerialInterface(noProto=True)
mocked_findPorts.assert_called()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r'Warning: No Meshtastic devices detected', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
@patch('meshtastic.util.findPorts', return_value=['/dev/ttyUSBfake1', '/dev/ttyUSBfake2'])
def test_SerialInterface_multiple_ports(mocked_findPorts, capsys):
"""Test that we can instantiate a SerialInterface with two ports"""
with pytest.raises(SystemExit) as pytest_wrapped_e:
SerialInterface(noProto=True)
mocked_findPorts.assert_called()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r'Warning: Multiple serial ports were detected', out, re.MULTILINE)
assert err == ''

View File

@@ -0,0 +1,699 @@
"""Meshtastic smoke tests with a single device via USB"""
import re
import subprocess
import time
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 = 2
PAUSE_AFTER_REBOOT = 7
@pytest.mark.smoke1
def test_smoke1_reboot():
"""Test reboot"""
return_value, _ = subprocess.getstatusoutput('meshtastic --reboot')
assert return_value == 0
# pause for the radio to reset (10 seconds for the pause, and a few more seconds to be back up)
time.sleep(18)
@pytest.mark.smoke1
def test_smoke1_info():
"""Test --info"""
return_value, out = subprocess.getstatusoutput('meshtastic --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.smoke1
def test_smoke1_sendping():
"""Test --sendping"""
return_value, out = subprocess.getstatusoutput('meshtastic --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.smoke1
def test_get_with_invalid_setting():
"""Test '--get a_bad_setting'."""
return_value, out = subprocess.getstatusoutput('meshtastic --get a_bad_setting')
assert re.search(r'Choices in sorted order', out)
assert return_value == 0
@pytest.mark.smoke1
def test_set_with_invalid_setting():
"""Test '--set a_bad_setting'."""
return_value, out = subprocess.getstatusoutput('meshtastic --set a_bad_setting foo')
assert re.search(r'Choices in sorted order', out)
assert return_value == 0
@pytest.mark.smoke1
def test_ch_set_with_invalid_settingpatch_find_ports():
"""Test '--ch-set with a_bad_setting'."""
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set invalid_setting foo --ch-index 0')
assert re.search(r'Choices in sorted order', out)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_pos_fields():
"""Test --pos-fields (with some values POS_ALTITUDE POS_ALT_MSL POS_BATTERY)"""
return_value, out = subprocess.getstatusoutput('meshtastic --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 --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.smoke1
def test_smoke1_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 --test')
assert re.search(r'^Warning: Must have at least two devices', out, re.MULTILINE)
assert return_value == 1
@pytest.mark.smoke1
def test_smoke1_debug():
"""Test --debug"""
return_value, out = subprocess.getstatusoutput('meshtastic --info --debug')
assert re.search(r'^Owner', out, re.MULTILINE)
assert re.search(r'^DEBUG:root', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_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 --info --seriallog {filename}')
assert os.path.exists(f"{filename}")
assert return_value == 0
os.remove(f"{filename}")
@pytest.mark.smoke1
def test_smoke1_qr():
"""Test --qr"""
filename = 'tmpqr'
if os.path.exists(f"{filename}"):
os.remove(f"{filename}")
return_value, _ = subprocess.getstatusoutput(f'meshtastic --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.smoke1
def test_smoke1_nodes():
"""Test --nodes"""
return_value, out = subprocess.getstatusoutput('meshtastic --nodes')
assert re.match(r'Connected to radio', out)
assert re.search(r'^│ N │ User', out, re.MULTILINE)
assert re.search(r'^│ 1 │', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_send_hello():
"""Test --sendtext hello"""
return_value, out = subprocess.getstatusoutput('meshtastic --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.smoke1
def test_smoke1_port():
"""Test --port"""
# first, get the ports
ports = findPorts()
# hopefully there is just one
assert len(ports) == 1
port = ports[0]
return_value, out = subprocess.getstatusoutput(f'meshtastic --port {port} --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Owner', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_set_is_router_true():
"""Test --set is_router true"""
return_value, out = subprocess.getstatusoutput('meshtastic --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 --get is_router')
assert re.search(r'^is_router: True', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_set_location_info():
"""Test --setlat, --setlon and --setalt """
return_value, out = subprocess.getstatusoutput('meshtastic --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 --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.smoke1
def test_smoke1_set_is_router_false():
"""Test --set is_router false"""
return_value, out = subprocess.getstatusoutput('meshtastic --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 --get is_router')
assert re.search(r'^is_router: False', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_set_owner():
"""Test --set-owner name"""
# make sure the owner is not Joe
return_value, out = subprocess.getstatusoutput('meshtastic --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 --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 --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 --info')
assert re.search(r'Owner: Joe', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_set_team():
"""Test --set-team """
# unset the team
return_value, out = subprocess.getstatusoutput('meshtastic --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 --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 --info')
assert re.search(r'CYAN', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_ch_values():
"""Test --ch-longslow, --ch-longfast, --ch-mediumslow, --ch-mediumsfast,
--ch-shortslow, and --ch-shortfast arguments
"""
exp = {
'--ch-longslow': 'Bw125Cr48Sf4096',
# TODO: not sure why these fail thru tests, but ok manually
#'--ch-longfast': 'Bw31_25Cr48Sf512',
#'--ch-mediumslow': 'Bw250Cr46Sf2048',
#'--ch-mediumfast': 'Bw250Cr47Sf1024',
# TODO '--ch-shortslow': '?',
'--ch-shortfast': 'Bw500Cr45Sf128'
}
for key, val in exp.items():
print(key, val)
return_value, out = subprocess.getstatusoutput(f'meshtastic {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 --info')
assert re.search(val, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@pytest.mark.smoke1
def test_smoke1_ch_set_name():
"""Test --ch-set name"""
return_value, out = subprocess.getstatusoutput('meshtastic --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 --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 --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 --info')
assert re.search(r'MyChannel', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_ch_set_downlink_and_uplink():
"""Test -ch-set downlink_enabled X and --ch-set uplink_enabled X"""
return_value, out = subprocess.getstatusoutput('meshtastic --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)
return_value, out = subprocess.getstatusoutput('meshtastic --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 --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)
return_value, out = subprocess.getstatusoutput('meshtastic --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 --info')
assert re.search(r'uplinkEnabled', out, re.MULTILINE)
assert re.search(r'downlinkEnabled', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_ch_add_and_ch_del():
"""Test --ch-add"""
return_value, out = subprocess.getstatusoutput('meshtastic --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 --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 --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 --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.smoke1
def test_smoke1_ch_enable_and_disable():
"""Test --ch-enable and --ch-disable"""
return_value, out = subprocess.getstatusoutput('meshtastic --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 --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 --ch-disable')
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable --ch-index 1')
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --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 --ch-enable --ch-index 1')
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --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 --ch-del --ch-index 1')
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@pytest.mark.smoke1
def test_smoke1_ch_del_a_disabled_non_primary_channel():
"""Test --ch-del will work on a disabled non-primary channel."""
return_value, out = subprocess.getstatusoutput('meshtastic --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 --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 --ch-disable')
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1')
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --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.smoke1
def test_smoke1_attempt_to_delete_primary_channel():
"""Test that we cannot delete the PRIMARY channel."""
return_value, out = subprocess.getstatusoutput('meshtastic --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.smoke1
def test_smoke1_attempt_to_disable_primary_channel():
"""Test that we cannot disable the PRIMARY channel."""
return_value, out = subprocess.getstatusoutput('meshtastic --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.smoke1
def test_smoke1_attempt_to_enable_primary_channel():
"""Test that we cannot enable the PRIMARY channel."""
return_value, out = subprocess.getstatusoutput('meshtastic --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.smoke1
def test_smoke1_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 --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 --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 --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 --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 --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 --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 --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.smoke1
def test_smoke1_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 --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 --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 --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 --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 --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 --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 --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.smoke1
def test_smoke1_ch_set_modem_config():
"""Test --ch-set modem_config"""
return_value, out = subprocess.getstatusoutput('meshtastic --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 --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 --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 --info')
assert re.search(r'Bw31_25Cr48Sf512', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_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 --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 --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 --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 --info')
assert re.search('CgUYAyIBAQ', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_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 --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.smoke1
def test_smoke1_configure():
"""Test --configure"""
_ , out = subprocess.getstatusoutput(f"meshtastic --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.smoke1
def test_smoke1_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 --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 --info')
assert re.search(r'Owner: KI1234', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_set_wifi_settings():
"""Test --set wifi_ssid and --set wifi_password"""
return_value, out = subprocess.getstatusoutput('meshtastic --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 --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.smoke1
def test_smoke1_factory_reset():
"""Test factory reset"""
return_value, out = subprocess.getstatusoutput('meshtastic --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 radio may not be responsive after this, may need to do a manual reboot
# by pressing the button

View File

@@ -0,0 +1,23 @@
"""Meshtastic smoke tests with 2 devices connected via USB"""
import re
import subprocess
import pytest
@pytest.mark.smoke2
def test_smoke2_info():
"""Test --info with 2 devices connected serially"""
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.search(r'Warning: Multiple', out, re.MULTILINE)
assert return_value == 1
@pytest.mark.smoke2
def test_smoke2_test():
"""Test --test"""
return_value, out = subprocess.getstatusoutput('meshtastic --test')
assert re.search(r'Writing serial debugging', out, re.MULTILINE)
assert re.search(r'Ports opened', out, re.MULTILINE)
assert re.search(r'Running 5 tests', out, re.MULTILINE)
assert return_value == 0

View File

@@ -0,0 +1,23 @@
"""Meshtastic smoke tests a device setup with wifi.
Need to have run the following on an esp32 device:
meshtastic --set wifi_ssid 'foo' --set wifi_password 'sekret'
"""
import re
import subprocess
import pytest
@pytest.mark.smokewifi
def test_smokewifi_info():
"""Test --info"""
return_value, out = subprocess.getstatusoutput('meshtastic --info --host meshtastic.local')
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

View File

@@ -0,0 +1,14 @@
"""Meshtastic unit tests for stream_interface.py"""
import pytest
from ..stream_interface import StreamInterface
@pytest.mark.unit
def test_StreamInterface():
"""Test that we can instantiate a StreamInterface"""
with pytest.raises(Exception) as pytest_wrapped_e:
StreamInterface(noProto=True)
assert pytest_wrapped_e.type == Exception

View File

@@ -0,0 +1,26 @@
"""Meshtastic unit tests for tcp_interface.py"""
import re
from unittest.mock import patch
import pytest
from ..tcp_interface import TCPInterface
@pytest.mark.unit
def test_TCPInterface(capsys):
"""Test that we can instantiate a TCPInterface"""
with patch('socket.socket') as mock_socket:
iface = TCPInterface(hostname='localhost', noProto=True)
iface.showInfo()
iface.localNode.showInfo()
out, err = capsys.readouterr()
assert re.search(r'Owner: None \(None\)', out, re.MULTILINE)
assert re.search(r'Nodes', 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 URL', out, re.MULTILINE)
assert err == ''
assert mock_socket.called
iface.close()

View File

@@ -0,0 +1,128 @@
"""Meshtastic unit tests for util.py"""
import re
import pytest
from meshtastic.util import fixme, stripnl, pskToString, our_exit, support_info, genPSK256, fromStr, fromPSK
@pytest.mark.unit
def test_genPSK256():
"""Test genPSK256"""
assert genPSK256() != ''
@pytest.mark.unit
def test_fromStr():
"""Test fromStr"""
assert fromStr('') == b''
assert fromStr('0x12') == b'\x12'
assert fromStr('t')
assert fromStr('T')
assert fromStr('true')
assert fromStr('True')
assert fromStr('yes')
assert fromStr('Yes')
assert fromStr('f') is False
assert fromStr('F') is False
assert fromStr('false') is False
assert fromStr('False') is False
assert fromStr('no') is False
assert fromStr('No') is False
assert fromStr('100.01') == 100.01
assert fromStr('123') == 123
assert fromStr('abc') == 'abc'
@pytest.mark.unit
def test_fromPSK():
"""Test fromPSK"""
assert fromPSK('random') != ''
assert fromPSK('none') == b'\x00'
assert fromPSK('default') == b'\x01'
assert fromPSK('simple22') == b'\x17'
assert fromPSK('trash') == 'trash'
@pytest.mark.unit
def test_stripnl():
"""Test stripnl"""
assert stripnl('') == ''
assert stripnl('a\n') == 'a'
assert stripnl(' a \n ') == 'a'
assert stripnl('a\nb') == 'a b'
@pytest.mark.unit
def test_pskToString_empty_string():
"""Test pskToString empty string"""
assert pskToString('') == 'unencrypted'
@pytest.mark.unit
def test_pskToString_string():
"""Test pskToString string"""
assert pskToString('hunter123') == 'secret'
@pytest.mark.unit
def test_pskToString_one_byte_zero_value():
"""Test pskToString one byte that is value of 0"""
assert pskToString(bytes([0x00])) == 'unencrypted'
@pytest.mark.unit
def test_pskToString_one_byte_non_zero_value():
"""Test pskToString one byte that is non-zero"""
assert pskToString(bytes([0x01])) == 'default'
@pytest.mark.unit
def test_pskToString_many_bytes():
"""Test pskToString many bytes"""
assert pskToString(bytes([0x02, 0x01])) == 'secret'
@pytest.mark.unit
def test_pskToString_simple():
"""Test pskToString simple"""
assert pskToString(bytes([0x03])) == 'simple2'
@pytest.mark.unit
def test_our_exit_zero_return_value():
"""Test our_exit with a zero return value"""
with pytest.raises(SystemExit) as pytest_wrapped_e:
our_exit("Warning: Some message", 0)
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 0
@pytest.mark.unit
def test_our_exit_non_zero_return_value():
"""Test our_exit with a non-zero return value"""
with pytest.raises(SystemExit) as pytest_wrapped_e:
our_exit("Error: Some message", 1)
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
@pytest.mark.unit
def test_fixme():
"""Test fixme"""
with pytest.raises(Exception) as pytest_wrapped_e:
fixme("some exception")
assert pytest_wrapped_e.type == Exception
@pytest.mark.unit
def test_support_info(capsys):
"""Test support_info"""
support_info()
out, err = capsys.readouterr()
assert re.search(r'System', out, re.MULTILINE)
assert re.search(r'Platform', out, re.MULTILINE)
assert re.search(r'Machine', out, re.MULTILINE)
assert re.search(r'Executable', out, re.MULTILINE)
assert err == ''

View File

@@ -1,4 +1,5 @@
# code for IP tunnel over a mesh
"""Code for IP tunnel over a mesh
# Note python-pytuntap was too buggy
# using pip3 install pytap2
# make sure to "sudo setcap cap_net_admin+eip /usr/bin/python3.8" so python can access tun device without being root
@@ -12,11 +13,15 @@
# ping -i 30 -W 30 10.115.64.152
# FIXME: use a more optimal MTU
"""
from . import portnums_pb2
from pubsub import pub
import logging
import threading
from pubsub import pub
from pytap2 import TapDevice
from . import portnums_pb2
# A new non standard log level that is lower level than DEBUG
LOG_TRACE = 5
@@ -83,8 +88,8 @@ class Tunnel:
global tunnelInstance
tunnelInstance = self
logging.info(
"Starting IP to mesh tunnel (you must be root for this *pre-alpha* feature to work). Mesh members:")
logging.info("Starting IP to mesh tunnel (you must be root for this *pre-alpha* "\
"feature to work). Mesh members:")
pub.subscribe(onTunnelReceive, "meshtastic.receive.data.IP_TUNNEL_APP")
myAddr = self._nodeNumToIp(self.iface.myInfo.my_node_num)
@@ -96,7 +101,6 @@ class Tunnel:
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
from pytap2 import TapDevice
self.tun = TapDevice(name="mesh")
self.tun.up()
self.tun.ifconfig(address=myAddr, netmask=netmask, mtu=200)
@@ -106,14 +110,15 @@ class Tunnel:
self._rxThread.start()
def onReceive(self, packet):
"""onReceive"""
p = packet["decoded"]["payload"]
if packet["from"] == self.iface.myInfo.my_node_num:
logging.debug("Ignoring message we sent")
else:
logging.debug(
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), but this provides
# useful debug printing on types of packets received
# 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
if not self._shouldFilterPacket(p):
self.tun.write(p)
@@ -132,8 +137,8 @@ class Tunnel:
icmpType = p[20]
icmpCode = p[21]
checksum = p[22:24]
logging.debug(
f"forwarding ICMP message src={ipstr(srcaddr)}, dest={ipstr(destAddr)}, type={icmpType}, code={icmpCode}, checksum={checksum}")
# pylint: disable=line-too-long
logging.debug(f"forwarding ICMP message src={ipstr(srcaddr)}, dest={ipstr(destAddr)}, type={icmpType}, code={icmpCode}, checksum={checksum}")
# reply to pings (swap src and dest but keep rest of packet unchanged)
#pingback = p[:12]+p[16:20]+p[12:16]+p[20:]
# tap.write(pingback)
@@ -152,14 +157,12 @@ class Tunnel:
destport = readnet_u16(p, subheader + 2)
if destport in tcpBlacklist:
ignore = True
logging.log(
LOG_TRACE, f"ignoring blacklisted TCP port {destport}")
logging.log(LOG_TRACE, f"ignoring blacklisted TCP port {destport}")
else:
logging.debug(
f"forwarding tcp srcport={srcport}, destport={destport}")
logging.debug(f"forwarding tcp srcport={srcport}, destport={destport}")
else:
logging.warning(
f"forwarding unexpected protocol 0x{protocol:02x}, src={ipstr(srcaddr)}, dest={ipstr(destAddr)}")
logging.warning(f"forwarding unexpected protocol 0x{protocol:02x}, "\
"src={ipstr(srcaddr)}, dest={ipstr(destAddr)}")
return ignore
@@ -195,13 +198,12 @@ class Tunnel:
"""Forward the provided IP packet into the mesh"""
nodeId = self._ipToNodeId(destAddr)
if nodeId is not None:
logging.debug(
f"Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}")
logging.debug(f"Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}")
self.iface.sendData(
p, nodeId, portnums_pb2.IP_TUNNEL_APP, wantAck=False)
else:
logging.warning(
f"Dropping packet because no node found for destIP={ipstr(destAddr)}")
logging.warning(f"Dropping packet because no node found for destIP={ipstr(destAddr)}")
def close(self):
"""Close"""
self.tun.close()

View File

@@ -1,21 +1,95 @@
from collections import defaultdict
import serial, traceback
import serial.tools.list_ports
"""Utility functions.
"""
import traceback
from queue import Queue
import threading, sys, time, logging
import os
import sys
import time
import platform
import logging
import threading
import serial
import serial.tools.list_ports
import pkg_resources
"""Some devices such as a seger jlink we never want to accidentally open"""
blacklistVids = dict.fromkeys([0x1366])
def genPSK256():
"""Generate a random preshared key"""
return os.urandom(32)
def fromPSK(valstr):
"""A special version of fromStr that assumes the user is trying to set a PSK.
In that case we also allow "none", "default" or "random" (to have python generate one), or simpleN
"""
if valstr == "random":
return genPSK256()
elif valstr == "none":
return bytes([0]) # Use the 'no encryption' PSK
elif valstr == "default":
return bytes([1]) # Use default channel psk
elif valstr.startswith("simple"):
# Use one of the single byte encodings
return bytes([int(valstr[6:]) + 1])
else:
return fromStr(valstr)
def fromStr(valstr):
"""Try to parse as int, float or bool (and fallback to a string as last resort)
Returns: an int, bool, float, str or byte array (for strings of hex digits)
Args:
valstr (string): A user provided string
"""
if len(valstr) == 0: # Treat an emptystring as an empty bytes
val = bytes()
elif valstr.startswith('0x'):
# if needed convert to string with asBytes.decode('utf-8')
val = bytes.fromhex(valstr[2:])
elif valstr.lower() in {"t", "true", "yes"}:
val = True
elif valstr.lower() in {"f", "false", "no"}:
val = False
else:
try:
val = int(valstr)
except ValueError:
try:
val = float(valstr)
except ValueError:
val = valstr # Not a float or an int, assume string
return val
def pskToString(psk: bytes):
"""Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string"""
if len(psk) == 0:
return "unencrypted"
elif len(psk) == 1:
b = psk[0]
if b == 0:
return "unencrypted"
elif b == 1:
return "default"
else:
return f"simple{b - 1}"
else:
return "secret"
def stripnl(s):
"""remove newlines from a string (and remove extra whitespace)"""
"""Remove newlines from a string (and remove extra whitespace)"""
s = str(s).replace("\n", " ")
return ' '.join(s.split())
def fixme(message):
"""Raise an exception for things that needs to be fixed"""
raise Exception(f"FIXME: {message}")
@@ -34,7 +108,7 @@ def findPorts():
list -- a list of device paths
"""
l = list(map(lambda port: port.device,
filter(lambda port: port.vid != None and port.vid not in blacklistVids,
filter(lambda port: port.vid is not None and port.vid not in blacklistVids,
serial.tools.list_ports.comports())))
l.sort()
return l
@@ -48,6 +122,7 @@ class dotdict(dict):
class Timeout:
"""Timeout class"""
def __init__(self, maxSecs=20):
self.expireTime = 0
self.sleepInterval = 0.1
@@ -77,6 +152,7 @@ class DeferredExecution():
self.thread.start()
def queueWork(self, runnable):
""" Queue up the work"""
self.queue.put(runnable)
def _run(self):
@@ -88,3 +164,32 @@ class DeferredExecution():
logging.error(
f"Unexpected error in deferred execution {sys.exc_info()[0]}")
print(traceback.format_exc())
def our_exit(message, return_value = 1):
"""Print the message and return a value.
return_value defaults to 1 (non-successful)
"""
print(message)
sys.exit(return_value)
def support_info():
"""Print out info that helps troubleshooting of the cli."""
print('')
print('If having issues with meshtastic cli or python library')
print('or wish to make feature requests, visit:')
print('https://github.com/meshtastic/Meshtastic-python/issues')
print('When adding an issue, be sure to include the following info:')
print(' System: {0}'.format(platform.system()))
print(' Platform: {0}'.format(platform.platform()))
print(' Release: {0}'.format(platform.uname().release))
print(' Machine: {0}'.format(platform.uname().machine))
print(' Encoding (stdin): {0}'.format(sys.stdin.encoding))
print(' Encoding (stdout): {0}'.format(sys.stdout.encoding))
print(' meshtastic: v{0}'.format(pkg_resources.require('meshtastic')[0].version))
print(' Executable: {0}'.format(sys.argv[0]))
print(' Python: {0} {1} {2}'.format(platform.python_version(),
platform.python_implementation(), platform.python_compiler()))
print('')
print('Please add the output from the command: meshtastic --info')

2
proto

Submodule proto updated: f5b3d0643b...10e6857b1b

10
pytest.ini Normal file
View File

@@ -0,0 +1,10 @@
[pytest]
addopts = -m "not smoke1 and not smoke2 and not smokewifi"
markers =
unit: marks tests as unit tests
int: marks tests as integration tests
smoke1: runs smoke tests on a single device 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

18
requirements.txt Normal file
View File

@@ -0,0 +1,18 @@
markdown
pyserial
protobuf
dotmap
pexpect
pyqrcode
pygatt
tabulate
timeago
webencodings
pyparsing
twine
autopep8
pylint
pytest
pytest-cov
pyyaml
pytap2

View File

@@ -12,7 +12,7 @@ with open("README.md", "r") as fh:
# This call to setup() does all the work
setup(
name="meshtastic",
version="1.2.40",
version="1.2.46",
description="Python API & client shell for talking to Meshtastic devices",
long_description=long_description,
long_description_content_type="text/markdown",
@@ -29,7 +29,7 @@ setup(
include_package_data=True,
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",
"pygatt>=4.0.5", "tabulate>=0.8.9", "timeago>=1.0.15"],
"pygatt>=4.0.5", "tabulate>=0.8.9", "timeago>=1.0.15", "pyyaml"],
extras_require={
'tunnel': ["pytap2>=2.0.0"]
},