Compare commits

...

100 Commits

Author SHA1 Message Date
Sacha Weatherstone
69bb8bcca2 Fx protobuf path 2021-12-30 16:52:25 +11:00
Sacha Weatherstone
bb2ea17371 Fix action name 2021-12-30 16:49:55 +11:00
Sacha Weatherstone
b51af8a070 Create update_protobufs 2021-12-30 16:48:20 +11:00
mkinney
aede26694c Merge pull request #188 from mkinney/work_on_serial_issue
revamp the serial connection to avoid Tbeam reboots
2021-12-29 21:41:59 -08:00
Mike Kinney
8e1010e9f2 ignore two checks for Windows smoke1 testing 2021-12-29 21:21:24 -08:00
Mike Kinney
ce2d9f5728 yet another attempt 2021-12-29 21:13:20 -08:00
Mike Kinney
9879e9f2df try yet another way 2021-12-29 21:00:22 -08:00
Mike Kinney
e79faf93d0 try a different way 2021-12-29 20:57:57 -08:00
Mike Kinney
181c04716a accidentally dropped an arg 2021-12-29 20:55:31 -08:00
Mike Kinney
c7d981ec35 fix typo 2021-12-29 20:51:46 -08:00
Mike Kinney
75fe7622a4 deal with windows on the serial issue 2021-12-29 20:49:19 -08:00
Mike Kinney
5dc800f9a3 deal with windows on smoke1 test 2021-12-29 20:39:34 -08:00
Mike Kinney
c7d3f9f787 close seriallog if we have one 2021-12-29 20:35:50 -08:00
Mike Kinney
c70d36d2cd revamp the serial connection to avoid Tbeam reboots 2021-12-29 19:24:26 -08:00
Jm Casler
f239c23d9a Merge branch 'master' of https://github.com/meshtastic/Meshtastic-python 2021-12-29 14:18:35 -08:00
Jm Casler
ac5d729cdf Bumped to 1.2.47. 2021-12-29 14:18:34 -08:00
mkinney
047f43534f Merge pull request #187 from mkinney/bump_version
bump version
2021-12-29 13:29:28 -08:00
Mike Kinney
c26e030f1f bump version 2021-12-29 13:28:08 -08:00
mkinney
1a61a5ccdc Update README.md 2021-12-29 11:01:13 -08:00
mkinney
eec3f722dc Merge pull request #186 from mkinney/docs
migrate docs to main project docs
2021-12-29 10:55:12 -08:00
Mike Kinney
adb797d453 migrate docs to main project docs 2021-12-29 10:53:41 -08:00
Jm Casler
7d75fd61d1 updating proto submodule to latest 2021-12-29 09:22:50 -08:00
Jm Casler
3abf82fc0a updating proto submodule to latest 2021-12-28 23:33:14 -08:00
Jm Casler
f56c7a0ef1 updating proto submodule to latest 2021-12-28 23:29:32 -08:00
Sacha Weatherstone
e1941bc6f5 Remove old docs & add vercel badge to readme 2021-12-29 18:15:38 +11:00
Sacha Weatherstone
7bd0565e21 Fix pypubsub in requirements.txt 2021-12-29 17:48:36 +11:00
Sacha Weatherstone
3f1165adc3 Add pubsub to requirements.txt 2021-12-29 17:42:57 +11:00
Sacha Weatherstone
e611a89392 Add pdoc3 to requirements.txt 2021-12-29 17:38:14 +11:00
mkinney
209a0669de Merge pull request #183 from mkinney/more_doc
regen doc with overriding the pdoc _is_public()
2021-12-28 22:12:04 -08:00
Mike Kinney
b6a9dec824 regen doc with overriding the pdoc _is_public() 2021-12-28 22:10:11 -08:00
mkinney
40afc56b2e Merge pull request #182 from mkinney/update_doc
fix some comments for doc
2021-12-28 21:15:02 -08:00
Mike Kinney
ecf68d5544 fix some comments for doc 2021-12-28 21:08:28 -08:00
mkinney
fc88d2bff3 Merge pull request #181 from linagee/master
Ham, not HAM
2021-12-28 12:41:53 -08:00
linagee
06d37914c3 Merge pull request #1 from linagee/Ham-not-HAM
Ham, not HAM
2021-12-27 21:10:06 -07:00
linagee
4575c58b97 Ham, not HAM
Discovered in 1ffa55d39a
2021-12-27 21:07:17 -07:00
mkinney
f8dfa3f0fd Merge pull request #180 from mkinney/oops_need_this_for_smoke1
meant to add this change to the last PR
2021-12-25 23:28:01 -08:00
Mike Kinney
dda4d4b653 meant to add this change to the last PR 2021-12-25 23:24:28 -08:00
mkinney
4a864d2b0e Merge pull request #179 from mkinney/more_boring_unit_testing
add more unit tests for onReceive in main
2021-12-25 22:26:18 -08:00
Mike Kinney
580c081303 captured packets and simulate what server would send 2021-12-25 16:19:13 -08:00
Mike Kinney
988540c77d move the slow unit tests into own group 2021-12-25 13:09:23 -08:00
Mike Kinney
a243fab9af figured out how to unit test __reader() 2021-12-25 11:39:04 -08:00
Mike Kinney
9fea479af4 do not add the test.py to the code coverage 2021-12-24 15:08:56 -08:00
Mike Kinney
e1f0c7f154 add tests for setPref() 2021-12-24 14:56:15 -08:00
Mike Kinney
7ef6465b5f add some tests for getPref() 2021-12-24 14:42:39 -08:00
Mike Kinney
87b95c5893 add more unit tests for onReceive in main 2021-12-24 13:48:10 -08:00
mkinney
3d00d05a89 Merge pull request #178 from mkinney/add_gpio_tests
Add gpio tests
2021-12-24 13:10:43 -08:00
Mike Kinney
c9ff1d9ad0 remove extraneous print 2021-12-24 13:02:49 -08:00
Mike Kinney
901be0ae04 wip; testing gpio read not working for me on real device; worked on gpio watch 2021-12-24 10:17:45 -08:00
Mike Kinney
fee052892b got gpio read to work and one unit test 2021-12-23 14:35:48 -08:00
mkinney
942600f399 Merge pull request #177 from mkinney/fix_channel_change
needed to do some actual testing to see error in code
2021-12-23 09:34:06 -08:00
Mike Kinney
a56a92b166 needed to do some actual testing to see error in code 2021-12-23 09:30:23 -08:00
mkinney
01ac345cf9 Merge pull request #176 from mkinney/yet_more_testing
add more info/checking on --sendtext and --ch-index; wrote helper met…
2021-12-23 09:20:37 -08:00
Mike Kinney
cbd41efb19 add unit test for catchAndIgnore() 2021-12-23 00:40:21 -08:00
Mike Kinney
276b2762c8 add more info/checking on --sendtext and --ch-index; wrote helper method and tests 2021-12-23 00:03:32 -08:00
mkinney
d21eaf9392 Merge pull request #175 from mkinney/continue_working_on_unit_tests
add unit tests for toRadio, sendData() and sendPosition(); found and …
2021-12-22 16:29:45 -08:00
Mike Kinney
0f0a978692 minor refactor now that there is a fixture 2021-12-22 16:20:09 -08:00
Mike Kinney
4428ccfe59 add unit tests for getMyUser(), getLongName(), getShortName(), and sendPacket() 2021-12-22 16:15:16 -08:00
Mike Kinney
dbb5af1f35 added unit test for test_onResponseRequestChannel() 2021-12-22 12:11:27 -08:00
Mike Kinney
586a3eb463 wip extracting onResponse() closures to class methods so they can be tested 2021-12-22 11:11:17 -08:00
Mike Kinney
2eeffdcd49 add unit tests for _requestChannel() 2021-12-22 09:21:47 -08:00
Mike Kinney
002a7caf38 add more unit tests to deleteChannel() and writeConfig() 2021-12-22 09:10:39 -08:00
Mike Kinney
8bf1dd6dcb add unit tests for showChannels(), deleteChannel(), getChannelByName(), getDisabledChannel(), getAdminChannelIndex(), turnOffEncryptionOnPrimaryChannel(), and writeConfig() 2021-12-21 22:13:02 -08:00
Mike Kinney
8e578c3b24 add unit test for getMyNodeInfo() 2021-12-21 16:27:43 -08:00
Mike Kinney
2919930473 unit tests for sendPacket() 2021-12-21 15:21:30 -08:00
Mike Kinney
302f52469a add unit tests for toRadio, sendData() and sendPosition(); found and fixed Exception without raise 2021-12-21 14:47:05 -08:00
mkinney
71c8ca5381 Merge pull request #174 from mkinney/import_issues
add examples; add Makefile
2021-12-21 11:33:53 -08:00
Mike Kinney
0701d400ff add examples; add Makefile 2021-12-21 11:21:37 -08:00
mkinney
1acc193248 Merge pull request #171 from mkinney/more_testing
add unit tests for RemoteHardwareClient()
2021-12-20 23:27:45 -08:00
Mike Kinney
d82f197c06 remove the message from the sendping option 2021-12-20 23:23:27 -08:00
Mike Kinney
a35f3a4e7b revert changes to onResponse() methods 2021-12-20 23:19:38 -08:00
Mike Kinney
036ce8258f ignore long binary line 2021-12-20 22:25:19 -08:00
Mike Kinney
1c8a5d8c5b work around for issue 172 2021-12-20 22:11:52 -08:00
Mike Kinney
266ed868d2 Merge remote-tracking branch 'upstream/master' into more_testing 2021-12-20 21:22:01 -08:00
Mike Kinney
29d3723094 not sure what happened to example_config.yaml file 2021-12-20 21:21:20 -08:00
Mike Kinney
4597ea1898 add channel index to send text 2021-12-20 14:00:56 -08:00
Mike Kinney
23d946baaa wip on validating --set-owner on remote node 2021-12-19 17:34:39 -08:00
Jm Casler
900ce6ab5e updating proto submodule to latest 2021-12-19 13:54:53 -05:00
Jm Casler
19e5ecb86d updating proto submodule to latest 2021-12-18 15:51:46 -05:00
Mike Kinney
c1cab5e62f add unit tests for RemoteHardwareClient() 2021-12-17 14:37:30 -08:00
mkinney
35f10f1490 Merge pull request #170 from mkinney/add_configure_dump
Add configure dump
2021-12-17 13:01:52 -08:00
Mike Kinney
c63dc09e5d rename to --export-config 2021-12-17 12:47:27 -08:00
Mike Kinney
b47393d62d refactor and write unit test 2021-12-17 10:55:51 -08:00
Mike Kinney
8fd00efe8b working initial 2021-12-17 10:19:48 -08:00
mkinney
0410a427b6 Merge pull request #169 from mkinney/fix_smoke_tests
now that the firmware is updated, can fix smoke test
2021-12-17 09:10:17 -08:00
Mike Kinney
e869e3c72e now that the firmware is updated, can fix smoke test 2021-12-17 09:08:09 -08:00
mkinney
0987db3d1d Merge pull request #167 from mkinney/work_on_unit_tests
Work on unit tests
2021-12-17 08:03:10 -08:00
Mike Kinney
95f7f560ba add unit tests for reboot(), exitSimulator(), and setURL() 2021-12-16 23:23:43 -08:00
Mike Kinney
fd6deedd8d add unit tests for setOwner() 2021-12-16 22:47:10 -08:00
Mike Kinney
fc637ee6ae add to test for showNodes() 2021-12-16 22:17:08 -08:00
Mike Kinney
e43d9f5318 add unit tests for _handleFromRadio() and sendPosition() 2021-12-16 19:12:55 -08:00
Mike Kinney
2a0c9f0669 add mesh_interface unit tests for handlePacketFromRadio and getNode 2021-12-16 17:02:40 -08:00
Mike Kinney
f7753b2ce8 Merge branch 'master' into work_on_unit_tests 2021-12-16 12:33:43 -08:00
mkinney
ca032e485c Merge pull request #166 from mkinney/validate_cli
see if we can validate if we can run the cli from just the build
2021-12-16 12:28:14 -08:00
Mike Kinney
df194389e1 add lib back in now that validation worked 2021-12-16 12:26:24 -08:00
Mike Kinney
1b60676365 temp remove a lib to see if the validation fails 2021-12-16 12:24:40 -08:00
Mike Kinney
8aa165ec93 see if we can validate if we can run the cli from just the build 2021-12-16 12:21:49 -08:00
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
Mike Kinney
6b0521003c wip on adding unit tests to mesh_interface 2021-12-16 11:51:25 -08:00
66 changed files with 9899 additions and 2823 deletions

View File

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

View File

@@ -32,3 +32,16 @@ jobs:
run: pylint meshtastic
- name: Run tests with pytest
run: pytest --cov=meshtastic
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Python 3
uses: actions/setup-python@v1
with:
python-version: 3.9
- name: Install meshtastic from local
run: |
pip3 install .
which meshtastic
meshtastic --version

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

@@ -0,0 +1,24 @@
name: "Update protobufs"
on: workflow_dispatch
jobs:
update-protobufs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: true
- name: Update Submodule
run: |
git pull --recurse-submodules
git submodule update --remote --recursive
- name: Commit update
run: |
git config --global user.name 'github-actions'
git config --global user.email 'bot@noreply.github.com'
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
git add proto
git commit -m "Update protobuf submodule" && git push || echo "No changes to commit"

5
.gitignore vendored
View File

@@ -1,4 +1,5 @@
README
docs/
build
dist
*.egg-info
@@ -9,3 +10,7 @@ nanopb-0.4.4
.coverage
*.py-E
venv/
*pyc
.DS_Store
__pycache__
examples/__pycache__

View File

@@ -23,7 +23,7 @@ ignore-patterns=mqtt_pb2.py,channel_pb2.py,environmental_measurement_pb2.py,admi
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
#
disable=invalid-name,fixme,logging-fstring-interpolation,too-many-statements,too-many-branches,too-many-locals,no-member,f-string-without-interpolation,protected-access,no-self-use,pointless-string-statement,too-few-public-methods,consider-using-f-string,broad-except,no-else-return,unused-argument,global-statement,global-variable-not-assigned,too-many-boolean-expressions,no-else-raise,bare-except
disable=invalid-name,fixme,logging-fstring-interpolation,too-many-statements,too-many-branches,too-many-locals,no-member,f-string-without-interpolation,protected-access,no-self-use,pointless-string-statement,too-few-public-methods,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,c-extension-no-member
[BASIC]

32
Makefile Normal file
View File

@@ -0,0 +1,32 @@
# only run the fast unit tests
test:
pytest -m unit
# local install
install:
pip install .
# generate the docs (for local use)
docs:
pdoc3 --html -f --output-dir docs meshtastic
# lint the codebase
lint:
pylint meshtastic
# show the slowest unit tests
slow:
pytest --durations=0
# run the coverage report and open results in a browser
cov:
pytest --cov-report html --cov=meshtastic
# on mac, this will open the coverage report in a browser
open htmlcov/index.html
# run cli examples
examples: FORCE
pytest -mexamples
# Makefile hack to get the examples to always run
FORCE: ;

244
README.md
View File

@@ -1,249 +1,13 @@
# 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.
Full documentation including examples [here](https://meshtastic.github.io/Meshtastic-python/meshtastic/index.html).
Full documentation including examples [here](https://meshtastic.org/docs/software/python/python-installation).
Installation is easily done through the Python package installer pip (note, you must use pip version 20 or later):
The library api is documented [here](https://meshtastic-python.vercel.app/meshtastic/index.html)
- check that your computer has the required serial drivers installed, if not download them from [here](https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers).
- 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
interface.sendText("hello mesh") # or sendData to send binary data, see documentations for other options.
interface.close()
```
For the rough notes/implementation plan see [TODO](https://github.com/meshtastic/Meshtastic-python/blob/master/TODO.md).
## Command line tool
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/).
To display a (partial) list of the available commands:
```
meshtastic -h
```
### Changing device settings
You can also use this tool to set any of the device parameters which are stored in persistent storage. For instance, here's how to set the device
to keep the bluetooth link alive for eight hours (any usage of the bluetooth protcol from your phone will reset this timer)
```
meshtastic --set wait_bluetooth_secs 28800
Connected to radio...
Setting preference wait_bluetooth_secs to 28800
Writing modified preferences to device...
```
Or to set a node at a fixed position and never power up the GPS.
```
meshtastic --setlat 25.2 --setlon -16.8 --setalt 120
```
Or to configure an ESP32 based board to join a wifi network as a station (wifi support in the device code is coming soon):
```
meshtastic --set wifi_ap_mode false --set wifi_ssid mywifissid --set wifi_password mywifipsw
```
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
The channel settings can be changed similiarly. Either by using a standard (sharable) meshtastic URL or you can set partiular channel parameters (for advanced users).
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 --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)
You can even set the channel preshared key to a particular AES128 or AES256 sequence.
```
meshtastic --ch-set psk 0x1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b --info
```
Use "--ch-set psk none" to turn off encryption.
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 "--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
Meshtastic is designed to be used without a radio operator license. If you do have a license you can set your operator ID and turn off encryption with:
```
meshtastic --port /dev/ttyUSB1 --set-ham KI1345
Connected to radio
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.
### [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 $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:
```
pip3 install -U --pre pyserial
```
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 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
```
[![Powered by Vercel](https://raw.githubusercontent.com/abumalick/powered-by-vercel/master/powered-by-vercel.svg)](https://vercel.com?utm_source=meshtastic&utm_campaign=oss)

View File

@@ -1 +1,4 @@
pdoc3 --html -f --output-dir docs meshtastic
# Note: Docs are generated from this command below, albeit from Vercel.
# The docs/ dir is not used and is no longer commited.
# see sachaw if you have questions
pdoc3 --html -f --output-dir docs meshtastic

View File

@@ -1 +0,0 @@
<meta http-equiv="refresh" content="0; url=./meshtastic/index.html" />

View File

@@ -40,8 +40,8 @@ _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 mesh_pb2 as mesh__pb2
DESCRIPTOR = _descriptor.FileDescriptor(
@@ -49,9 +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;,
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;
serialized_pb=b&#39;\n\x0b\x61\x64min.proto\x1a\rchannel.proto\x1a\x11radioconfig.proto\x1a\nmesh.proto\&#34;\xbd\x03\n\x0c\x41\x64minMessage\x12!\n\tset_radio\x18\x01 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1a\n\tset_owner\x18\x02 \x01(\x0b\x32\x05.UserH\x00\x12\x1f\n\x0bset_channel\x18\x03 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_radio_request\x18\x04 \x01(\x08H\x00\x12*\n\x12get_radio_response\x18\x05 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1d\n\x13get_channel_request\x18\x06 \x01(\rH\x00\x12(\n\x14get_channel_response\x18\x07 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_owner_request\x18\x08 \x01(\x08H\x00\x12#\n\x12get_owner_response\x18\t \x01(\x0b\x32\x05.UserH\x00\x12\x1d\n\x13\x63onfirm_set_channel\x18 \x01(\x08H\x00\x12\x1b\n\x11\x63onfirm_set_radio\x18! \x01(\x08H\x00\x12\x18\n\x0e\x65xit_simulator\x18\&#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=[channel__pb2.DESCRIPTOR,mesh__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,])
dependencies=[channel__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,mesh__pb2.DESCRIPTOR,])
@@ -113,28 +113,42 @@ _ADMINMESSAGE = _descriptor.Descriptor(
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;confirm_set_channel&#39;, full_name=&#39;AdminMessage.confirm_set_channel&#39;, index=7,
name=&#39;get_owner_request&#39;, full_name=&#39;AdminMessage.get_owner_request&#39;, index=7,
number=8, 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=&#39;get_owner_response&#39;, full_name=&#39;AdminMessage.get_owner_response&#39;, index=8,
number=9, 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=&#39;confirm_set_channel&#39;, full_name=&#39;AdminMessage.confirm_set_channel&#39;, index=9,
number=32, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;confirm_set_radio&#39;, full_name=&#39;AdminMessage.confirm_set_radio&#39;, index=8,
name=&#39;confirm_set_radio&#39;, full_name=&#39;AdminMessage.confirm_set_radio&#39;, index=10,
number=33, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;exit_simulator&#39;, full_name=&#39;AdminMessage.exit_simulator&#39;, index=9,
name=&#39;exit_simulator&#39;, full_name=&#39;AdminMessage.exit_simulator&#39;, index=11,
number=34, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;reboot_seconds&#39;, full_name=&#39;AdminMessage.reboot_seconds&#39;, index=10,
name=&#39;reboot_seconds&#39;, full_name=&#39;AdminMessage.reboot_seconds&#39;, index=12,
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,
@@ -156,7 +170,7 @@ _ADMINMESSAGE = _descriptor.Descriptor(
index=0, containing_type=None, fields=[]),
],
serialized_start=62,
serialized_end=441,
serialized_end=507,
)
_ADMINMESSAGE.fields_by_name[&#39;set_radio&#39;].message_type = radioconfig__pb2._RADIOCONFIG
@@ -164,6 +178,7 @@ _ADMINMESSAGE.fields_by_name[&#39;set_owner&#39;].message_type = mesh__pb2._USER
_ADMINMESSAGE.fields_by_name[&#39;set_channel&#39;].message_type = channel__pb2._CHANNEL
_ADMINMESSAGE.fields_by_name[&#39;get_radio_response&#39;].message_type = radioconfig__pb2._RADIOCONFIG
_ADMINMESSAGE.fields_by_name[&#39;get_channel_response&#39;].message_type = channel__pb2._CHANNEL
_ADMINMESSAGE.fields_by_name[&#39;get_owner_response&#39;].message_type = mesh__pb2._USER
_ADMINMESSAGE.oneofs_by_name[&#39;variant&#39;].fields.append(
_ADMINMESSAGE.fields_by_name[&#39;set_radio&#39;])
_ADMINMESSAGE.fields_by_name[&#39;set_radio&#39;].containing_oneof = _ADMINMESSAGE.oneofs_by_name[&#39;variant&#39;]
@@ -185,6 +200,12 @@ _ADMINMESSAGE.fields_by_name[&#39;get_channel_request&#39;].containing_oneof = _
_ADMINMESSAGE.oneofs_by_name[&#39;variant&#39;].fields.append(
_ADMINMESSAGE.fields_by_name[&#39;get_channel_response&#39;])
_ADMINMESSAGE.fields_by_name[&#39;get_channel_response&#39;].containing_oneof = _ADMINMESSAGE.oneofs_by_name[&#39;variant&#39;]
_ADMINMESSAGE.oneofs_by_name[&#39;variant&#39;].fields.append(
_ADMINMESSAGE.fields_by_name[&#39;get_owner_request&#39;])
_ADMINMESSAGE.fields_by_name[&#39;get_owner_request&#39;].containing_oneof = _ADMINMESSAGE.oneofs_by_name[&#39;variant&#39;]
_ADMINMESSAGE.oneofs_by_name[&#39;variant&#39;].fields.append(
_ADMINMESSAGE.fields_by_name[&#39;get_owner_response&#39;])
_ADMINMESSAGE.fields_by_name[&#39;get_owner_response&#39;].containing_oneof = _ADMINMESSAGE.oneofs_by_name[&#39;variant&#39;]
_ADMINMESSAGE.oneofs_by_name[&#39;variant&#39;].fields.append(
_ADMINMESSAGE.fields_by_name[&#39;confirm_set_channel&#39;])
_ADMINMESSAGE.fields_by_name[&#39;confirm_set_channel&#39;].containing_oneof = _ADMINMESSAGE.oneofs_by_name[&#39;variant&#39;]
@@ -261,6 +282,14 @@ shown below.</p></div>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.GET_OWNER_REQUEST_FIELD_NUMBER"><code class="name">var <span class="ident">GET_OWNER_REQUEST_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.GET_OWNER_RESPONSE_FIELD_NUMBER"><code class="name">var <span class="ident">GET_OWNER_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>
@@ -398,6 +427,42 @@ shown below.</p></div>
return field_value</code></pre>
</details>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.get_owner_request"><code class="name">var <span class="ident">get_owner_request</span></code></dt>
<dd>
<div class="desc"><p>Getter for get_owner_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_owner_response"><code class="name">var <span class="ident">get_owner_response</span></code></dt>
<dd>
<div class="desc"><p>Getter for get_owner_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>Getter for get_radio_request.</p></div>
@@ -1007,6 +1072,8 @@ and propagates this to our listener iff this was a state change.</p></div>
<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_OWNER_REQUEST_FIELD_NUMBER" href="#meshtastic.admin_pb2.AdminMessage.GET_OWNER_REQUEST_FIELD_NUMBER">GET_OWNER_REQUEST_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.GET_OWNER_RESPONSE_FIELD_NUMBER" href="#meshtastic.admin_pb2.AdminMessage.GET_OWNER_RESPONSE_FIELD_NUMBER">GET_OWNER_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>
@@ -1029,6 +1096,8 @@ and propagates this to our listener iff this was a state change.</p></div>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.exit_simulator" href="#meshtastic.admin_pb2.AdminMessage.exit_simulator">exit_simulator</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.get_channel_request" href="#meshtastic.admin_pb2.AdminMessage.get_channel_request">get_channel_request</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.get_channel_response" href="#meshtastic.admin_pb2.AdminMessage.get_channel_response">get_channel_response</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.get_owner_request" href="#meshtastic.admin_pb2.AdminMessage.get_owner_request">get_owner_request</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.get_owner_response" href="#meshtastic.admin_pb2.AdminMessage.get_owner_response">get_owner_response</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.get_radio_request" href="#meshtastic.admin_pb2.AdminMessage.get_radio_request">get_radio_request</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.get_radio_response" href="#meshtastic.admin_pb2.AdminMessage.get_radio_response">get_radio_response</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.reboot_seconds" href="#meshtastic.admin_pb2.AdminMessage.reboot_seconds">reboot_seconds</a></code></li>

View File

@@ -66,6 +66,7 @@ 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
import meshtastic.serial_interface
from pubsub import pub
def onReceive(packet, interface): # called when a packet arrives
@@ -78,7 +79,7 @@ def onConnection(interface, topic=pub.AUTO_TOPIC): # called when we (re)connect
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()
interface = meshtastic.serial_interface.SerialInterface()
</code></pre>
<details class="source">
@@ -124,6 +125,7 @@ unicode scripts they can be different.
# Example Usage
```
import meshtastic
import meshtastic.serial_interface
from pubsub import pub
def onReceive(packet, interface): # called when a packet arrives
@@ -136,7 +138,7 @@ def onConnection(interface, topic=pub.AUTO_TOPIC): # called when we (re)connect
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()
interface = meshtastic.serial_interface.SerialInterface()
```
@@ -167,23 +169,23 @@ 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
# Note: To follow PEP224, comments should be after the module variable.
&#34;&#34;&#34;A special ID that means the local node&#34;&#34;&#34;
LOCAL_ADDR = &#34;^local&#34;
&#34;&#34;&#34;A special ID that means the local node&#34;&#34;&#34;
# if using 8 bit nodenums this will be shortend on the target
BROADCAST_NUM = 0xffffffff
&#34;&#34;&#34;if using 8 bit nodenums this will be shortend on the target&#34;&#34;&#34;
&#34;&#34;&#34;A special ID that means broadcast&#34;&#34;&#34;
BROADCAST_ADDR = &#34;^all&#34;
&#34;&#34;&#34;A special ID that means broadcast&#34;&#34;&#34;
OUR_APP_VERSION = 20200
&#34;&#34;&#34;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
&#34;&#34;&#34;
OUR_APP_VERSION = 20200
publishingThread = DeferredExecution(&#34;publishing&#34;)
@@ -242,10 +244,11 @@ def _onNodeInfoReceive(iface, asDict):
def _receiveInfoUpdate(iface, asDict):
iface._getOrCreateByNum(asDict[&#34;from&#34;])[&#34;lastReceived&#34;] = asDict
iface._getOrCreateByNum(asDict[&#34;from&#34;])[&#34;lastHeard&#34;] = asDict.get(&#34;rxTime&#34;)
iface._getOrCreateByNum(asDict[&#34;from&#34;])[&#34;snr&#34;] = asDict.get(&#34;rxSnr&#34;)
iface._getOrCreateByNum(asDict[&#34;from&#34;])[&#34;hopLimit&#34;] = asDict.get(&#34;hopLimit&#34;)
if &#34;from&#34; in asDict:
iface._getOrCreateByNum(asDict[&#34;from&#34;])[&#34;lastReceived&#34;] = asDict
iface._getOrCreateByNum(asDict[&#34;from&#34;])[&#34;lastHeard&#34;] = asDict.get(&#34;rxTime&#34;)
iface._getOrCreateByNum(asDict[&#34;from&#34;])[&#34;snr&#34;] = asDict.get(&#34;rxSnr&#34;)
iface._getOrCreateByNum(asDict[&#34;from&#34;])[&#34;hopLimit&#34;] = asDict.get(&#34;hopLimit&#34;)
&#34;&#34;&#34;Well known message payloads can register decoders for automatic protobuf parsing&#34;&#34;&#34;
@@ -368,13 +371,21 @@ messages and report back if successful.</p></div>
<dl>
<dt id="meshtastic.BROADCAST_ADDR"><code class="name">var <span class="ident">BROADCAST_ADDR</span></code></dt>
<dd>
<div class="desc"><p>The numeric buildnumber (shared with android apps) specifying the
level of device code we are guaranteed to understand</p>
<p>format is Mmmss (where M is 1+the numeric major number. i.e. 20120 means 1.1.20</p></div>
<div class="desc"><p>A special ID that means broadcast</p></div>
</dd>
<dt id="meshtastic.BROADCAST_NUM"><code class="name">var <span class="ident">BROADCAST_NUM</span></code></dt>
<dd>
<div class="desc"><p>A special ID that means broadcast</p></div>
<div class="desc"><p>if using 8 bit nodenums this will be shortend on the target</p></div>
</dd>
<dt id="meshtastic.LOCAL_ADDR"><code class="name">var <span class="ident">LOCAL_ADDR</span></code></dt>
<dd>
<div class="desc"><p>A special ID that means the local node</p></div>
</dd>
<dt id="meshtastic.OUR_APP_VERSION"><code class="name">var <span class="ident">OUR_APP_VERSION</span></code></dt>
<dd>
<div class="desc"><p>The numeric buildnumber (shared with android apps) specifying the
level of device code we are guaranteed to understand</p>
<p>format is Mmmss (where M is 1+the numeric major number. i.e. 20120 means 1.1.20</p></div>
</dd>
</dl>
</section>
@@ -495,6 +506,8 @@ level of device code we are guaranteed to understand</p>
<ul class="">
<li><code><a title="meshtastic.BROADCAST_ADDR" href="#meshtastic.BROADCAST_ADDR">BROADCAST_ADDR</a></code></li>
<li><code><a title="meshtastic.BROADCAST_NUM" href="#meshtastic.BROADCAST_NUM">BROADCAST_NUM</a></code></li>
<li><code><a title="meshtastic.LOCAL_ADDR" href="#meshtastic.LOCAL_ADDR">LOCAL_ADDR</a></code></li>
<li><code><a title="meshtastic.OUR_APP_VERSION" href="#meshtastic.OUR_APP_VERSION">OUR_APP_VERSION</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>

View File

@@ -27,7 +27,7 @@
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34; Mesh Interface class
<pre><code class="python">&#34;&#34;&#34;Mesh Interface class
&#34;&#34;&#34;
import sys
import random
@@ -45,15 +45,11 @@ from pubsub import pub
from google.protobuf.json_format import MessageToJson
import meshtastic.node
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:
&#34;&#34;&#34;Interface class for meshtastic devices
@@ -75,7 +71,7 @@ class MeshInterface:
self.nodes = None # FIXME
self.isConnected = threading.Event()
self.noProto = noProto
self.localNode = Node(self, -1) # We fixup nodenum later
self.localNode = meshtastic.node.Node(self, -1) # We fixup nodenum later
self.myInfo = None # We don&#39;t have device info yet
self.responseHandlers = {} # A map from request ID to the handler
self.failure = None # If we&#39;ve encountered a fatal exception it will be kept here
@@ -85,6 +81,9 @@ class MeshInterface:
self.currentPacketId = random.randint(0, 0xffffffff)
self.nodesByNum = None
self.configId = None
self.defaultHopLimit = 3
self.gotResponse = False # used in gpio read
self.mask = None # used in gpio read and gpio watch
def close(self):
&#34;&#34;&#34;Shutdown this interface&#34;&#34;&#34;
@@ -135,6 +134,7 @@ class MeshInterface:
rows = []
if self.nodes:
logging.debug(f&#39;self.nodes:{self.nodes}&#39;)
for node in self.nodes.values():
if not includeSelf and node[&#39;num&#39;] == self.localNode.nodeNum:
continue
@@ -180,7 +180,8 @@ class MeshInterface:
if nodeId == LOCAL_ADDR:
return self.localNode
else:
n = Node(self, nodeId)
n = meshtastic.node.Node(self, nodeId)
logging.debug(&#34;About to requestConfig&#34;)
n.requestConfig()
if not n.waitForConfig():
our_exit(&#34;Error: Timed out waiting for node config&#34;)
@@ -190,7 +191,7 @@ class MeshInterface:
destinationId=BROADCAST_ADDR,
wantAck=False,
wantResponse=False,
hopLimit=defaultHopLimit,
hopLimit=None,
onResponse=None,
channelIndex=0):
&#34;&#34;&#34;Send a utf8 string to some other node, if the node has a display it
@@ -212,6 +213,9 @@ class MeshInterface:
Returns the sent packet. The id field will be populated in this packet
and can be used to track future message acks/naks.
&#34;&#34;&#34;
if hopLimit is None:
hopLimit = self.defaultHopLimit
return self.sendData(text.encode(&#34;utf-8&#34;), destinationId,
portNum=portnums_pb2.PortNum.TEXT_MESSAGE_APP,
wantAck=wantAck,
@@ -223,7 +227,7 @@ class MeshInterface:
def sendData(self, data, destinationId=BROADCAST_ADDR,
portNum=portnums_pb2.PortNum.PRIVATE_APP, wantAck=False,
wantResponse=False,
hopLimit=defaultHopLimit,
hopLimit=None,
onResponse=None,
channelIndex=0):
&#34;&#34;&#34;Send a data packet to some other node
@@ -243,16 +247,22 @@ class MeshInterface:
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)
channelIndex - channel number to use
Returns the sent packet. The id field will be populated in this packet
and can be used to track future message acks/naks.
&#34;&#34;&#34;
if hopLimit is None:
hopLimit = self.defaultHopLimit
if getattr(data, &#34;SerializeToString&#34;, None):
logging.debug(f&#34;Serializing protobuf as data: {stripnl(data)}&#34;)
data = data.SerializeToString()
logging.debug(f&#34;len(data): {len(data)}&#34;)
logging.debug(f&#34;mesh_pb2.Constants.DATA_PAYLOAD_LEN: {mesh_pb2.Constants.DATA_PAYLOAD_LEN}&#34;)
if len(data) &gt; mesh_pb2.Constants.DATA_PAYLOAD_LEN:
Exception(&#34;Data payload too big&#34;)
raise Exception(&#34;Data payload too big&#34;)
if portNum == portnums_pb2.PortNum.UNKNOWN_APP: # we are now more strict wrt port numbers
our_exit(&#34;Warning: A non-zero port number must be specified&#34;)
@@ -285,16 +295,20 @@ class MeshInterface:
p = mesh_pb2.Position()
if latitude != 0.0:
p.latitude_i = int(latitude / 1e-7)
logging.debug(f&#39;p.latitude_i:{p.latitude_i}&#39;)
if longitude != 0.0:
p.longitude_i = int(longitude / 1e-7)
logging.debug(f&#39;p.longitude_i:{p.longitude_i}&#39;)
if altitude != 0:
p.altitude = int(altitude)
logging.debug(f&#39;p.altitude:{p.altitude}&#39;)
if timeSec == 0:
timeSec = time.time() # returns unix timestamp in seconds
p.time = int(timeSec)
logging.debug(f&#39;p.time:{p.time}&#39;)
return self.sendData(p, destinationId,
portNum=portnums_pb2.PortNum.POSITION_APP,
@@ -306,13 +320,15 @@ class MeshInterface:
def _sendPacket(self, meshPacket,
destinationId=BROADCAST_ADDR,
wantAck=False, hopLimit=defaultHopLimit):
wantAck=False, hopLimit=None):
&#34;&#34;&#34;Send a MeshPacket to the specified node (or if unspecified, broadcast).
You probably don&#39;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.
&#34;&#34;&#34;
if hopLimit is None:
hopLimit = self.defaultHopLimit
# We allow users to talk to the local node before we&#39;ve completed the full connection flow...
if(self.myInfo is not None and destinationId != self.myInfo.my_node_num):
@@ -320,6 +336,7 @@ class MeshInterface:
toRadio = mesh_pb2.ToRadio()
nodeNum = 0
if destinationId is None:
our_exit(&#34;Warning: destinationId must not be None&#34;)
elif isinstance(destinationId, int):
@@ -327,15 +344,21 @@ class MeshInterface:
elif destinationId == BROADCAST_ADDR:
nodeNum = BROADCAST_NUM
elif destinationId == LOCAL_ADDR:
nodeNum = self.myInfo.my_node_num
if self.myInfo:
nodeNum = self.myInfo.my_node_num
else:
our_exit(&#34;Warning: No myInfo found.&#34;)
# A simple hex style nodeid - we can parse this without needing the DB
elif destinationId.startswith(&#34;!&#34;):
nodeNum = int(destinationId[1:], 16)
else:
node = self.nodes.get(destinationId)
if not node:
our_exit(f&#34;Warning: NodeId {destinationId} not found in DB&#34;)
nodeNum = node[&#39;num&#39;]
if self.nodes:
node = self.nodes.get(destinationId)
if not node:
our_exit(f&#34;Warning: NodeId {destinationId} not found in DB&#34;)
nodeNum = node[&#39;num&#39;]
else:
logging.warning(&#34;Warning: There were no self.nodes.&#34;)
meshPacket.to = nodeNum
meshPacket.want_ack = wantAck
@@ -347,8 +370,11 @@ class MeshInterface:
meshPacket.id = self._generatePacketId()
toRadio.packet.CopyFrom(meshPacket)
#logging.debug(f&#34;Sending packet: {stripnl(meshPacket)}&#34;)
self._sendToRadio(toRadio)
if self.noProto:
logging.warning(f&#34;Not sending packet because protocol use is disabled by noProto&#34;)
else:
logging.debug(f&#34;Sending packet: {stripnl(meshPacket)}&#34;)
self._sendToRadio(toRadio)
return meshPacket
def waitForConfig(self):
@@ -361,6 +387,7 @@ class MeshInterface:
&#34;&#34;&#34;Get info about my node.&#34;&#34;&#34;
if self.myInfo is None:
return None
logging.debug(f&#39;self.nodesByNum:{self.nodesByNum}&#39;)
return self.nodesByNum.get(self.myInfo.my_node_num)
def getMyUser(self):
@@ -387,8 +414,9 @@ class MeshInterface:
def _waitConnected(self):
&#34;&#34;&#34;Block until the initial node db download is complete, or timeout
and raise an exception&#34;&#34;&#34;
if not self.isConnected.wait(10.0): # timeout after 10 seconds
raise Exception(&#34;Timed out waiting for connection completion&#34;)
if not self.noProto:
if not self.isConnected.wait(15.0): # timeout after x seconds
raise Exception(&#34;Timed out waiting for connection completion&#34;)
# If we failed while connecting, raise the connection to the client
if self.failure:
@@ -478,8 +506,9 @@ class MeshInterface:
Called by subclasses.&#34;&#34;&#34;
fromRadio = mesh_pb2.FromRadio()
fromRadio.ParseFromString(fromRadioBytes)
logging.debug(f&#34;in mesh_interface.py _handleFromRadio() fromRadioBytes: {fromRadioBytes}&#34;)
asDict = google.protobuf.json_format.MessageToDict(fromRadio)
#logging.debug(f&#34;Received from radio: {fromRadio}&#34;)
logging.debug(f&#34;Received from radio: {fromRadio}&#34;)
if fromRadio.HasField(&#34;my_info&#34;):
self.myInfo = fromRadio.my_info
self.localNode.nodeNum = self.myInfo.my_node_num
@@ -512,7 +541,8 @@ class MeshInterface:
self.nodesByNum[node[&#34;num&#34;]] = node
if &#34;user&#34; in node: # Some nodes might not have user/ids assigned yet
self.nodes[node[&#34;user&#34;][&#34;id&#34;]] = node
if &#34;id&#34; in node[&#34;user&#34;]:
self.nodes[node[&#34;user&#34;][&#34;id&#34;]] = node
publishingThread.queueWork(lambda: pub.sendMessage(&#34;meshtastic.node.updated&#34;,
node=node, interface=self))
elif fromRadio.config_complete_id == self.configId:
@@ -572,16 +602,22 @@ class MeshInterface:
self.nodesByNum[nodeNum] = n
return n
def _handlePacketFromRadio(self, meshPacket):
def _handlePacketFromRadio(self, meshPacket, hack=False):
&#34;&#34;&#34;Handle a MeshPacket that just arrived from the radio
hack - well, since we used &#39;from&#39;, which is a python keyword,
as an attribute to MeshPacket in protobufs,
there really is no way to do something like this:
meshPacket = mesh_pb2.MeshPacket()
meshPacket.from = 123
If hack is True, we can unit test this code.
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)
&#34;&#34;&#34;
asDict = google.protobuf.json_format.MessageToDict(meshPacket)
# We normally decompose the payload into a dictionary so that the client
@@ -590,10 +626,10 @@ class MeshInterface:
asDict[&#34;raw&#34;] = meshPacket
# from might be missing if the nodenum was zero.
if &#34;from&#34; not in asDict:
if not hack and &#34;from&#34; not in asDict:
asDict[&#34;from&#34;] = 0
logging.error(
f&#34;Device returned a packet we sent, ignoring: {stripnl(asDict)}&#34;)
logging.error(f&#34;Device returned a packet we sent, ignoring: {stripnl(asDict)}&#34;)
print(f&#34;Error: Device returned a packet we sent, ignoring: {stripnl(asDict)}&#34;)
return
if &#34;to&#34; not in asDict:
asDict[&#34;to&#34;] = 0
@@ -621,9 +657,10 @@ class MeshInterface:
# 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 &#34;portnum&#34; in decoded:
decoded[&#34;portnum&#34;] = portnums_pb2.PortNum.Name(
portnums_pb2.PortNum.UNKNOWN_APP)
if &#34;portnum&#34; not in decoded:
new_portnum = portnums_pb2.PortNum.Name(portnums_pb2.PortNum.UNKNOWN_APP)
decoded[&#34;portnum&#34;] = new_portnum
logging.warning(f&#34;portnum was not in decoded. Setting to:{new_portnum}&#34;)
portnum = decoded[&#34;portnum&#34;]
@@ -717,7 +754,7 @@ link - just be a dumb serial client.</p></div>
self.nodes = None # FIXME
self.isConnected = threading.Event()
self.noProto = noProto
self.localNode = Node(self, -1) # We fixup nodenum later
self.localNode = meshtastic.node.Node(self, -1) # We fixup nodenum later
self.myInfo = None # We don&#39;t have device info yet
self.responseHandlers = {} # A map from request ID to the handler
self.failure = None # If we&#39;ve encountered a fatal exception it will be kept here
@@ -727,6 +764,9 @@ link - just be a dumb serial client.</p></div>
self.currentPacketId = random.randint(0, 0xffffffff)
self.nodesByNum = None
self.configId = None
self.defaultHopLimit = 3
self.gotResponse = False # used in gpio read
self.mask = None # used in gpio read and gpio watch
def close(self):
&#34;&#34;&#34;Shutdown this interface&#34;&#34;&#34;
@@ -777,6 +817,7 @@ link - just be a dumb serial client.</p></div>
rows = []
if self.nodes:
logging.debug(f&#39;self.nodes:{self.nodes}&#39;)
for node in self.nodes.values():
if not includeSelf and node[&#39;num&#39;] == self.localNode.nodeNum:
continue
@@ -822,7 +863,8 @@ link - just be a dumb serial client.</p></div>
if nodeId == LOCAL_ADDR:
return self.localNode
else:
n = Node(self, nodeId)
n = meshtastic.node.Node(self, nodeId)
logging.debug(&#34;About to requestConfig&#34;)
n.requestConfig()
if not n.waitForConfig():
our_exit(&#34;Error: Timed out waiting for node config&#34;)
@@ -832,7 +874,7 @@ link - just be a dumb serial client.</p></div>
destinationId=BROADCAST_ADDR,
wantAck=False,
wantResponse=False,
hopLimit=defaultHopLimit,
hopLimit=None,
onResponse=None,
channelIndex=0):
&#34;&#34;&#34;Send a utf8 string to some other node, if the node has a display it
@@ -854,6 +896,9 @@ link - just be a dumb serial client.</p></div>
Returns the sent packet. The id field will be populated in this packet
and can be used to track future message acks/naks.
&#34;&#34;&#34;
if hopLimit is None:
hopLimit = self.defaultHopLimit
return self.sendData(text.encode(&#34;utf-8&#34;), destinationId,
portNum=portnums_pb2.PortNum.TEXT_MESSAGE_APP,
wantAck=wantAck,
@@ -865,7 +910,7 @@ link - just be a dumb serial client.</p></div>
def sendData(self, data, destinationId=BROADCAST_ADDR,
portNum=portnums_pb2.PortNum.PRIVATE_APP, wantAck=False,
wantResponse=False,
hopLimit=defaultHopLimit,
hopLimit=None,
onResponse=None,
channelIndex=0):
&#34;&#34;&#34;Send a data packet to some other node
@@ -885,16 +930,22 @@ link - just be a dumb serial client.</p></div>
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)
channelIndex - channel number to use
Returns the sent packet. The id field will be populated in this packet
and can be used to track future message acks/naks.
&#34;&#34;&#34;
if hopLimit is None:
hopLimit = self.defaultHopLimit
if getattr(data, &#34;SerializeToString&#34;, None):
logging.debug(f&#34;Serializing protobuf as data: {stripnl(data)}&#34;)
data = data.SerializeToString()
logging.debug(f&#34;len(data): {len(data)}&#34;)
logging.debug(f&#34;mesh_pb2.Constants.DATA_PAYLOAD_LEN: {mesh_pb2.Constants.DATA_PAYLOAD_LEN}&#34;)
if len(data) &gt; mesh_pb2.Constants.DATA_PAYLOAD_LEN:
Exception(&#34;Data payload too big&#34;)
raise Exception(&#34;Data payload too big&#34;)
if portNum == portnums_pb2.PortNum.UNKNOWN_APP: # we are now more strict wrt port numbers
our_exit(&#34;Warning: A non-zero port number must be specified&#34;)
@@ -927,16 +978,20 @@ link - just be a dumb serial client.</p></div>
p = mesh_pb2.Position()
if latitude != 0.0:
p.latitude_i = int(latitude / 1e-7)
logging.debug(f&#39;p.latitude_i:{p.latitude_i}&#39;)
if longitude != 0.0:
p.longitude_i = int(longitude / 1e-7)
logging.debug(f&#39;p.longitude_i:{p.longitude_i}&#39;)
if altitude != 0:
p.altitude = int(altitude)
logging.debug(f&#39;p.altitude:{p.altitude}&#39;)
if timeSec == 0:
timeSec = time.time() # returns unix timestamp in seconds
p.time = int(timeSec)
logging.debug(f&#39;p.time:{p.time}&#39;)
return self.sendData(p, destinationId,
portNum=portnums_pb2.PortNum.POSITION_APP,
@@ -948,13 +1003,15 @@ link - just be a dumb serial client.</p></div>
def _sendPacket(self, meshPacket,
destinationId=BROADCAST_ADDR,
wantAck=False, hopLimit=defaultHopLimit):
wantAck=False, hopLimit=None):
&#34;&#34;&#34;Send a MeshPacket to the specified node (or if unspecified, broadcast).
You probably don&#39;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.
&#34;&#34;&#34;
if hopLimit is None:
hopLimit = self.defaultHopLimit
# We allow users to talk to the local node before we&#39;ve completed the full connection flow...
if(self.myInfo is not None and destinationId != self.myInfo.my_node_num):
@@ -962,6 +1019,7 @@ link - just be a dumb serial client.</p></div>
toRadio = mesh_pb2.ToRadio()
nodeNum = 0
if destinationId is None:
our_exit(&#34;Warning: destinationId must not be None&#34;)
elif isinstance(destinationId, int):
@@ -969,15 +1027,21 @@ link - just be a dumb serial client.</p></div>
elif destinationId == BROADCAST_ADDR:
nodeNum = BROADCAST_NUM
elif destinationId == LOCAL_ADDR:
nodeNum = self.myInfo.my_node_num
if self.myInfo:
nodeNum = self.myInfo.my_node_num
else:
our_exit(&#34;Warning: No myInfo found.&#34;)
# A simple hex style nodeid - we can parse this without needing the DB
elif destinationId.startswith(&#34;!&#34;):
nodeNum = int(destinationId[1:], 16)
else:
node = self.nodes.get(destinationId)
if not node:
our_exit(f&#34;Warning: NodeId {destinationId} not found in DB&#34;)
nodeNum = node[&#39;num&#39;]
if self.nodes:
node = self.nodes.get(destinationId)
if not node:
our_exit(f&#34;Warning: NodeId {destinationId} not found in DB&#34;)
nodeNum = node[&#39;num&#39;]
else:
logging.warning(&#34;Warning: There were no self.nodes.&#34;)
meshPacket.to = nodeNum
meshPacket.want_ack = wantAck
@@ -989,8 +1053,11 @@ link - just be a dumb serial client.</p></div>
meshPacket.id = self._generatePacketId()
toRadio.packet.CopyFrom(meshPacket)
#logging.debug(f&#34;Sending packet: {stripnl(meshPacket)}&#34;)
self._sendToRadio(toRadio)
if self.noProto:
logging.warning(f&#34;Not sending packet because protocol use is disabled by noProto&#34;)
else:
logging.debug(f&#34;Sending packet: {stripnl(meshPacket)}&#34;)
self._sendToRadio(toRadio)
return meshPacket
def waitForConfig(self):
@@ -1003,6 +1070,7 @@ link - just be a dumb serial client.</p></div>
&#34;&#34;&#34;Get info about my node.&#34;&#34;&#34;
if self.myInfo is None:
return None
logging.debug(f&#39;self.nodesByNum:{self.nodesByNum}&#39;)
return self.nodesByNum.get(self.myInfo.my_node_num)
def getMyUser(self):
@@ -1029,8 +1097,9 @@ link - just be a dumb serial client.</p></div>
def _waitConnected(self):
&#34;&#34;&#34;Block until the initial node db download is complete, or timeout
and raise an exception&#34;&#34;&#34;
if not self.isConnected.wait(10.0): # timeout after 10 seconds
raise Exception(&#34;Timed out waiting for connection completion&#34;)
if not self.noProto:
if not self.isConnected.wait(15.0): # timeout after x seconds
raise Exception(&#34;Timed out waiting for connection completion&#34;)
# If we failed while connecting, raise the connection to the client
if self.failure:
@@ -1120,8 +1189,9 @@ link - just be a dumb serial client.</p></div>
Called by subclasses.&#34;&#34;&#34;
fromRadio = mesh_pb2.FromRadio()
fromRadio.ParseFromString(fromRadioBytes)
logging.debug(f&#34;in mesh_interface.py _handleFromRadio() fromRadioBytes: {fromRadioBytes}&#34;)
asDict = google.protobuf.json_format.MessageToDict(fromRadio)
#logging.debug(f&#34;Received from radio: {fromRadio}&#34;)
logging.debug(f&#34;Received from radio: {fromRadio}&#34;)
if fromRadio.HasField(&#34;my_info&#34;):
self.myInfo = fromRadio.my_info
self.localNode.nodeNum = self.myInfo.my_node_num
@@ -1154,7 +1224,8 @@ link - just be a dumb serial client.</p></div>
self.nodesByNum[node[&#34;num&#34;]] = node
if &#34;user&#34; in node: # Some nodes might not have user/ids assigned yet
self.nodes[node[&#34;user&#34;][&#34;id&#34;]] = node
if &#34;id&#34; in node[&#34;user&#34;]:
self.nodes[node[&#34;user&#34;][&#34;id&#34;]] = node
publishingThread.queueWork(lambda: pub.sendMessage(&#34;meshtastic.node.updated&#34;,
node=node, interface=self))
elif fromRadio.config_complete_id == self.configId:
@@ -1214,16 +1285,22 @@ link - just be a dumb serial client.</p></div>
self.nodesByNum[nodeNum] = n
return n
def _handlePacketFromRadio(self, meshPacket):
def _handlePacketFromRadio(self, meshPacket, hack=False):
&#34;&#34;&#34;Handle a MeshPacket that just arrived from the radio
hack - well, since we used &#39;from&#39;, which is a python keyword,
as an attribute to MeshPacket in protobufs,
there really is no way to do something like this:
meshPacket = mesh_pb2.MeshPacket()
meshPacket.from = 123
If hack is True, we can unit test this code.
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)
&#34;&#34;&#34;
asDict = google.protobuf.json_format.MessageToDict(meshPacket)
# We normally decompose the payload into a dictionary so that the client
@@ -1232,10 +1309,10 @@ link - just be a dumb serial client.</p></div>
asDict[&#34;raw&#34;] = meshPacket
# from might be missing if the nodenum was zero.
if &#34;from&#34; not in asDict:
if not hack and &#34;from&#34; not in asDict:
asDict[&#34;from&#34;] = 0
logging.error(
f&#34;Device returned a packet we sent, ignoring: {stripnl(asDict)}&#34;)
logging.error(f&#34;Device returned a packet we sent, ignoring: {stripnl(asDict)}&#34;)
print(f&#34;Error: Device returned a packet we sent, ignoring: {stripnl(asDict)}&#34;)
return
if &#34;to&#34; not in asDict:
asDict[&#34;to&#34;] = 0
@@ -1263,9 +1340,10 @@ link - just be a dumb serial client.</p></div>
# 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 &#34;portnum&#34; in decoded:
decoded[&#34;portnum&#34;] = portnums_pb2.PortNum.Name(
portnums_pb2.PortNum.UNKNOWN_APP)
if &#34;portnum&#34; not in decoded:
new_portnum = portnums_pb2.PortNum.Name(portnums_pb2.PortNum.UNKNOWN_APP)
decoded[&#34;portnum&#34;] = new_portnum
logging.warning(f&#34;portnum was not in decoded. Setting to:{new_portnum}&#34;)
portnum = decoded[&#34;portnum&#34;]
@@ -1364,6 +1442,7 @@ link - just be a dumb serial client.</p></div>
&#34;&#34;&#34;Get info about my node.&#34;&#34;&#34;
if self.myInfo is None:
return None
logging.debug(f&#39;self.nodesByNum:{self.nodesByNum}&#39;)
return self.nodesByNum.get(self.myInfo.my_node_num)</code></pre>
</details>
</dd>
@@ -1398,7 +1477,8 @@ link - just be a dumb serial client.</p></div>
if nodeId == LOCAL_ADDR:
return self.localNode
else:
n = Node(self, nodeId)
n = meshtastic.node.Node(self, nodeId)
logging.debug(&#34;About to requestConfig&#34;)
n.requestConfig()
if not n.waitForConfig():
our_exit(&#34;Error: Timed out waiting for node config&#34;)
@@ -1423,7 +1503,7 @@ link - just be a dumb serial client.</p></div>
</details>
</dd>
<dt id="meshtastic.mesh_interface.MeshInterface.sendData"><code class="name flex">
<span>def <span class="ident">sendData</span></span>(<span>self, data, destinationId='^all', portNum=256, wantAck=False, wantResponse=False, hopLimit=3, onResponse=None, channelIndex=0)</span>
<span>def <span class="ident">sendData</span></span>(<span>self, data, destinationId='^all', portNum=256, wantAck=False, wantResponse=False, hopLimit=None, onResponse=None, channelIndex=0)</span>
</code></dt>
<dd>
<div class="desc"><p>Send a data packet to some other node</p>
@@ -1441,7 +1521,8 @@ wantResponse &ndash; True if you want the service on the other
side to send an application layer response
onResponse &ndash; 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)</p>
is NAKed due to non receipt)
channelIndex - channel number to use</p>
<p>Returns the sent packet. The id field will be populated in this packet
and can be used to track future message acks/naks.</p></div>
<details class="source">
@@ -1451,7 +1532,7 @@ and can be used to track future message acks/naks.</p></div>
<pre><code class="python">def sendData(self, data, destinationId=BROADCAST_ADDR,
portNum=portnums_pb2.PortNum.PRIVATE_APP, wantAck=False,
wantResponse=False,
hopLimit=defaultHopLimit,
hopLimit=None,
onResponse=None,
channelIndex=0):
&#34;&#34;&#34;Send a data packet to some other node
@@ -1471,16 +1552,22 @@ and can be used to track future message acks/naks.</p></div>
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)
channelIndex - channel number to use
Returns the sent packet. The id field will be populated in this packet
and can be used to track future message acks/naks.
&#34;&#34;&#34;
if hopLimit is None:
hopLimit = self.defaultHopLimit
if getattr(data, &#34;SerializeToString&#34;, None):
logging.debug(f&#34;Serializing protobuf as data: {stripnl(data)}&#34;)
data = data.SerializeToString()
logging.debug(f&#34;len(data): {len(data)}&#34;)
logging.debug(f&#34;mesh_pb2.Constants.DATA_PAYLOAD_LEN: {mesh_pb2.Constants.DATA_PAYLOAD_LEN}&#34;)
if len(data) &gt; mesh_pb2.Constants.DATA_PAYLOAD_LEN:
Exception(&#34;Data payload too big&#34;)
raise Exception(&#34;Data payload too big&#34;)
if portNum == portnums_pb2.PortNum.UNKNOWN_APP: # we are now more strict wrt port numbers
our_exit(&#34;Warning: A non-zero port number must be specified&#34;)
@@ -1528,16 +1615,20 @@ can be used to track future message acks/naks.</p></div>
p = mesh_pb2.Position()
if latitude != 0.0:
p.latitude_i = int(latitude / 1e-7)
logging.debug(f&#39;p.latitude_i:{p.latitude_i}&#39;)
if longitude != 0.0:
p.longitude_i = int(longitude / 1e-7)
logging.debug(f&#39;p.longitude_i:{p.longitude_i}&#39;)
if altitude != 0:
p.altitude = int(altitude)
logging.debug(f&#39;p.altitude:{p.altitude}&#39;)
if timeSec == 0:
timeSec = time.time() # returns unix timestamp in seconds
p.time = int(timeSec)
logging.debug(f&#39;p.time:{p.time}&#39;)
return self.sendData(p, destinationId,
portNum=portnums_pb2.PortNum.POSITION_APP,
@@ -1546,7 +1637,7 @@ can be used to track future message acks/naks.</p></div>
</details>
</dd>
<dt id="meshtastic.mesh_interface.MeshInterface.sendText"><code class="name flex">
<span>def <span class="ident">sendText</span></span>(<span>self, text: ~AnyStr, destinationId='^all', wantAck=False, wantResponse=False, hopLimit=3, onResponse=None, channelIndex=0)</span>
<span>def <span class="ident">sendText</span></span>(<span>self, text: ~AnyStr, destinationId='^all', wantAck=False, wantResponse=False, hopLimit=None, onResponse=None, channelIndex=0)</span>
</code></dt>
<dd>
<div class="desc"><p>Send a utf8 string to some other node, if the node has a display it
@@ -1572,7 +1663,7 @@ and can be used to track future message acks/naks.</p></div>
destinationId=BROADCAST_ADDR,
wantAck=False,
wantResponse=False,
hopLimit=defaultHopLimit,
hopLimit=None,
onResponse=None,
channelIndex=0):
&#34;&#34;&#34;Send a utf8 string to some other node, if the node has a display it
@@ -1594,6 +1685,9 @@ and can be used to track future message acks/naks.</p></div>
Returns the sent packet. The id field will be populated in this packet
and can be used to track future message acks/naks.
&#34;&#34;&#34;
if hopLimit is None:
hopLimit = self.defaultHopLimit
return self.sendData(text.encode(&#34;utf-8&#34;), destinationId,
portNum=portnums_pb2.PortNum.TEXT_MESSAGE_APP,
wantAck=wantAck,
@@ -1653,6 +1747,7 @@ and can be used to track future message acks/naks.</p></div>
rows = []
if self.nodes:
logging.debug(f&#39;self.nodes:{self.nodes}&#39;)
for node in self.nodes.values():
if not includeSelf and node[&#39;num&#39;] == self.localNode.nodeNum:
continue

View File

File diff suppressed because one or more lines are too long

View File

@@ -37,26 +37,31 @@ from . import portnums_pb2, apponly_pb2, admin_pb2, channel_pb2
from .util import pskToString, stripnl, Timeout, our_exit, fromPSK
class Node:
&#34;&#34;&#34;A model of a (local or remote) node in the mesh
Includes methods for radioConfig and channels
&#34;&#34;&#34;
def __init__(self, iface, nodeNum):
def __init__(self, iface, nodeNum, noProto=False):
&#34;&#34;&#34;Constructor&#34;&#34;&#34;
self.iface = iface
self.nodeNum = nodeNum
self.radioConfig = None
self.channels = None
self._timeout = Timeout(maxSecs=60)
self._timeout = Timeout(maxSecs=300)
self.partialChannels = None
self.noProto = noProto
def showChannels(self):
&#34;&#34;&#34;Show human readable description of our channels.&#34;&#34;&#34;
print(&#34;Channels:&#34;)
if self.channels:
logging.debug(f&#39;self.channels:{self.channels}&#39;)
for c in self.channels:
#print(&#39;c.settings.psk:&#39;, c.settings.psk)
cStr = stripnl(MessageToJson(c.settings))
# only show if there is no psk (meaning disabled channel)
if c.settings.psk:
@@ -77,6 +82,7 @@ class Node:
def requestConfig(self):
&#34;&#34;&#34;Send regular MeshPackets to ask for settings and channels.&#34;&#34;&#34;
logging.debug(f&#34;requestConfig for nodeNum:{self.nodeNum}&#34;)
self.radioConfig = None
self.channels = None
self.partialChannels = [] # We keep our channels in a temp array until finished
@@ -113,6 +119,16 @@ class Node:
self._sendAdmin(p, adminIndex=adminIndex)
logging.debug(f&#34;Wrote channel {channelIndex}&#34;)
def getChannelByChannelIndex(self, channelIndex):
&#34;&#34;&#34;Get channel by channelIndex
channelIndex: number, typically 0-7; based on max number channels
returns: None if there is no channel found
&#34;&#34;&#34;
ch = None
if self.channels and 0 &lt;= channelIndex &lt; len(self.channels):
ch = self.channels[channelIndex]
return ch
def deleteChannel(self, channelIndex):
&#34;&#34;&#34;Delete the specifed channelIndex and shift other channels up&#34;&#34;&#34;
ch = self.channels[channelIndex]
@@ -135,7 +151,7 @@ class Node:
# *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
# (and written it), so we can start finding it by name again
adminIndex = 0
def getChannelByName(self, name):
@@ -162,6 +178,7 @@ class Node:
def setOwner(self, long_name=None, short_name=None, is_licensed=False, team=None):
&#34;&#34;&#34;Set device owner name&#34;&#34;&#34;
logging.debug(f&#34;in setOwner nodeNum:{self.nodeNum}&#34;)
nChars = 3
minChars = 2
if long_name is not None:
@@ -191,6 +208,11 @@ class Node:
if team is not None:
p.set_owner.team = team
# Note: These debug lines are used in unit tests
logging.debug(f&#39;p.set_owner.long_name:{p.set_owner.long_name}:&#39;)
logging.debug(f&#39;p.set_owner.short_name:{p.set_owner.short_name}:&#39;)
logging.debug(f&#39;p.set_owner.is_licensed:{p.set_owner.is_licensed}&#39;)
logging.debug(f&#39;p.set_owner.team:{p.set_owner.team}&#39;)
return self._sendAdmin(p)
def getURL(self, includeAll: bool = True):
@@ -226,6 +248,7 @@ class Node:
channelSet = apponly_pb2.ChannelSet()
channelSet.ParseFromString(decodedURL)
if len(channelSet.settings) == 0:
our_exit(&#34;Warning: There were no settings.&#34;)
@@ -236,39 +259,51 @@ class Node:
ch.index = i
ch.settings.CopyFrom(chs)
self.channels[ch.index] = ch
logging.debug(f&#39;Channel i:{i} ch:{ch}&#39;)
self.writeChannel(ch.index)
i = i + 1
def onResponseRequestSettings(self, p):
&#34;&#34;&#34;Handle the response packet for requesting settings _requestSettings()&#34;&#34;&#34;
logging.debug(f&#39;onResponseRequestSetting() p:{p}&#39;)
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(f&#39;self.radioConfig:{self.radioConfig}&#39;)
logging.debug(&#34;Received radio config, now fetching channels...&#34;)
self._timeout.reset() # We made foreward progress
self._requestChannel(0) # now start fetching channels
def _requestSettings(self):
&#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;
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:
print(&#34;Requesting preferences from remote node (this could take a while)&#34;)
print(&#34;Requesting preferences from remote node.&#34;)
print(&#34;Be sure:&#34;)
print(&#34; 1. There is a SECONDARY channel named &#39;admin&#39;.&#34;)
print(&#34; 2. The &#39;--seturl&#39; was used to configure.&#34;)
print(&#34; 3. All devices have the same modem config. (i.e., &#39;--ch-longfast&#39;)&#34;)
print(&#34; 4. All devices have been rebooted after all of the above. (optional, but recommended)&#34;)
print(&#34;Note: This could take a while (it requests remote channel configs, then writes config)&#34;)
return self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
return self._sendAdmin(p, wantResponse=True, onResponse=self.onResponseRequestSettings)
def exitSimulator(self):
&#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
logging.debug(&#39;in exitSimulator()&#39;)
return self._sendAdmin(p)
@@ -302,6 +337,34 @@ class Node:
self.channels.append(ch)
index += 1
def onResponseRequestChannel(self, p):
&#34;&#34;&#34;Handle the response packet for requesting a channel _requestChannel()&#34;&#34;&#34;
logging.debug(f&#39;onResponseRequestChannel() p:{p}&#39;)
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
logging.debug(f&#34;Received channel {stripnl(c)}&#34;)
index = c.index
# 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
if quitEarly or index &gt;= self.iface.myInfo.max_channels - 1:
logging.debug(&#34;Finished downloading channels&#34;)
self.channels = self.partialChannels
self._fixupChannels()
# FIXME, the following should only be called after we have settings and channels
self.iface._connected() # Tell everyone else we are ready to go
else:
self._requestChannel(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;
@@ -310,51 +373,32 @@ class Node:
# 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;)
print(f&#34;Requesting channel {channelNum} info from remote node (this could take a while)&#34;)
logging.debug(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 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
logging.debug(f&#34;Received channel {stripnl(c)}&#34;)
index = c.index
return self._sendAdmin(p, wantResponse=True, onResponse=self.onResponseRequestChannel)
# 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
if quitEarly or index &gt;= self.iface.myInfo.max_channels - 1:
logging.debug(&#34;Finished downloading channels&#34;)
self.channels = self.partialChannels
self._fixupChannels()
# FIXME, the following should only be called after we have settings and channels
self.iface._connected() # Tell everone else we are ready to go
else:
self._requestChannel(index + 1)
return self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
# pylint: disable=R1710
def _sendAdmin(self, p: admin_pb2.AdminMessage, wantResponse=False,
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
adminIndex = self.iface.localNode._getAdminChannelIndex()
if self.noProto:
logging.warning(f&#34;Not sending packet because protocol use is disabled by noProto&#34;)
else:
if adminIndex == 0: # unless a special channel index was used, we want to use the admin index
adminIndex = self.iface.localNode._getAdminChannelIndex()
logging.debug(f&#39;adminIndex:{adminIndex}&#39;)
return self.iface.sendData(p, self.nodeNum,
portNum=portnums_pb2.PortNum.ADMIN_APP,
wantAck=True,
wantResponse=wantResponse,
onResponse=onResponse,
channelIndex=adminIndex)</code></pre>
return self.iface.sendData(p, self.nodeNum,
portNum=portnums_pb2.PortNum.ADMIN_APP,
wantAck=True,
wantResponse=wantResponse,
onResponse=onResponse,
channelIndex=adminIndex)</code></pre>
</details>
</section>
<section>
@@ -368,7 +412,7 @@ class Node:
<dl>
<dt id="meshtastic.node.Node"><code class="flex name class">
<span>class <span class="ident">Node</span></span>
<span>(</span><span>iface, nodeNum)</span>
<span>(</span><span>iface, nodeNum, noProto=False)</span>
</code></dt>
<dd>
<div class="desc"><p>A model of a (local or remote) node in the mesh</p>
@@ -384,20 +428,23 @@ class Node:
Includes methods for radioConfig and channels
&#34;&#34;&#34;
def __init__(self, iface, nodeNum):
def __init__(self, iface, nodeNum, noProto=False):
&#34;&#34;&#34;Constructor&#34;&#34;&#34;
self.iface = iface
self.nodeNum = nodeNum
self.radioConfig = None
self.channels = None
self._timeout = Timeout(maxSecs=60)
self._timeout = Timeout(maxSecs=300)
self.partialChannels = None
self.noProto = noProto
def showChannels(self):
&#34;&#34;&#34;Show human readable description of our channels.&#34;&#34;&#34;
print(&#34;Channels:&#34;)
if self.channels:
logging.debug(f&#39;self.channels:{self.channels}&#39;)
for c in self.channels:
#print(&#39;c.settings.psk:&#39;, c.settings.psk)
cStr = stripnl(MessageToJson(c.settings))
# only show if there is no psk (meaning disabled channel)
if c.settings.psk:
@@ -418,6 +465,7 @@ class Node:
def requestConfig(self):
&#34;&#34;&#34;Send regular MeshPackets to ask for settings and channels.&#34;&#34;&#34;
logging.debug(f&#34;requestConfig for nodeNum:{self.nodeNum}&#34;)
self.radioConfig = None
self.channels = None
self.partialChannels = [] # We keep our channels in a temp array until finished
@@ -454,6 +502,16 @@ class Node:
self._sendAdmin(p, adminIndex=adminIndex)
logging.debug(f&#34;Wrote channel {channelIndex}&#34;)
def getChannelByChannelIndex(self, channelIndex):
&#34;&#34;&#34;Get channel by channelIndex
channelIndex: number, typically 0-7; based on max number channels
returns: None if there is no channel found
&#34;&#34;&#34;
ch = None
if self.channels and 0 &lt;= channelIndex &lt; len(self.channels):
ch = self.channels[channelIndex]
return ch
def deleteChannel(self, channelIndex):
&#34;&#34;&#34;Delete the specifed channelIndex and shift other channels up&#34;&#34;&#34;
ch = self.channels[channelIndex]
@@ -476,7 +534,7 @@ class Node:
# *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
# (and written it), so we can start finding it by name again
adminIndex = 0
def getChannelByName(self, name):
@@ -503,6 +561,7 @@ class Node:
def setOwner(self, long_name=None, short_name=None, is_licensed=False, team=None):
&#34;&#34;&#34;Set device owner name&#34;&#34;&#34;
logging.debug(f&#34;in setOwner nodeNum:{self.nodeNum}&#34;)
nChars = 3
minChars = 2
if long_name is not None:
@@ -532,6 +591,11 @@ class Node:
if team is not None:
p.set_owner.team = team
# Note: These debug lines are used in unit tests
logging.debug(f&#39;p.set_owner.long_name:{p.set_owner.long_name}:&#39;)
logging.debug(f&#39;p.set_owner.short_name:{p.set_owner.short_name}:&#39;)
logging.debug(f&#39;p.set_owner.is_licensed:{p.set_owner.is_licensed}&#39;)
logging.debug(f&#39;p.set_owner.team:{p.set_owner.team}&#39;)
return self._sendAdmin(p)
def getURL(self, includeAll: bool = True):
@@ -567,6 +631,7 @@ class Node:
channelSet = apponly_pb2.ChannelSet()
channelSet.ParseFromString(decodedURL)
if len(channelSet.settings) == 0:
our_exit(&#34;Warning: There were no settings.&#34;)
@@ -577,39 +642,51 @@ class Node:
ch.index = i
ch.settings.CopyFrom(chs)
self.channels[ch.index] = ch
logging.debug(f&#39;Channel i:{i} ch:{ch}&#39;)
self.writeChannel(ch.index)
i = i + 1
def onResponseRequestSettings(self, p):
&#34;&#34;&#34;Handle the response packet for requesting settings _requestSettings()&#34;&#34;&#34;
logging.debug(f&#39;onResponseRequestSetting() p:{p}&#39;)
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(f&#39;self.radioConfig:{self.radioConfig}&#39;)
logging.debug(&#34;Received radio config, now fetching channels...&#34;)
self._timeout.reset() # We made foreward progress
self._requestChannel(0) # now start fetching channels
def _requestSettings(self):
&#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;
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:
print(&#34;Requesting preferences from remote node (this could take a while)&#34;)
print(&#34;Requesting preferences from remote node.&#34;)
print(&#34;Be sure:&#34;)
print(&#34; 1. There is a SECONDARY channel named &#39;admin&#39;.&#34;)
print(&#34; 2. The &#39;--seturl&#39; was used to configure.&#34;)
print(&#34; 3. All devices have the same modem config. (i.e., &#39;--ch-longfast&#39;)&#34;)
print(&#34; 4. All devices have been rebooted after all of the above. (optional, but recommended)&#34;)
print(&#34;Note: This could take a while (it requests remote channel configs, then writes config)&#34;)
return self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
return self._sendAdmin(p, wantResponse=True, onResponse=self.onResponseRequestSettings)
def exitSimulator(self):
&#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
logging.debug(&#39;in exitSimulator()&#39;)
return self._sendAdmin(p)
@@ -643,6 +720,34 @@ class Node:
self.channels.append(ch)
index += 1
def onResponseRequestChannel(self, p):
&#34;&#34;&#34;Handle the response packet for requesting a channel _requestChannel()&#34;&#34;&#34;
logging.debug(f&#39;onResponseRequestChannel() p:{p}&#39;)
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
logging.debug(f&#34;Received channel {stripnl(c)}&#34;)
index = c.index
# 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
if quitEarly or index &gt;= self.iface.myInfo.max_channels - 1:
logging.debug(&#34;Finished downloading channels&#34;)
self.channels = self.partialChannels
self._fixupChannels()
# FIXME, the following should only be called after we have settings and channels
self.iface._connected() # Tell everyone else we are ready to go
else:
self._requestChannel(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;
@@ -651,51 +756,32 @@ class Node:
# 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;)
print(f&#34;Requesting channel {channelNum} info from remote node (this could take a while)&#34;)
logging.debug(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 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
logging.debug(f&#34;Received channel {stripnl(c)}&#34;)
index = c.index
return self._sendAdmin(p, wantResponse=True, onResponse=self.onResponseRequestChannel)
# 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
if quitEarly or index &gt;= self.iface.myInfo.max_channels - 1:
logging.debug(&#34;Finished downloading channels&#34;)
self.channels = self.partialChannels
self._fixupChannels()
# FIXME, the following should only be called after we have settings and channels
self.iface._connected() # Tell everone else we are ready to go
else:
self._requestChannel(index + 1)
return self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
# pylint: disable=R1710
def _sendAdmin(self, p: admin_pb2.AdminMessage, wantResponse=False,
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
adminIndex = self.iface.localNode._getAdminChannelIndex()
if self.noProto:
logging.warning(f&#34;Not sending packet because protocol use is disabled by noProto&#34;)
else:
if adminIndex == 0: # unless a special channel index was used, we want to use the admin index
adminIndex = self.iface.localNode._getAdminChannelIndex()
logging.debug(f&#39;adminIndex:{adminIndex}&#39;)
return self.iface.sendData(p, self.nodeNum,
portNum=portnums_pb2.PortNum.ADMIN_APP,
wantAck=True,
wantResponse=wantResponse,
onResponse=onResponse,
channelIndex=adminIndex)</code></pre>
return self.iface.sendData(p, self.nodeNum,
portNum=portnums_pb2.PortNum.ADMIN_APP,
wantAck=True,
wantResponse=wantResponse,
onResponse=onResponse,
channelIndex=adminIndex)</code></pre>
</details>
<h3>Methods</h3>
<dl>
@@ -730,7 +816,7 @@ class Node:
# *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
# (and written it), so we can start finding it by name again
adminIndex = 0</code></pre>
</details>
</dd>
@@ -749,10 +835,33 @@ is ignored for other nodes)</p></div>
is ignored for other nodes)&#34;&#34;&#34;
p = admin_pb2.AdminMessage()
p.exit_simulator = True
logging.debug(&#39;in exitSimulator()&#39;)
return self._sendAdmin(p)</code></pre>
</details>
</dd>
<dt id="meshtastic.node.Node.getChannelByChannelIndex"><code class="name flex">
<span>def <span class="ident">getChannelByChannelIndex</span></span>(<span>self, channelIndex)</span>
</code></dt>
<dd>
<div class="desc"><p>Get channel by channelIndex
channelIndex: number, typically 0-7; based on max number channels
returns: None if there is no channel found</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def getChannelByChannelIndex(self, channelIndex):
&#34;&#34;&#34;Get channel by channelIndex
channelIndex: number, typically 0-7; based on max number channels
returns: None if there is no channel found
&#34;&#34;&#34;
ch = None
if self.channels and 0 &lt;= channelIndex &lt; len(self.channels):
ch = self.channels[channelIndex]
return ch</code></pre>
</details>
</dd>
<dt id="meshtastic.node.Node.getChannelByName"><code class="name flex">
<span>def <span class="ident">getChannelByName</span></span>(<span>self, name)</span>
</code></dt>
@@ -809,6 +918,68 @@ is ignored for other nodes)</p></div>
return f&#34;https://www.meshtastic.org/d/#{s}&#34;.replace(&#34;=&#34;, &#34;&#34;)</code></pre>
</details>
</dd>
<dt id="meshtastic.node.Node.onResponseRequestChannel"><code class="name flex">
<span>def <span class="ident">onResponseRequestChannel</span></span>(<span>self, p)</span>
</code></dt>
<dd>
<div class="desc"><p>Handle the response packet for requesting a channel _requestChannel()</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def onResponseRequestChannel(self, p):
&#34;&#34;&#34;Handle the response packet for requesting a channel _requestChannel()&#34;&#34;&#34;
logging.debug(f&#39;onResponseRequestChannel() p:{p}&#39;)
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
logging.debug(f&#34;Received channel {stripnl(c)}&#34;)
index = c.index
# 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
if quitEarly or index &gt;= self.iface.myInfo.max_channels - 1:
logging.debug(&#34;Finished downloading channels&#34;)
self.channels = self.partialChannels
self._fixupChannels()
# FIXME, the following should only be called after we have settings and channels
self.iface._connected() # Tell everyone else we are ready to go
else:
self._requestChannel(index + 1)</code></pre>
</details>
</dd>
<dt id="meshtastic.node.Node.onResponseRequestSettings"><code class="name flex">
<span>def <span class="ident">onResponseRequestSettings</span></span>(<span>self, p)</span>
</code></dt>
<dd>
<div class="desc"><p>Handle the response packet for requesting settings _requestSettings()</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def onResponseRequestSettings(self, p):
&#34;&#34;&#34;Handle the response packet for requesting settings _requestSettings()&#34;&#34;&#34;
logging.debug(f&#39;onResponseRequestSetting() p:{p}&#39;)
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(f&#39;self.radioConfig:{self.radioConfig}&#39;)
logging.debug(&#34;Received radio config, now fetching channels...&#34;)
self._timeout.reset() # We made foreward progress
self._requestChannel(0) # now start fetching channels</code></pre>
</details>
</dd>
<dt id="meshtastic.node.Node.reboot"><code class="name flex">
<span>def <span class="ident">reboot</span></span>(<span>self, secs: int = 10)</span>
</code></dt>
@@ -838,6 +1009,7 @@ is ignored for other nodes)</p></div>
</summary>
<pre><code class="python">def requestConfig(self):
&#34;&#34;&#34;Send regular MeshPackets to ask for settings and channels.&#34;&#34;&#34;
logging.debug(f&#34;requestConfig for nodeNum:{self.nodeNum}&#34;)
self.radioConfig = None
self.channels = None
self.partialChannels = [] # We keep our channels in a temp array until finished
@@ -856,6 +1028,7 @@ is ignored for other nodes)</p></div>
</summary>
<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;
logging.debug(f&#34;in setOwner nodeNum:{self.nodeNum}&#34;)
nChars = 3
minChars = 2
if long_name is not None:
@@ -885,6 +1058,11 @@ is ignored for other nodes)</p></div>
if team is not None:
p.set_owner.team = team
# Note: These debug lines are used in unit tests
logging.debug(f&#39;p.set_owner.long_name:{p.set_owner.long_name}:&#39;)
logging.debug(f&#39;p.set_owner.short_name:{p.set_owner.short_name}:&#39;)
logging.debug(f&#39;p.set_owner.is_licensed:{p.set_owner.is_licensed}&#39;)
logging.debug(f&#39;p.set_owner.team:{p.set_owner.team}&#39;)
return self._sendAdmin(p)</code></pre>
</details>
</dd>
@@ -918,6 +1096,7 @@ is ignored for other nodes)</p></div>
channelSet = apponly_pb2.ChannelSet()
channelSet.ParseFromString(decodedURL)
if len(channelSet.settings) == 0:
our_exit(&#34;Warning: There were no settings.&#34;)
@@ -928,6 +1107,7 @@ is ignored for other nodes)</p></div>
ch.index = i
ch.settings.CopyFrom(chs)
self.channels[ch.index] = ch
logging.debug(f&#39;Channel i:{i} ch:{ch}&#39;)
self.writeChannel(ch.index)
i = i + 1</code></pre>
</details>
@@ -945,7 +1125,9 @@ is ignored for other nodes)</p></div>
&#34;&#34;&#34;Show human readable description of our channels.&#34;&#34;&#34;
print(&#34;Channels:&#34;)
if self.channels:
logging.debug(f&#39;self.channels:{self.channels}&#39;)
for c in self.channels:
#print(&#39;c.settings.psk:&#39;, c.settings.psk)
cStr = stripnl(MessageToJson(c.settings))
# only show if there is no psk (meaning disabled channel)
if c.settings.psk:
@@ -1068,9 +1250,12 @@ is ignored for other nodes)</p></div>
<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.getChannelByChannelIndex" href="#meshtastic.node.Node.getChannelByChannelIndex">getChannelByChannelIndex</a></code></li>
<li><code><a title="meshtastic.node.Node.getChannelByName" href="#meshtastic.node.Node.getChannelByName">getChannelByName</a></code></li>
<li><code><a title="meshtastic.node.Node.getDisabledChannel" href="#meshtastic.node.Node.getDisabledChannel">getDisabledChannel</a></code></li>
<li><code><a title="meshtastic.node.Node.getURL" href="#meshtastic.node.Node.getURL">getURL</a></code></li>
<li><code><a title="meshtastic.node.Node.onResponseRequestChannel" href="#meshtastic.node.Node.onResponseRequestChannel">onResponseRequestChannel</a></code></li>
<li><code><a title="meshtastic.node.Node.onResponseRequestSettings" href="#meshtastic.node.Node.onResponseRequestSettings">onResponseRequestSettings</a></code></li>
<li><code><a title="meshtastic.node.Node.reboot" href="#meshtastic.node.Node.reboot">reboot</a></code></li>
<li><code><a title="meshtastic.node.Node.requestConfig" href="#meshtastic.node.Node.requestConfig">requestConfig</a></code></li>
<li><code><a title="meshtastic.node.Node.setOwner" href="#meshtastic.node.Node.setOwner">setOwner</a></code></li>

View File

File diff suppressed because one or more lines are too long

View File

@@ -27,18 +27,24 @@
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34; Remote hardware
<pre><code class="python">&#34;&#34;&#34;Remote hardware
&#34;&#34;&#34;
import logging
from pubsub import pub
from . import portnums_pb2, remote_hardware_pb2
from .util import our_exit
def onGPIOreceive(packet, interface):
&#34;&#34;&#34;Callback for received GPIO responses
FIXME figure out how to do closures with methods in python&#34;&#34;&#34;
&#34;&#34;&#34;
logging.debug(f&#34;packet:{packet} interface:{interface}&#34;)
hw = packet[&#34;decoded&#34;][&#34;remotehw&#34;]
print(f&#39;Received RemoteHardware typ={hw[&#34;typ&#34;]}, gpio_value={hw[&#34;gpioValue&#34;]}&#39;)
gpioValue = hw[&#34;gpioValue&#34;]
#print(f&#39;mask:{interface.mask}&#39;)
value = int(gpioValue) &amp; int(interface.mask)
print(f&#39;Received RemoteHardware typ={hw[&#34;typ&#34;]}, gpio_value={gpioValue} value={value}&#39;)
interface.gotResponse = True
class RemoteHardwareClient:
@@ -57,26 +63,28 @@ class RemoteHardwareClient:
self.iface = iface
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 &#34;\
&#34;to use this (secured) service (--ch-add gpio --info then --seturl)&#34;)
our_exit(
&#34;Warning: No channel named &#39;gpio&#39; was found.\n&#34;\
&#34;On the sending and receive nodes create a channel named &#39;gpio&#39;.\n&#34;\
&#34;For example, run &#39;--ch-add gpio&#39; on one device, then &#39;--seturl&#39; on\n&#34;\
&#34;the other devices using the url from the device where the channel was added.&#34;)
self.channelIndex = ch.index
pub.subscribe(
onGPIOreceive, &#34;meshtastic.receive.remotehw&#34;)
pub.subscribe(onGPIOreceive, &#34;meshtastic.receive.remotehw&#34;)
def _sendHardware(self, nodeid, r, wantResponse=False, onResponse=None):
if not nodeid:
raise Exception(
r&#34;You must set a destination node ID for this operation (use --dest \!xxxxxxxxx)&#34;)
our_exit(r&#34;Warning: Must use 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)
wantAck=True, channelIndex=self.channelIndex,
wantResponse=wantResponse, onResponse=onResponse)
def writeGPIOs(self, nodeid, mask, vals):
&#34;&#34;&#34;
Write the specified vals bits to the device GPIOs. Only bits in mask that
are 1 will be changed
&#34;&#34;&#34;
logging.debug(f&#39;writeGPIOs nodeid:{nodeid} mask:{mask} vals:{vals}&#39;)
r = remote_hardware_pb2.HardwareMessage()
r.typ = remote_hardware_pb2.HardwareMessage.Type.WRITE_GPIOS
r.gpio_mask = mask
@@ -85,6 +93,7 @@ class RemoteHardwareClient:
def readGPIOs(self, nodeid, mask, onResponse = None):
&#34;&#34;&#34;Read the specified bits from GPIO inputs on the device&#34;&#34;&#34;
logging.debug(f&#39;readGPIOs nodeid:{nodeid} mask:{mask}&#39;)
r = remote_hardware_pb2.HardwareMessage()
r.typ = remote_hardware_pb2.HardwareMessage.Type.READ_GPIOS
r.gpio_mask = mask
@@ -92,9 +101,11 @@ class RemoteHardwareClient:
def watchGPIOs(self, nodeid, mask):
&#34;&#34;&#34;Watch the specified bits from GPIO inputs on the device for changes&#34;&#34;&#34;
logging.debug(f&#39;watchGPIOs nodeid:{nodeid} mask:{mask}&#39;)
r = remote_hardware_pb2.HardwareMessage()
r.typ = remote_hardware_pb2.HardwareMessage.Type.WATCH_GPIOS
r.gpio_mask = mask
self.iface.mask = mask
return self._sendHardware(nodeid, r)</code></pre>
</details>
</section>
@@ -109,18 +120,21 @@ class RemoteHardwareClient:
<span>def <span class="ident">onGPIOreceive</span></span>(<span>packet, interface)</span>
</code></dt>
<dd>
<div class="desc"><p>Callback for received GPIO responses</p>
<p>FIXME figure out how to do closures with methods in python</p></div>
<div class="desc"><p>Callback for received GPIO responses</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def onGPIOreceive(packet, interface):
&#34;&#34;&#34;Callback for received GPIO responses
FIXME figure out how to do closures with methods in python&#34;&#34;&#34;
&#34;&#34;&#34;
logging.debug(f&#34;packet:{packet} interface:{interface}&#34;)
hw = packet[&#34;decoded&#34;][&#34;remotehw&#34;]
print(f&#39;Received RemoteHardware typ={hw[&#34;typ&#34;]}, gpio_value={hw[&#34;gpioValue&#34;]}&#39;)</code></pre>
gpioValue = hw[&#34;gpioValue&#34;]
#print(f&#39;mask:{interface.mask}&#39;)
value = int(gpioValue) &amp; int(interface.mask)
print(f&#39;Received RemoteHardware typ={hw[&#34;typ&#34;]}, gpio_value={gpioValue} value={value}&#39;)
interface.gotResponse = True</code></pre>
</details>
</dd>
</dl>
@@ -159,26 +173,28 @@ code for how you can connect to your own custom meshtastic services</p>
self.iface = iface
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 &#34;\
&#34;to use this (secured) service (--ch-add gpio --info then --seturl)&#34;)
our_exit(
&#34;Warning: No channel named &#39;gpio&#39; was found.\n&#34;\
&#34;On the sending and receive nodes create a channel named &#39;gpio&#39;.\n&#34;\
&#34;For example, run &#39;--ch-add gpio&#39; on one device, then &#39;--seturl&#39; on\n&#34;\
&#34;the other devices using the url from the device where the channel was added.&#34;)
self.channelIndex = ch.index
pub.subscribe(
onGPIOreceive, &#34;meshtastic.receive.remotehw&#34;)
pub.subscribe(onGPIOreceive, &#34;meshtastic.receive.remotehw&#34;)
def _sendHardware(self, nodeid, r, wantResponse=False, onResponse=None):
if not nodeid:
raise Exception(
r&#34;You must set a destination node ID for this operation (use --dest \!xxxxxxxxx)&#34;)
our_exit(r&#34;Warning: Must use 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)
wantAck=True, channelIndex=self.channelIndex,
wantResponse=wantResponse, onResponse=onResponse)
def writeGPIOs(self, nodeid, mask, vals):
&#34;&#34;&#34;
Write the specified vals bits to the device GPIOs. Only bits in mask that
are 1 will be changed
&#34;&#34;&#34;
logging.debug(f&#39;writeGPIOs nodeid:{nodeid} mask:{mask} vals:{vals}&#39;)
r = remote_hardware_pb2.HardwareMessage()
r.typ = remote_hardware_pb2.HardwareMessage.Type.WRITE_GPIOS
r.gpio_mask = mask
@@ -187,6 +203,7 @@ code for how you can connect to your own custom meshtastic services</p>
def readGPIOs(self, nodeid, mask, onResponse = None):
&#34;&#34;&#34;Read the specified bits from GPIO inputs on the device&#34;&#34;&#34;
logging.debug(f&#39;readGPIOs nodeid:{nodeid} mask:{mask}&#39;)
r = remote_hardware_pb2.HardwareMessage()
r.typ = remote_hardware_pb2.HardwareMessage.Type.READ_GPIOS
r.gpio_mask = mask
@@ -194,9 +211,11 @@ code for how you can connect to your own custom meshtastic services</p>
def watchGPIOs(self, nodeid, mask):
&#34;&#34;&#34;Watch the specified bits from GPIO inputs on the device for changes&#34;&#34;&#34;
logging.debug(f&#39;watchGPIOs nodeid:{nodeid} mask:{mask}&#39;)
r = remote_hardware_pb2.HardwareMessage()
r.typ = remote_hardware_pb2.HardwareMessage.Type.WATCH_GPIOS
r.gpio_mask = mask
self.iface.mask = mask
return self._sendHardware(nodeid, r)</code></pre>
</details>
<h3>Methods</h3>
@@ -212,6 +231,7 @@ code for how you can connect to your own custom meshtastic services</p>
</summary>
<pre><code class="python">def readGPIOs(self, nodeid, mask, onResponse = None):
&#34;&#34;&#34;Read the specified bits from GPIO inputs on the device&#34;&#34;&#34;
logging.debug(f&#39;readGPIOs nodeid:{nodeid} mask:{mask}&#39;)
r = remote_hardware_pb2.HardwareMessage()
r.typ = remote_hardware_pb2.HardwareMessage.Type.READ_GPIOS
r.gpio_mask = mask
@@ -229,9 +249,11 @@ code for how you can connect to your own custom meshtastic services</p>
</summary>
<pre><code class="python">def watchGPIOs(self, nodeid, mask):
&#34;&#34;&#34;Watch the specified bits from GPIO inputs on the device for changes&#34;&#34;&#34;
logging.debug(f&#39;watchGPIOs nodeid:{nodeid} mask:{mask}&#39;)
r = remote_hardware_pb2.HardwareMessage()
r.typ = remote_hardware_pb2.HardwareMessage.Type.WATCH_GPIOS
r.gpio_mask = mask
self.iface.mask = mask
return self._sendHardware(nodeid, r)</code></pre>
</details>
</dd>
@@ -251,6 +273,7 @@ are 1 will be changed</p></div>
Write the specified vals bits to the device GPIOs. Only bits in mask that
are 1 will be changed
&#34;&#34;&#34;
logging.debug(f&#39;writeGPIOs nodeid:{nodeid} mask:{mask} vals:{vals}&#39;)
r = remote_hardware_pb2.HardwareMessage()
r.typ = remote_hardware_pb2.HardwareMessage.Type.WRITE_GPIOS
r.gpio_mask = mask

View File

@@ -46,7 +46,7 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package=&#39;&#39;,
syntax=&#39;proto3&#39;,
serialized_options=b&#39;\n\023com.geeksville.meshB\025StoreAndForwardProtosH\003Z!github.com/meshtastic/gomeshproto&#39;,
serialized_pb=b&#39;\n\x12storeforward.proto\&#34;\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\&#34;\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&#39;
serialized_pb=b&#39;\n\x12storeforward.proto\&#34;\x8a\x06\n\x0fStoreAndForward\x12,\n\x02rr\x18\x01 \x01(\x0e\x32 .StoreAndForward.RequestResponse\x12*\n\x05stats\x18\x02 \x01(\x0b\x32\x1b.StoreAndForward.Statistics\x12)\n\x07history\x18\x03 \x01(\x0b\x32\x18.StoreAndForward.History\x12-\n\theartbeat\x18\x04 \x01(\x0b\x32\x1a.StoreAndForward.Heartbeat\x1a\xcd\x01\n\nStatistics\x12\x16\n\x0emessages_total\x18\x01 \x01(\r\x12\x16\n\x0emessages_saved\x18\x02 \x01(\r\x12\x14\n\x0cmessages_max\x18\x03 \x01(\r\x12\x0f\n\x07up_time\x18\x04 \x01(\r\x12\x10\n\x08requests\x18\x05 \x01(\r\x12\x18\n\x10requests_history\x18\x06 \x01(\r\x12\x11\n\theartbeat\x18\x07 \x01(\x08\x12\x12\n\nreturn_max\x18\x08 \x01(\r\x12\x15\n\rreturn_window\x18\t \x01(\r\x1aI\n\x07History\x12\x18\n\x10history_messages\x18\x01 \x01(\r\x12\x0e\n\x06window\x18\x02 \x01(\r\x12\x14\n\x0clast_request\x18\x03 \x01(\r\x1a.\n\tHeartbeat\x12\x0e\n\x06period\x18\x01 \x01(\r\x12\x11\n\tsecondary\x18\x02 \x01(\r\&#34;\xf7\x01\n\x0fRequestResponse\x12\t\n\x05UNSET\x10\x00\x12\x10\n\x0cROUTER_ERROR\x10\x01\x12\x14\n\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n\x0bROUTER_PING\x10\x03\x12\x0f\n\x0bROUTER_PONG\x10\x04\x12\x0f\n\x0bROUTER_BUSY\x10\x05\x12\x12\n\x0eROUTER_HISTORY\x10\x06\x12\x10\n\x0c\x43LIENT_ERROR\x10\x65\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x66\x12\x10\n\x0c\x43LIENT_STATS\x10g\x12\x0f\n\x0b\x43LIENT_PING\x10h\x12\x0f\n\x0b\x43LIENT_PONG\x10i\x12\x10\n\x0c\x43LIENT_ABORT\x10jBQ\n\x13\x63om.geeksville.meshB\x15StoreAndForwardProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3&#39;
)
@@ -82,30 +82,38 @@ _STOREANDFORWARD_REQUESTRESPONSE = _descriptor.EnumDescriptor(
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;CLIENT_ERROR&#39;, index=6, number=101,
name=&#39;ROUTER_HISTORY&#39;, index=6, number=6,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;CLIENT_HISTORY&#39;, index=7, number=102,
name=&#39;CLIENT_ERROR&#39;, index=7, number=101,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;CLIENT_STATS&#39;, index=8, number=103,
name=&#39;CLIENT_HISTORY&#39;, index=8, number=102,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;CLIENT_PING&#39;, index=9, number=104,
name=&#39;CLIENT_STATS&#39;, index=9, number=103,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;CLIENT_PONG&#39;, index=10, number=105,
name=&#39;CLIENT_PING&#39;, index=10, number=104,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;CLIENT_PONG&#39;, index=11, number=105,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name=&#39;CLIENT_ABORT&#39;, index=12, number=106,
serialized_options=None,
type=None),
],
containing_type=None,
serialized_options=None,
serialized_start=429,
serialized_end=638,
serialized_start=554,
serialized_end=801,
)
_sym_db.RegisterEnumDescriptor(_STOREANDFORWARD_REQUESTRESPONSE)
@@ -118,63 +126,63 @@ _STOREANDFORWARD_STATISTICS = _descriptor.Descriptor(
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name=&#39;MessagesTotal&#39;, full_name=&#39;StoreAndForward.Statistics.MessagesTotal&#39;, index=0,
name=&#39;messages_total&#39;, full_name=&#39;StoreAndForward.Statistics.messages_total&#39;, 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=&#39;MessagesSaved&#39;, full_name=&#39;StoreAndForward.Statistics.MessagesSaved&#39;, index=1,
name=&#39;messages_saved&#39;, full_name=&#39;StoreAndForward.Statistics.messages_saved&#39;, 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=&#39;MessagesMax&#39;, full_name=&#39;StoreAndForward.Statistics.MessagesMax&#39;, index=2,
name=&#39;messages_max&#39;, full_name=&#39;StoreAndForward.Statistics.messages_max&#39;, 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=&#39;UpTime&#39;, full_name=&#39;StoreAndForward.Statistics.UpTime&#39;, index=3,
name=&#39;up_time&#39;, full_name=&#39;StoreAndForward.Statistics.up_time&#39;, 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=&#39;Requests&#39;, full_name=&#39;StoreAndForward.Statistics.Requests&#39;, index=4,
name=&#39;requests&#39;, full_name=&#39;StoreAndForward.Statistics.requests&#39;, 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=&#39;RequestsHistory&#39;, full_name=&#39;StoreAndForward.Statistics.RequestsHistory&#39;, index=5,
name=&#39;requests_history&#39;, full_name=&#39;StoreAndForward.Statistics.requests_history&#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),
_descriptor.FieldDescriptor(
name=&#39;Heartbeat&#39;, full_name=&#39;StoreAndForward.Statistics.Heartbeat&#39;, index=6,
name=&#39;heartbeat&#39;, full_name=&#39;StoreAndForward.Statistics.heartbeat&#39;, 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=&#39;ReturnMax&#39;, full_name=&#39;StoreAndForward.Statistics.ReturnMax&#39;, index=7,
name=&#39;return_max&#39;, full_name=&#39;StoreAndForward.Statistics.return_max&#39;, 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=&#39;ReturnWindow&#39;, full_name=&#39;StoreAndForward.Statistics.ReturnWindow&#39;, index=8,
name=&#39;return_window&#39;, full_name=&#39;StoreAndForward.Statistics.return_window&#39;, 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,
@@ -192,8 +200,8 @@ _STOREANDFORWARD_STATISTICS = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=176,
serialized_end=374,
serialized_start=223,
serialized_end=428,
)
_STOREANDFORWARD_HISTORY = _descriptor.Descriptor(
@@ -204,14 +212,58 @@ _STOREANDFORWARD_HISTORY = _descriptor.Descriptor(
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name=&#39;HistoryMessages&#39;, full_name=&#39;StoreAndForward.History.HistoryMessages&#39;, index=0,
name=&#39;history_messages&#39;, full_name=&#39;StoreAndForward.History.history_messages&#39;, 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=&#39;Window&#39;, full_name=&#39;StoreAndForward.History.Window&#39;, index=1,
name=&#39;window&#39;, full_name=&#39;StoreAndForward.History.window&#39;, 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=&#39;last_request&#39;, full_name=&#39;StoreAndForward.History.last_request&#39;, 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),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax=&#39;proto3&#39;,
extension_ranges=[],
oneofs=[
],
serialized_start=430,
serialized_end=503,
)
_STOREANDFORWARD_HEARTBEAT = _descriptor.Descriptor(
name=&#39;Heartbeat&#39;,
full_name=&#39;StoreAndForward.Heartbeat&#39;,
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name=&#39;period&#39;, full_name=&#39;StoreAndForward.Heartbeat.period&#39;, 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=&#39;secondary&#39;, full_name=&#39;StoreAndForward.Heartbeat.secondary&#39;, 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,
@@ -229,8 +281,8 @@ _STOREANDFORWARD_HISTORY = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=376,
serialized_end=426,
serialized_start=505,
serialized_end=551,
)
_STOREANDFORWARD = _descriptor.Descriptor(
@@ -261,10 +313,17 @@ _STOREANDFORWARD = _descriptor.Descriptor(
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name=&#39;heartbeat&#39;, full_name=&#39;StoreAndForward.heartbeat&#39;, index=3,
number=4, 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, ],
nested_types=[_STOREANDFORWARD_STATISTICS, _STOREANDFORWARD_HISTORY, _STOREANDFORWARD_HEARTBEAT, ],
enum_types=[
_STOREANDFORWARD_REQUESTRESPONSE,
],
@@ -275,14 +334,16 @@ _STOREANDFORWARD = _descriptor.Descriptor(
oneofs=[
],
serialized_start=23,
serialized_end=638,
serialized_end=801,
)
_STOREANDFORWARD_STATISTICS.containing_type = _STOREANDFORWARD
_STOREANDFORWARD_HISTORY.containing_type = _STOREANDFORWARD
_STOREANDFORWARD_HEARTBEAT.containing_type = _STOREANDFORWARD
_STOREANDFORWARD.fields_by_name[&#39;rr&#39;].enum_type = _STOREANDFORWARD_REQUESTRESPONSE
_STOREANDFORWARD.fields_by_name[&#39;stats&#39;].message_type = _STOREANDFORWARD_STATISTICS
_STOREANDFORWARD.fields_by_name[&#39;history&#39;].message_type = _STOREANDFORWARD_HISTORY
_STOREANDFORWARD.fields_by_name[&#39;heartbeat&#39;].message_type = _STOREANDFORWARD_HEARTBEAT
_STOREANDFORWARD_REQUESTRESPONSE.containing_type = _STOREANDFORWARD
DESCRIPTOR.message_types_by_name[&#39;StoreAndForward&#39;] = _STOREANDFORWARD
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
@@ -302,6 +363,13 @@ StoreAndForward = _reflection.GeneratedProtocolMessageType(&#39;StoreAndForward&
# @@protoc_insertion_point(class_scope:StoreAndForward.History)
})
,
&#39;Heartbeat&#39; : _reflection.GeneratedProtocolMessageType(&#39;Heartbeat&#39;, (_message.Message,), {
&#39;DESCRIPTOR&#39; : _STOREANDFORWARD_HEARTBEAT,
&#39;__module__&#39; : &#39;storeforward_pb2&#39;
# @@protoc_insertion_point(class_scope:StoreAndForward.Heartbeat)
})
,
&#39;DESCRIPTOR&#39; : _STOREANDFORWARD,
&#39;__module__&#39; : &#39;storeforward_pb2&#39;
# @@protoc_insertion_point(class_scope:StoreAndForward)
@@ -309,6 +377,7 @@ StoreAndForward = _reflection.GeneratedProtocolMessageType(&#39;StoreAndForward&
_sym_db.RegisterMessage(StoreAndForward)
_sym_db.RegisterMessage(StoreAndForward.Statistics)
_sym_db.RegisterMessage(StoreAndForward.History)
_sym_db.RegisterMessage(StoreAndForward.Heartbeat)
DESCRIPTOR._options = None
@@ -340,6 +409,10 @@ shown below.</p></div>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="meshtastic.storeforward_pb2.StoreAndForward.CLIENT_ABORT"><code class="name">var <span class="ident">CLIENT_ABORT</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.storeforward_pb2.StoreAndForward.CLIENT_ERROR"><code class="name">var <span class="ident">CLIENT_ERROR</span></code></dt>
<dd>
<div class="desc"></div>
@@ -364,10 +437,22 @@ shown below.</p></div>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.storeforward_pb2.StoreAndForward.HEARTBEAT_FIELD_NUMBER"><code class="name">var <span class="ident">HEARTBEAT_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.storeforward_pb2.StoreAndForward.HISTORY_FIELD_NUMBER"><code class="name">var <span class="ident">HISTORY_FIELD_NUMBER</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.storeforward_pb2.StoreAndForward.Heartbeat"><code class="name">var <span class="ident">Heartbeat</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>
</dd>
<dt id="meshtastic.storeforward_pb2.StoreAndForward.History"><code class="name">var <span class="ident">History</span></code></dt>
<dd>
<div class="desc"><p>Abstract base class for protocol messages.</p>
@@ -388,6 +473,10 @@ shown below.</p></div>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.storeforward_pb2.StoreAndForward.ROUTER_HISTORY"><code class="name">var <span class="ident">ROUTER_HISTORY</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="meshtastic.storeforward_pb2.StoreAndForward.ROUTER_PING"><code class="name">var <span class="ident">ROUTER_PING</span></code></dt>
<dd>
<div class="desc"></div>
@@ -458,6 +547,29 @@ shown below.</p></div>
</dl>
<h3>Instance variables</h3>
<dl>
<dt id="meshtastic.storeforward_pb2.StoreAndForward.heartbeat"><code class="name">var <span class="ident">heartbeat</span></code></dt>
<dd>
<div class="desc"><p>Getter for heartbeat.</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.storeforward_pb2.StoreAndForward.history"><code class="name">var <span class="ident">history</span></code></dt>
<dd>
<div class="desc"><p>Getter for history.</p></div>
@@ -997,6 +1109,7 @@ and propagates this to our listener iff this was a state change.</p></div>
<h4><code><a title="meshtastic.storeforward_pb2.StoreAndForward" href="#meshtastic.storeforward_pb2.StoreAndForward">StoreAndForward</a></code></h4>
<ul class="">
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.ByteSize" href="#meshtastic.storeforward_pb2.StoreAndForward.ByteSize">ByteSize</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.CLIENT_ABORT" href="#meshtastic.storeforward_pb2.StoreAndForward.CLIENT_ABORT">CLIENT_ABORT</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.CLIENT_ERROR" href="#meshtastic.storeforward_pb2.StoreAndForward.CLIENT_ERROR">CLIENT_ERROR</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.CLIENT_HISTORY" href="#meshtastic.storeforward_pb2.StoreAndForward.CLIENT_HISTORY">CLIENT_HISTORY</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.CLIENT_PING" href="#meshtastic.storeforward_pb2.StoreAndForward.CLIENT_PING">CLIENT_PING</a></code></li>
@@ -1008,8 +1121,10 @@ and propagates this to our listener iff this was a state change.</p></div>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.DiscardUnknownFields" href="#meshtastic.storeforward_pb2.StoreAndForward.DiscardUnknownFields">DiscardUnknownFields</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.FindInitializationErrors" href="#meshtastic.storeforward_pb2.StoreAndForward.FindInitializationErrors">FindInitializationErrors</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.FromString" href="#meshtastic.storeforward_pb2.StoreAndForward.FromString">FromString</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.HEARTBEAT_FIELD_NUMBER" href="#meshtastic.storeforward_pb2.StoreAndForward.HEARTBEAT_FIELD_NUMBER">HEARTBEAT_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.HISTORY_FIELD_NUMBER" href="#meshtastic.storeforward_pb2.StoreAndForward.HISTORY_FIELD_NUMBER">HISTORY_FIELD_NUMBER</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.HasField" href="#meshtastic.storeforward_pb2.StoreAndForward.HasField">HasField</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.Heartbeat" href="#meshtastic.storeforward_pb2.StoreAndForward.Heartbeat">Heartbeat</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.History" href="#meshtastic.storeforward_pb2.StoreAndForward.History">History</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.IsInitialized" href="#meshtastic.storeforward_pb2.StoreAndForward.IsInitialized">IsInitialized</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.ListFields" href="#meshtastic.storeforward_pb2.StoreAndForward.ListFields">ListFields</a></code></li>
@@ -1018,6 +1133,7 @@ and propagates this to our listener iff this was a state change.</p></div>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.ROUTER_BUSY" href="#meshtastic.storeforward_pb2.StoreAndForward.ROUTER_BUSY">ROUTER_BUSY</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.ROUTER_ERROR" href="#meshtastic.storeforward_pb2.StoreAndForward.ROUTER_ERROR">ROUTER_ERROR</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.ROUTER_HEARTBEAT" href="#meshtastic.storeforward_pb2.StoreAndForward.ROUTER_HEARTBEAT">ROUTER_HEARTBEAT</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.ROUTER_HISTORY" href="#meshtastic.storeforward_pb2.StoreAndForward.ROUTER_HISTORY">ROUTER_HISTORY</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.ROUTER_PING" href="#meshtastic.storeforward_pb2.StoreAndForward.ROUTER_PING">ROUTER_PING</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.ROUTER_PONG" href="#meshtastic.storeforward_pb2.StoreAndForward.ROUTER_PONG">ROUTER_PONG</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.RR_FIELD_NUMBER" href="#meshtastic.storeforward_pb2.StoreAndForward.RR_FIELD_NUMBER">RR_FIELD_NUMBER</a></code></li>
@@ -1031,6 +1147,7 @@ and propagates this to our listener iff this was a state change.</p></div>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.UNSET" href="#meshtastic.storeforward_pb2.StoreAndForward.UNSET">UNSET</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.UnknownFields" href="#meshtastic.storeforward_pb2.StoreAndForward.UnknownFields">UnknownFields</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.WhichOneof" href="#meshtastic.storeforward_pb2.StoreAndForward.WhichOneof">WhichOneof</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.heartbeat" href="#meshtastic.storeforward_pb2.StoreAndForward.heartbeat">heartbeat</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.history" href="#meshtastic.storeforward_pb2.StoreAndForward.history">history</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.rr" href="#meshtastic.storeforward_pb2.StoreAndForward.rr">rr</a></code></li>
<li><code><a title="meshtastic.storeforward_pb2.StoreAndForward.stats" href="#meshtastic.storeforward_pb2.StoreAndForward.stats">stats</a></code></li>

View File

@@ -53,7 +53,6 @@ class StreamInterface(MeshInterface):
&#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})
@@ -62,15 +61,14 @@ class StreamInterface(MeshInterface):
Exception: [description]
&#34;&#34;&#34;
if not hasattr(self, &#39;stream&#39;):
if not hasattr(self, &#39;stream&#39;) and not noProto:
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)
self._rxThread = threading.Thread(target=self.__reader, args=(), daemon=True)
MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto)
@@ -122,7 +120,10 @@ class StreamInterface(MeshInterface):
def _readBytes(self, length):
&#34;&#34;&#34;Read an array of bytes from our stream&#34;&#34;&#34;
return self.stream.read(length)
if self.stream:
return self.stream.read(length)
else:
return None
def _sendToRadioImpl(self, toRadio):
&#34;&#34;&#34;Send a ToRadio protobuf to the device&#34;&#34;&#34;
@@ -131,6 +132,7 @@ class StreamInterface(MeshInterface):
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])
logging.debug(f&#39;sending header:{header} b:{b}&#39;)
self._writeBytes(header + b)
def close(self):
@@ -145,16 +147,18 @@ class StreamInterface(MeshInterface):
def __reader(self):
&#34;&#34;&#34;The reader thread that reads bytes from our stream&#34;&#34;&#34;
logging.debug(&#39;in __reader()&#39;)
empty = bytes()
try:
while not self._wantExit:
# logging.debug(&#34;reading character&#34;)
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;)
logging.debug(&#34;In reader loop&#34;)
#logging.debug(f&#34;read returned {b}&#34;)
if len(b) &gt; 0:
c = b[0]
#logging.debug(f&#39;c:{c}&#39;)
ptr = len(self._rxBuf)
# Assume we want to append this byte, fixme use bytearray instead
@@ -173,12 +177,13 @@ class StreamInterface(MeshInterface):
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
#logging.debug(&#39;at least we received a header&#39;)
# big endian length follows 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
self._rxBuf = empty # length was out out bounds, restart
if len(self._rxBuf) != 0 and ptr + 1 &gt;= packetlen + HEADER_LEN:
try:
@@ -220,7 +225,6 @@ class StreamInterface(MeshInterface):
<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>
@@ -241,7 +245,6 @@ device will be emitted to that stream. (default: {None})</p>
&#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})
@@ -250,15 +253,14 @@ device will be emitted to that stream. (default: {None})</p>
Exception: [description]
&#34;&#34;&#34;
if not hasattr(self, &#39;stream&#39;):
if not hasattr(self, &#39;stream&#39;) and not noProto:
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)
self._rxThread = threading.Thread(target=self.__reader, args=(), daemon=True)
MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto)
@@ -310,7 +312,10 @@ device will be emitted to that stream. (default: {None})</p>
def _readBytes(self, length):
&#34;&#34;&#34;Read an array of bytes from our stream&#34;&#34;&#34;
return self.stream.read(length)
if self.stream:
return self.stream.read(length)
else:
return None
def _sendToRadioImpl(self, toRadio):
&#34;&#34;&#34;Send a ToRadio protobuf to the device&#34;&#34;&#34;
@@ -319,6 +324,7 @@ device will be emitted to that stream. (default: {None})</p>
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])
logging.debug(f&#39;sending header:{header} b:{b}&#39;)
self._writeBytes(header + b)
def close(self):
@@ -333,16 +339,18 @@ device will be emitted to that stream. (default: {None})</p>
def __reader(self):
&#34;&#34;&#34;The reader thread that reads bytes from our stream&#34;&#34;&#34;
logging.debug(&#39;in __reader()&#39;)
empty = bytes()
try:
while not self._wantExit:
# logging.debug(&#34;reading character&#34;)
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;)
logging.debug(&#34;In reader loop&#34;)
#logging.debug(f&#34;read returned {b}&#34;)
if len(b) &gt; 0:
c = b[0]
#logging.debug(f&#39;c:{c}&#39;)
ptr = len(self._rxBuf)
# Assume we want to append this byte, fixme use bytearray instead
@@ -361,12 +369,13 @@ device will be emitted to that stream. (default: {None})</p>
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
#logging.debug(&#39;at least we received a header&#39;)
# big endian length follows 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
self._rxBuf = empty # length was out out bounds, restart
if len(self._rxBuf) != 0 and ptr + 1 &gt;= packetlen + HEADER_LEN:
try:

View File

@@ -46,18 +46,29 @@ class TCPInterface(StreamInterface):
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)
self.hostname = hostname
self.portNumber = portNumber
if connectNow:
logging.debug(f&#34;Connecting to {hostname}&#34;)
server_address = (hostname, portNumber)
sock = socket.create_connection(server_address)
self.socket = sock
else:
self.socket = None
StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto,
connectNow=connectNow)
def myConnect(self):
&#34;&#34;&#34;Connect to socket&#34;&#34;&#34;
server_address = (self.hostname, self.portNumber)
sock = socket.create_connection(server_address)
self.socket = sock
def close(self):
&#34;&#34;&#34;Close a connection to the device&#34;&#34;&#34;
@@ -115,18 +126,29 @@ hostname {string} &ndash; Hostname/IP address of the device to connect to</p></d
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)
self.hostname = hostname
self.portNumber = portNumber
if connectNow:
logging.debug(f&#34;Connecting to {hostname}&#34;)
server_address = (hostname, portNumber)
sock = socket.create_connection(server_address)
self.socket = sock
else:
self.socket = None
StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto,
connectNow=connectNow)
def myConnect(self):
&#34;&#34;&#34;Connect to socket&#34;&#34;&#34;
server_address = (self.hostname, self.portNumber)
sock = socket.create_connection(server_address)
self.socket = sock
def close(self):
&#34;&#34;&#34;Close a connection to the device&#34;&#34;&#34;
@@ -155,6 +177,25 @@ hostname {string} &ndash; Hostname/IP address of the device to connect to</p></d
<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>Methods</h3>
<dl>
<dt id="meshtastic.tcp_interface.TCPInterface.myConnect"><code class="name flex">
<span>def <span class="ident">myConnect</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Connect to socket</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def myConnect(self):
&#34;&#34;&#34;Connect to socket&#34;&#34;&#34;
server_address = (self.hostname, self.portNumber)
sock = socket.create_connection(server_address)
self.socket = sock</code></pre>
</details>
</dd>
</dl>
<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>:
@@ -194,6 +235,9 @@ hostname {string} &ndash; Hostname/IP address of the device to connect to</p></d
<ul>
<li>
<h4><code><a title="meshtastic.tcp_interface.TCPInterface" href="#meshtastic.tcp_interface.TCPInterface">TCPInterface</a></code></h4>
<ul class="">
<li><code><a title="meshtastic.tcp_interface.TCPInterface.myConnect" href="#meshtastic.tcp_interface.TCPInterface.myConnect">myConnect</a></code></li>
</ul>
</li>
</ul>
</li>

View File

@@ -1,80 +0,0 @@
<!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

@@ -1,177 +0,0 @@
<!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

@@ -1,982 +0,0 @@
<!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

@@ -1,236 +0,0 @@
<!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

@@ -31,9 +31,12 @@
import argparse
from unittest.mock import MagicMock
import pytest
from meshtastic.__main__ import Globals
from ..mesh_interface import MeshInterface
@pytest.fixture
def reset_globals():
@@ -41,7 +44,50 @@ def reset_globals():
parser = None
parser = argparse.ArgumentParser()
Globals.getInstance().reset()
Globals.getInstance().set_parser(parser)</code></pre>
Globals.getInstance().set_parser(parser)
@pytest.fixture
def iface_with_nodes():
&#34;&#34;&#34;Fixture to setup some nodes.&#34;&#34;&#34;
nodesById = {
&#39;!9388f81c&#39;: {
&#39;num&#39;: 2475227164,
&#39;user&#39;: {
&#39;id&#39;: &#39;!9388f81c&#39;,
&#39;longName&#39;: &#39;Unknown f81c&#39;,
&#39;shortName&#39;: &#39;?1C&#39;,
&#39;macaddr&#39;: &#39;RBeTiPgc&#39;,
&#39;hwModel&#39;: &#39;TBEAM&#39;
},
&#39;position&#39;: {},
&#39;lastHeard&#39;: 1640204888
}
}
nodesByNum = {
2475227164: {
&#39;num&#39;: 2475227164,
&#39;user&#39;: {
&#39;id&#39;: &#39;!9388f81c&#39;,
&#39;longName&#39;: &#39;Unknown f81c&#39;,
&#39;shortName&#39;: &#39;?1C&#39;,
&#39;macaddr&#39;: &#39;RBeTiPgc&#39;,
&#39;hwModel&#39;: &#39;TBEAM&#39;
},
&#39;position&#39;: {
&#39;time&#39;: 1640206266
},
&#39;lastHeard&#39;: 1640206266
}
}
iface = MeshInterface(noProto=True)
iface.nodes = nodesById
iface.nodesByNum = nodesByNum
myInfo = MagicMock()
iface.myInfo = myInfo
iface.myInfo.my_node_num = 2475227164
return iface</code></pre>
</details>
</section>
<section>
@@ -51,6 +97,58 @@ def reset_globals():
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.tests.conftest.iface_with_nodes"><code class="name flex">
<span>def <span class="ident">iface_with_nodes</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Fixture to setup some nodes.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.fixture
def iface_with_nodes():
&#34;&#34;&#34;Fixture to setup some nodes.&#34;&#34;&#34;
nodesById = {
&#39;!9388f81c&#39;: {
&#39;num&#39;: 2475227164,
&#39;user&#39;: {
&#39;id&#39;: &#39;!9388f81c&#39;,
&#39;longName&#39;: &#39;Unknown f81c&#39;,
&#39;shortName&#39;: &#39;?1C&#39;,
&#39;macaddr&#39;: &#39;RBeTiPgc&#39;,
&#39;hwModel&#39;: &#39;TBEAM&#39;
},
&#39;position&#39;: {},
&#39;lastHeard&#39;: 1640204888
}
}
nodesByNum = {
2475227164: {
&#39;num&#39;: 2475227164,
&#39;user&#39;: {
&#39;id&#39;: &#39;!9388f81c&#39;,
&#39;longName&#39;: &#39;Unknown f81c&#39;,
&#39;shortName&#39;: &#39;?1C&#39;,
&#39;macaddr&#39;: &#39;RBeTiPgc&#39;,
&#39;hwModel&#39;: &#39;TBEAM&#39;
},
&#39;position&#39;: {
&#39;time&#39;: 1640206266
},
&#39;lastHeard&#39;: 1640206266
}
}
iface = MeshInterface(noProto=True)
iface.nodes = nodesById
iface.nodesByNum = nodesByNum
myInfo = MagicMock()
iface.myInfo = myInfo
iface.myInfo.my_node_num = 2475227164
return iface</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.conftest.reset_globals"><code class="name flex">
<span>def <span class="ident">reset_globals</span></span>(<span>)</span>
</code></dt>
@@ -87,6 +185,7 @@ def reset_globals():
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.tests.conftest.iface_with_nodes" href="#meshtastic.tests.conftest.iface_with_nodes">iface_with_nodes</a></code></li>
<li><code><a title="meshtastic.tests.conftest.reset_globals" href="#meshtastic.tests.conftest.reset_globals">reset_globals</a></code></li>
</ul>
</li>

View File

@@ -34,6 +34,12 @@
<dd>
<div class="desc"><p>Meshtastic unit tests for ble_interface.py</p></div>
</dd>
<dt><code class="name"><a title="meshtastic.tests.test_examples" href="test_examples.html">meshtastic.tests.test_examples</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic test that the examples run as expected.
We assume you have a python virtual environment in current directory.
If not, you need to run: …</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>
@@ -54,6 +60,10 @@
<dd>
<div class="desc"><p>Meshtastic unit tests for node.py</p></div>
</dd>
<dt><code class="name"><a title="meshtastic.tests.test_remote_hardware" href="test_remote_hardware.html">meshtastic.tests.test_remote_hardware</a></code></dt>
<dd>
<div class="desc"><p>Meshtastic unit tests for remote_hardware.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>
@@ -106,11 +116,13 @@
<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_examples" href="test_examples.html">meshtastic.tests.test_examples</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_remote_hardware" href="test_remote_hardware.html">meshtastic.tests.test_remote_hardware</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>

View File

@@ -4,8 +4,10 @@
<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" />
<title>meshtastic.tests.test_examples API documentation</title>
<meta name="description" content="Meshtastic test that the examples run as expected.
We assume you have a python virtual environment in current directory.
If not, you need to run: …" />
<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>
@@ -19,28 +21,40 @@
<main>
<article id="content">
<header>
<h1 class="title">Module <code>meshtastic.test.test_mesh_interface</code></h1>
<h1 class="title">Module <code>meshtastic.tests.test_examples</code></h1>
</header>
<section id="section-intro">
<p>Meshtastic unit tests for node.py</p>
<p>Meshtastic test that the examples run as expected.
We assume you have a python virtual environment in current directory.
If not, you need to run: "python3 -m venv venv", "source venv/bin/activate", "pip install ."</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;
<pre><code class="python">&#34;&#34;&#34;Meshtastic test that the examples run as expected.
We assume you have a python virtual environment in current directory.
If not, you need to run: &#34;python3 -m venv venv&#34;, &#34;source venv/bin/activate&#34;, &#34;pip install .&#34;
&#34;&#34;&#34;
import subprocess
import pytest
from meshtastic.mesh_interface import MeshInterface
@pytest.mark.examples
def test_examples_hello_world_serial_no_arg():
&#34;&#34;&#34;Test hello_world_serial without any args&#34;&#34;&#34;
return_value, _ = subprocess.getstatusoutput(&#39;source venv/bin/activate; python3 examples/hello_world_serial.py&#39;)
assert return_value == 3
@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>
@pytest.mark.examples
def test_examples_hello_world_serial_with_arg(capsys):
&#34;&#34;&#34;Test hello_world_serial with arg&#34;&#34;&#34;
return_value, _ = subprocess.getstatusoutput(&#39;source venv/bin/activate; python3 examples/hello_world_serial.py hello&#39;)
assert return_value == 1
_, err = capsys.readouterr()
assert err == &#39;&#39;
# TODO: Why does this not work?
# assert out == &#39;Warning: No Meshtastic devices detected.&#39;</code></pre>
</details>
</section>
<section>
@@ -50,22 +64,40 @@ def test_MeshInterface():
<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>
<dt id="meshtastic.tests.test_examples.test_examples_hello_world_serial_no_arg"><code class="name flex">
<span>def <span class="ident">test_examples_hello_world_serial_no_arg</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test that we instantiate a MeshInterface</p></div>
<div class="desc"><p>Test hello_world_serial without any args</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>
<pre><code class="python">@pytest.mark.examples
def test_examples_hello_world_serial_no_arg():
&#34;&#34;&#34;Test hello_world_serial without any args&#34;&#34;&#34;
return_value, _ = subprocess.getstatusoutput(&#39;source venv/bin/activate; python3 examples/hello_world_serial.py&#39;)
assert return_value == 3</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_examples.test_examples_hello_world_serial_with_arg"><code class="name flex">
<span>def <span class="ident">test_examples_hello_world_serial_with_arg</span></span>(<span>capsys)</span>
</code></dt>
<dd>
<div class="desc"><p>Test hello_world_serial with arg</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.examples
def test_examples_hello_world_serial_with_arg(capsys):
&#34;&#34;&#34;Test hello_world_serial with arg&#34;&#34;&#34;
return_value, _ = subprocess.getstatusoutput(&#39;source venv/bin/activate; python3 examples/hello_world_serial.py hello&#39;)
assert return_value == 1
_, err = capsys.readouterr()
assert err == &#39;&#39;
# TODO: Why does this not work?
# assert out == &#39;Warning: No Meshtastic devices detected.&#39;</code></pre>
</details>
</dd>
</dl>
@@ -81,12 +113,13 @@ def test_MeshInterface():
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="meshtastic.test" href="index.html">meshtastic.test</a></code></li>
<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.test.test_mesh_interface.test_MeshInterface" href="#meshtastic.test.test_mesh_interface.test_MeshInterface">test_MeshInterface</a></code></li>
<li><code><a title="meshtastic.tests.test_examples.test_examples_hello_world_serial_no_arg" href="#meshtastic.tests.test_examples.test_examples_hello_world_serial_no_arg">test_examples_hello_world_serial_no_arg</a></code></li>
<li><code><a title="meshtastic.tests.test_examples.test_examples_hello_world_serial_with_arg" href="#meshtastic.tests.test_examples.test_examples_hello_world_serial_with_arg">test_examples_hello_world_serial_with_arg</a></code></li>
</ul>
</li>
</ul>

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 it is too large Load Diff

View File

@@ -0,0 +1,304 @@
<!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_remote_hardware API documentation</title>
<meta name="description" content="Meshtastic unit tests for remote_hardware.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_remote_hardware</code></h1>
</header>
<section id="section-intro">
<p>Meshtastic unit tests for remote_hardware.py</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;Meshtastic unit tests for remote_hardware.py&#34;&#34;&#34;
import logging
import re
from unittest.mock import patch, MagicMock
import pytest
from ..remote_hardware import RemoteHardwareClient, onGPIOreceive
from ..serial_interface import SerialInterface
@pytest.mark.unit
def test_RemoteHardwareClient():
&#34;&#34;&#34;Test that we can instantiate a RemoteHardwareClient instance&#34;&#34;&#34;
iface = MagicMock(autospec=SerialInterface)
rhw = RemoteHardwareClient(iface)
assert rhw.iface == iface
iface.close()
@pytest.mark.unit
def test_onGPIOreceive(capsys):
&#34;&#34;&#34;Test onGPIOreceive&#34;&#34;&#34;
iface = MagicMock(autospec=SerialInterface)
packet = {&#39;decoded&#39;: {&#39;remotehw&#39;: {&#39;typ&#39;: &#39;foo&#39;, &#39;gpioValue&#39;: &#39;4096&#39; }}}
onGPIOreceive(packet, iface)
out, err = capsys.readouterr()
assert re.search(r&#39;Received RemoteHardware&#39;, out)
assert err == &#39;&#39;
@pytest.mark.unit
def test_RemoteHardwareClient_no_gpio_channel(capsys):
&#34;&#34;&#34;Test that we can instantiate a RemoteHardwareClient instance but there is no channel named channel &#39;gpio&#39;&#34;&#34;&#34;
iface = MagicMock(autospec=SerialInterface)
with patch(&#39;meshtastic.serial_interface.SerialInterface&#39;, return_value=iface) as mo:
mo.localNode.getChannelByName.return_value = None
with pytest.raises(SystemExit) as pytest_wrapped_e:
RemoteHardwareClient(mo)
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r&#39;Warning: No channel named&#39;, out)
assert err == &#34;&#34;
@pytest.mark.unit
def test_readGPIOs(caplog):
&#34;&#34;&#34;Test readGPIOs&#34;&#34;&#34;
iface = MagicMock(autospec=SerialInterface)
rhw = RemoteHardwareClient(iface)
with caplog.at_level(logging.DEBUG):
rhw.readGPIOs(&#39;0x10&#39;, 123)
assert re.search(r&#39;readGPIOs&#39;, caplog.text, re.MULTILINE)
iface.close()
@pytest.mark.unit
def test_writeGPIOs(caplog):
&#34;&#34;&#34;Test writeGPIOs&#34;&#34;&#34;
iface = MagicMock(autospec=SerialInterface)
rhw = RemoteHardwareClient(iface)
with caplog.at_level(logging.DEBUG):
rhw.writeGPIOs(&#39;0x10&#39;, 123, 1)
assert re.search(r&#39;writeGPIOs&#39;, caplog.text, re.MULTILINE)
iface.close()
@pytest.mark.unit
def test_watchGPIOs(caplog):
&#34;&#34;&#34;Test watchGPIOs&#34;&#34;&#34;
iface = MagicMock(autospec=SerialInterface)
rhw = RemoteHardwareClient(iface)
with caplog.at_level(logging.DEBUG):
rhw.watchGPIOs(&#39;0x10&#39;, 123)
assert re.search(r&#39;watchGPIOs&#39;, caplog.text, re.MULTILINE)
iface.close()
@pytest.mark.unit
def test_sendHardware_no_nodeid():
&#34;&#34;&#34;Test sending no nodeid to _sendHardware()&#34;&#34;&#34;
iface = MagicMock(autospec=SerialInterface)
with patch(&#39;meshtastic.serial_interface.SerialInterface&#39;, return_value=iface) as mo:
with pytest.raises(SystemExit) as pytest_wrapped_e:
rhw = RemoteHardwareClient(mo)
rhw._sendHardware(None, None)
assert pytest_wrapped_e.type == SystemExit</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.tests.test_remote_hardware.test_RemoteHardwareClient"><code class="name flex">
<span>def <span class="ident">test_RemoteHardwareClient</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test that we can instantiate a RemoteHardwareClient instance</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_RemoteHardwareClient():
&#34;&#34;&#34;Test that we can instantiate a RemoteHardwareClient instance&#34;&#34;&#34;
iface = MagicMock(autospec=SerialInterface)
rhw = RemoteHardwareClient(iface)
assert rhw.iface == iface
iface.close()</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_remote_hardware.test_RemoteHardwareClient_no_gpio_channel"><code class="name flex">
<span>def <span class="ident">test_RemoteHardwareClient_no_gpio_channel</span></span>(<span>capsys)</span>
</code></dt>
<dd>
<div class="desc"><p>Test that we can instantiate a RemoteHardwareClient instance but there is no channel named channel 'gpio'</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_RemoteHardwareClient_no_gpio_channel(capsys):
&#34;&#34;&#34;Test that we can instantiate a RemoteHardwareClient instance but there is no channel named channel &#39;gpio&#39;&#34;&#34;&#34;
iface = MagicMock(autospec=SerialInterface)
with patch(&#39;meshtastic.serial_interface.SerialInterface&#39;, return_value=iface) as mo:
mo.localNode.getChannelByName.return_value = None
with pytest.raises(SystemExit) as pytest_wrapped_e:
RemoteHardwareClient(mo)
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r&#39;Warning: No channel named&#39;, out)
assert err == &#34;&#34;</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_remote_hardware.test_onGPIOreceive"><code class="name flex">
<span>def <span class="ident">test_onGPIOreceive</span></span>(<span>capsys)</span>
</code></dt>
<dd>
<div class="desc"><p>Test onGPIOreceive</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_onGPIOreceive(capsys):
&#34;&#34;&#34;Test onGPIOreceive&#34;&#34;&#34;
iface = MagicMock(autospec=SerialInterface)
packet = {&#39;decoded&#39;: {&#39;remotehw&#39;: {&#39;typ&#39;: &#39;foo&#39;, &#39;gpioValue&#39;: &#39;4096&#39; }}}
onGPIOreceive(packet, iface)
out, err = capsys.readouterr()
assert re.search(r&#39;Received RemoteHardware&#39;, out)
assert err == &#39;&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_remote_hardware.test_readGPIOs"><code class="name flex">
<span>def <span class="ident">test_readGPIOs</span></span>(<span>caplog)</span>
</code></dt>
<dd>
<div class="desc"><p>Test readGPIOs</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_readGPIOs(caplog):
&#34;&#34;&#34;Test readGPIOs&#34;&#34;&#34;
iface = MagicMock(autospec=SerialInterface)
rhw = RemoteHardwareClient(iface)
with caplog.at_level(logging.DEBUG):
rhw.readGPIOs(&#39;0x10&#39;, 123)
assert re.search(r&#39;readGPIOs&#39;, caplog.text, re.MULTILINE)
iface.close()</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_remote_hardware.test_sendHardware_no_nodeid"><code class="name flex">
<span>def <span class="ident">test_sendHardware_no_nodeid</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test sending no nodeid to _sendHardware()</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_sendHardware_no_nodeid():
&#34;&#34;&#34;Test sending no nodeid to _sendHardware()&#34;&#34;&#34;
iface = MagicMock(autospec=SerialInterface)
with patch(&#39;meshtastic.serial_interface.SerialInterface&#39;, return_value=iface) as mo:
with pytest.raises(SystemExit) as pytest_wrapped_e:
rhw = RemoteHardwareClient(mo)
rhw._sendHardware(None, None)
assert pytest_wrapped_e.type == SystemExit</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_remote_hardware.test_watchGPIOs"><code class="name flex">
<span>def <span class="ident">test_watchGPIOs</span></span>(<span>caplog)</span>
</code></dt>
<dd>
<div class="desc"><p>Test watchGPIOs</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_watchGPIOs(caplog):
&#34;&#34;&#34;Test watchGPIOs&#34;&#34;&#34;
iface = MagicMock(autospec=SerialInterface)
rhw = RemoteHardwareClient(iface)
with caplog.at_level(logging.DEBUG):
rhw.watchGPIOs(&#39;0x10&#39;, 123)
assert re.search(r&#39;watchGPIOs&#39;, caplog.text, re.MULTILINE)
iface.close()</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_remote_hardware.test_writeGPIOs"><code class="name flex">
<span>def <span class="ident">test_writeGPIOs</span></span>(<span>caplog)</span>
</code></dt>
<dd>
<div class="desc"><p>Test writeGPIOs</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_writeGPIOs(caplog):
&#34;&#34;&#34;Test writeGPIOs&#34;&#34;&#34;
iface = MagicMock(autospec=SerialInterface)
rhw = RemoteHardwareClient(iface)
with caplog.at_level(logging.DEBUG):
rhw.writeGPIOs(&#39;0x10&#39;, 123, 1)
assert re.search(r&#39;writeGPIOs&#39;, caplog.text, re.MULTILINE)
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_remote_hardware.test_RemoteHardwareClient" href="#meshtastic.tests.test_remote_hardware.test_RemoteHardwareClient">test_RemoteHardwareClient</a></code></li>
<li><code><a title="meshtastic.tests.test_remote_hardware.test_RemoteHardwareClient_no_gpio_channel" href="#meshtastic.tests.test_remote_hardware.test_RemoteHardwareClient_no_gpio_channel">test_RemoteHardwareClient_no_gpio_channel</a></code></li>
<li><code><a title="meshtastic.tests.test_remote_hardware.test_onGPIOreceive" href="#meshtastic.tests.test_remote_hardware.test_onGPIOreceive">test_onGPIOreceive</a></code></li>
<li><code><a title="meshtastic.tests.test_remote_hardware.test_readGPIOs" href="#meshtastic.tests.test_remote_hardware.test_readGPIOs">test_readGPIOs</a></code></li>
<li><code><a title="meshtastic.tests.test_remote_hardware.test_sendHardware_no_nodeid" href="#meshtastic.tests.test_remote_hardware.test_sendHardware_no_nodeid">test_sendHardware_no_nodeid</a></code></li>
<li><code><a title="meshtastic.tests.test_remote_hardware.test_watchGPIOs" href="#meshtastic.tests.test_remote_hardware.test_watchGPIOs">test_watchGPIOs</a></code></li>
<li><code><a title="meshtastic.tests.test_remote_hardware.test_writeGPIOs" href="#meshtastic.tests.test_remote_hardware.test_writeGPIOs">test_writeGPIOs</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

@@ -133,7 +133,7 @@ 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 re.search(r&#39;^DEBUG file&#39;, out, re.MULTILINE)
assert return_value == 0
@@ -296,11 +296,11 @@ def test_smoke1_ch_values():
&#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-longfast&#39;: &#39;Bw31_25Cr48Sf512&#39;,
&#39;--ch-mediumslow&#39;: &#39;Bw250Cr46Sf2048&#39;,
&#39;--ch-mediumfast&#39;: &#39;Bw250Cr47Sf1024&#39;,
# for some reason, this value does not show any modemConfig
&#39;--ch-shortslow&#39;: &#39;{ &#34;psk&#39;,
&#39;--ch-shortfast&#39;: &#39;Bw500Cr45Sf128&#39;
}
@@ -691,7 +691,7 @@ def test_smoke1_set_ham():
Note: Do a factory reset after this setting so it is very short-lived.
&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --set-ham KI1234&#39;)
assert re.search(r&#39;Setting HAM ID&#39;, out, re.MULTILINE)
assert re.search(r&#39;Setting Ham ID&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)
@@ -1106,11 +1106,11 @@ def test_smoke1_ch_values():
&#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-longfast&#39;: &#39;Bw31_25Cr48Sf512&#39;,
&#39;--ch-mediumslow&#39;: &#39;Bw250Cr46Sf2048&#39;,
&#39;--ch-mediumfast&#39;: &#39;Bw250Cr47Sf1024&#39;,
# for some reason, this value does not show any modemConfig
&#39;--ch-shortslow&#39;: &#39;{ &#34;psk&#39;,
&#39;--ch-shortfast&#39;: &#39;Bw500Cr45Sf128&#39;
}
@@ -1172,7 +1172,7 @@ 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 re.search(r&#39;^DEBUG file&#39;, out, re.MULTILINE)
assert return_value == 0</code></pre>
</details>
</dd>
@@ -1510,7 +1510,7 @@ def test_smoke1_set_ham():
Note: Do a factory reset after this setting so it is very short-lived.
&#34;&#34;&#34;
return_value, out = subprocess.getstatusoutput(&#39;meshtastic --set-ham KI1234&#39;)
assert re.search(r&#39;Setting HAM ID&#39;, out, re.MULTILINE)
assert re.search(r&#39;Setting Ham ID&#39;, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)

View File

@@ -29,7 +29,10 @@
</summary>
<pre><code class="python">&#34;&#34;&#34;Meshtastic unit tests for stream_interface.py&#34;&#34;&#34;
import logging
import re
from unittest.mock import MagicMock
import pytest
from ..stream_interface import StreamInterface
@@ -37,10 +40,74 @@ from ..stream_interface import StreamInterface
@pytest.mark.unit
def test_StreamInterface():
&#34;&#34;&#34;Test that we can instantiate a StreamInterface&#34;&#34;&#34;
&#34;&#34;&#34;Test that we cannot instantiate a StreamInterface based on noProto&#34;&#34;&#34;
with pytest.raises(Exception) as pytest_wrapped_e:
StreamInterface(noProto=True)
assert pytest_wrapped_e.type == Exception</code></pre>
StreamInterface()
assert pytest_wrapped_e.type == Exception
# Note: This takes a bit, so moving from unit to slow
@pytest.mark.unitslow
def test_StreamInterface_with_noProto(caplog, reset_globals):
&#34;&#34;&#34;Test that we can instantiate a StreamInterface based on nonProto
and we can read/write bytes from a mocked stream
&#34;&#34;&#34;
stream = MagicMock()
test_data = b&#39;hello&#39;
stream.read.return_value = test_data
with caplog.at_level(logging.DEBUG):
iface = StreamInterface(noProto=True, connectNow=False)
iface.stream = stream
iface._writeBytes(test_data)
data = iface._readBytes(len(test_data))
assert data == test_data
# Note: This takes a bit, so moving from unit to slow
# Tip: If you want to see the print output, run with &#39;-s&#39; flag:
# pytest -s meshtastic/tests/test_stream_interface.py::test_sendToRadioImpl
@pytest.mark.unitslow
def test_sendToRadioImpl(caplog, reset_globals):
&#34;&#34;&#34;Test _sendToRadioImpl()&#34;&#34;&#34;
# def add_header(b):
# &#34;&#34;&#34;Add header stuffs for radio&#34;&#34;&#34;
# bufLen = len(b)
# header = bytes([START1, START2, (bufLen &gt;&gt; 8) &amp; 0xff, bufLen &amp; 0xff])
# return header + b
# captured raw bytes of a Heltec2.1 radio with 2 channels (primary and a secondary channel named &#34;gpio&#34;)
raw_1_my_info = b&#39;\x1a,\x08\xdc\x8c\xd5\xc5\x02\x18\r2\x0e1.2.49.5354c49P\x15]\xe1%\x17Eh\xe0\xa7\x12p\xe8\x9d\x01x\x08\x90\x01\x01&#39;
raw_2_node_info = b&#39;&#34;9\x08\xdc\x8c\xd5\xc5\x02\x12(\n\t!28b5465c\x12\x0cUnknown 465c\x1a\x03?5C&#34;\x06$o(\xb5F\\0\n\x1a\x02 1%M&lt;\xc6a&#39;
# pylint: disable=C0301
raw_3_node_info = b&#39;&#34;C\x08\xa4\x8c\xd5\xc5\x02\x12(\n\t!28b54624\x12\x0cUnknown 4624\x1a\x03?24&#34;\x06$o(\xb5F$0\n\x1a\x07 5MH&lt;\xc6a%G&lt;\xc6a=\x00\x00\xc0@&#39;
raw_4_complete = b&#39;@\xcf\xe5\xd1\x8c\x0e&#39;
# pylint: disable=C0301
raw_5_prefs = b&#39;Z6\r\\F\xb5(\x15\\F\xb5(&#34;\x1c\x08\x06\x12\x13*\x11\n\x0f0\x84\x07P\xac\x02\x88\x01\x01\xb0\t#\xb8\t\x015]$\xddk5\xd5\x7f!b=M&lt;\xc6aP\x03`F&#39;
# pylint: disable=C0301
raw_6_channel0 = b&#39;Z.\r\\F\xb5(\x15\\F\xb5(&#34;\x14\x08\x06\x12\x0b:\t\x12\x05\x18\x01&#34;\x01\x01\x18\x015^$\xddk5\xd6\x7f!b=M&lt;\xc6aP\x03`F&#39;
# pylint: disable=C0301
raw_7_channel1 = b&#39;ZS\r\\F\xb5(\x15\\F\xb5(&#34;9\x08\x06\x120:.\x08\x01\x12(&#34; \xb4&amp;\xb3\xc7\x06\xd8\xe39%\xba\xa5\xee\x8eH\x06\xf6\xf4H\xe8\xd5\xc1[ao\xb5Y\\\xb4&#34;\xafmi*\x04gpio\x18\x025_$\xddk5\xd7\x7f!b=M&lt;\xc6aP\x03`F&#39;
raw_8_channel2 = b&#39;Z)\r\\F\xb5(\x15\\F\xb5(&#34;\x0f\x08\x06\x12\x06:\x04\x08\x02\x12\x005`$\xddk5\xd8\x7f!b=M&lt;\xc6aP\x03`F&#39;
raw_blank = b&#39;&#39;
test_data = b&#39;hello&#39;
stream = MagicMock()
#stream.read.return_value = add_header(test_data)
stream.read.side_effect = [ raw_1_my_info, raw_2_node_info, raw_3_node_info, raw_4_complete,
raw_5_prefs, raw_6_channel0, raw_7_channel1, raw_8_channel2,
raw_blank, raw_blank]
toRadio = MagicMock()
toRadio.SerializeToString.return_value = test_data
with caplog.at_level(logging.DEBUG):
iface = StreamInterface(noProto=True, connectNow=False)
iface.stream = stream
iface.connect()
iface._sendToRadioImpl(toRadio)
assert re.search(r&#39;Sending: &#39;, caplog.text, re.MULTILINE)
assert re.search(r&#39;reading character&#39;, caplog.text, re.MULTILINE)
assert re.search(r&#39;In reader loop&#39;, caplog.text, re.MULTILINE)
print(caplog.text)</code></pre>
</details>
</section>
<section>
@@ -54,19 +121,98 @@ def test_StreamInterface():
<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>
<div class="desc"><p>Test that we cannot instantiate a StreamInterface based on noProto</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;
&#34;&#34;&#34;Test that we cannot instantiate a StreamInterface based on noProto&#34;&#34;&#34;
with pytest.raises(Exception) as pytest_wrapped_e:
StreamInterface(noProto=True)
StreamInterface()
assert pytest_wrapped_e.type == Exception</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_stream_interface.test_StreamInterface_with_noProto"><code class="name flex">
<span>def <span class="ident">test_StreamInterface_with_noProto</span></span>(<span>caplog, reset_globals)</span>
</code></dt>
<dd>
<div class="desc"><p>Test that we can instantiate a StreamInterface based on nonProto
and we can read/write bytes from a mocked stream</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unitslow
def test_StreamInterface_with_noProto(caplog, reset_globals):
&#34;&#34;&#34;Test that we can instantiate a StreamInterface based on nonProto
and we can read/write bytes from a mocked stream
&#34;&#34;&#34;
stream = MagicMock()
test_data = b&#39;hello&#39;
stream.read.return_value = test_data
with caplog.at_level(logging.DEBUG):
iface = StreamInterface(noProto=True, connectNow=False)
iface.stream = stream
iface._writeBytes(test_data)
data = iface._readBytes(len(test_data))
assert data == test_data</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_stream_interface.test_sendToRadioImpl"><code class="name flex">
<span>def <span class="ident">test_sendToRadioImpl</span></span>(<span>caplog, reset_globals)</span>
</code></dt>
<dd>
<div class="desc"><p>Test _sendToRadioImpl()</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unitslow
def test_sendToRadioImpl(caplog, reset_globals):
&#34;&#34;&#34;Test _sendToRadioImpl()&#34;&#34;&#34;
# def add_header(b):
# &#34;&#34;&#34;Add header stuffs for radio&#34;&#34;&#34;
# bufLen = len(b)
# header = bytes([START1, START2, (bufLen &gt;&gt; 8) &amp; 0xff, bufLen &amp; 0xff])
# return header + b
# captured raw bytes of a Heltec2.1 radio with 2 channels (primary and a secondary channel named &#34;gpio&#34;)
raw_1_my_info = b&#39;\x1a,\x08\xdc\x8c\xd5\xc5\x02\x18\r2\x0e1.2.49.5354c49P\x15]\xe1%\x17Eh\xe0\xa7\x12p\xe8\x9d\x01x\x08\x90\x01\x01&#39;
raw_2_node_info = b&#39;&#34;9\x08\xdc\x8c\xd5\xc5\x02\x12(\n\t!28b5465c\x12\x0cUnknown 465c\x1a\x03?5C&#34;\x06$o(\xb5F\\0\n\x1a\x02 1%M&lt;\xc6a&#39;
# pylint: disable=C0301
raw_3_node_info = b&#39;&#34;C\x08\xa4\x8c\xd5\xc5\x02\x12(\n\t!28b54624\x12\x0cUnknown 4624\x1a\x03?24&#34;\x06$o(\xb5F$0\n\x1a\x07 5MH&lt;\xc6a%G&lt;\xc6a=\x00\x00\xc0@&#39;
raw_4_complete = b&#39;@\xcf\xe5\xd1\x8c\x0e&#39;
# pylint: disable=C0301
raw_5_prefs = b&#39;Z6\r\\F\xb5(\x15\\F\xb5(&#34;\x1c\x08\x06\x12\x13*\x11\n\x0f0\x84\x07P\xac\x02\x88\x01\x01\xb0\t#\xb8\t\x015]$\xddk5\xd5\x7f!b=M&lt;\xc6aP\x03`F&#39;
# pylint: disable=C0301
raw_6_channel0 = b&#39;Z.\r\\F\xb5(\x15\\F\xb5(&#34;\x14\x08\x06\x12\x0b:\t\x12\x05\x18\x01&#34;\x01\x01\x18\x015^$\xddk5\xd6\x7f!b=M&lt;\xc6aP\x03`F&#39;
# pylint: disable=C0301
raw_7_channel1 = b&#39;ZS\r\\F\xb5(\x15\\F\xb5(&#34;9\x08\x06\x120:.\x08\x01\x12(&#34; \xb4&amp;\xb3\xc7\x06\xd8\xe39%\xba\xa5\xee\x8eH\x06\xf6\xf4H\xe8\xd5\xc1[ao\xb5Y\\\xb4&#34;\xafmi*\x04gpio\x18\x025_$\xddk5\xd7\x7f!b=M&lt;\xc6aP\x03`F&#39;
raw_8_channel2 = b&#39;Z)\r\\F\xb5(\x15\\F\xb5(&#34;\x0f\x08\x06\x12\x06:\x04\x08\x02\x12\x005`$\xddk5\xd8\x7f!b=M&lt;\xc6aP\x03`F&#39;
raw_blank = b&#39;&#39;
test_data = b&#39;hello&#39;
stream = MagicMock()
#stream.read.return_value = add_header(test_data)
stream.read.side_effect = [ raw_1_my_info, raw_2_node_info, raw_3_node_info, raw_4_complete,
raw_5_prefs, raw_6_channel0, raw_7_channel1, raw_8_channel2,
raw_blank, raw_blank]
toRadio = MagicMock()
toRadio.SerializeToString.return_value = test_data
with caplog.at_level(logging.DEBUG):
iface = StreamInterface(noProto=True, connectNow=False)
iface.stream = stream
iface.connect()
iface._sendToRadioImpl(toRadio)
assert re.search(r&#39;Sending: &#39;, caplog.text, re.MULTILINE)
assert re.search(r&#39;reading character&#39;, caplog.text, re.MULTILINE)
assert re.search(r&#39;In reader loop&#39;, caplog.text, re.MULTILINE)
print(caplog.text)</code></pre>
</details>
</dd>
</dl>
</section>
<section>
@@ -86,6 +232,8 @@ def test_StreamInterface():
<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>
<li><code><a title="meshtastic.tests.test_stream_interface.test_StreamInterface_with_noProto" href="#meshtastic.tests.test_stream_interface.test_StreamInterface_with_noProto">test_StreamInterface_with_noProto</a></code></li>
<li><code><a title="meshtastic.tests.test_stream_interface.test_sendToRadioImpl" href="#meshtastic.tests.test_stream_interface.test_sendToRadioImpl">test_sendToRadioImpl</a></code></li>
</ul>
</li>
</ul>

View File

@@ -30,10 +30,13 @@
<pre><code class="python">&#34;&#34;&#34;Meshtastic unit tests for util.py&#34;&#34;&#34;
import re
import logging
import pytest
from meshtastic.util import fixme, stripnl, pskToString, our_exit, support_info, genPSK256, fromStr, fromPSK
from meshtastic.util import (fixme, stripnl, pskToString, our_exit,
support_info, genPSK256, fromStr, fromPSK,
quoteBooleans, catchAndIgnore)
@pytest.mark.unit
@@ -64,6 +67,16 @@ def test_fromStr():
assert fromStr(&#39;abc&#39;) == &#39;abc&#39;
@pytest.mark.unit
def test_quoteBooleans():
&#34;&#34;&#34;Test quoteBooleans&#34;&#34;&#34;
assert quoteBooleans(&#39;&#39;) == &#39;&#39;
assert quoteBooleans(&#39;foo&#39;) == &#39;foo&#39;
assert quoteBooleans(&#39;true&#39;) == &#39;true&#39;
assert quoteBooleans(&#39;false&#39;) == &#39;false&#39;
assert quoteBooleans(&#39;: true&#39;) == &#34;: &#39;true&#39;&#34;
assert quoteBooleans(&#39;: false&#39;) == &#34;: &#39;false&#39;&#34;
@pytest.mark.unit
def test_fromPSK():
&#34;&#34;&#34;Test fromPSK&#34;&#34;&#34;
@@ -139,7 +152,7 @@ def test_our_exit_non_zero_return_value():
@pytest.mark.unit
def test_fixme():
&#34;&#34;&#34;Test fixme&#34;&#34;&#34;
&#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
@@ -154,7 +167,17 @@ def test_support_info(capsys):
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>
assert err == &#39;&#39;
@pytest.mark.unit
def test_catchAndIgnore(caplog):
&#34;&#34;&#34;Test catchAndIgnore() does not actually throw an exception, but just logs&#34;&#34;&#34;
def some_closure():
raise Exception(&#39;foo&#39;)
with caplog.at_level(logging.DEBUG):
catchAndIgnore(&#34;something&#34;, some_closure)
assert re.search(r&#39;Exception thrown in something&#39;, caplog.text, re.MULTILINE)</code></pre>
</details>
</section>
<section>
@@ -164,18 +187,37 @@ def test_support_info(capsys):
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="meshtastic.tests.test_util.test_catchAndIgnore"><code class="name flex">
<span>def <span class="ident">test_catchAndIgnore</span></span>(<span>caplog)</span>
</code></dt>
<dd>
<div class="desc"><p>Test catchAndIgnore() does not actually throw an exception, but just logs</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_catchAndIgnore(caplog):
&#34;&#34;&#34;Test catchAndIgnore() does not actually throw an exception, but just logs&#34;&#34;&#34;
def some_closure():
raise Exception(&#39;foo&#39;)
with caplog.at_level(logging.DEBUG):
catchAndIgnore(&#34;something&#34;, some_closure)
assert re.search(r&#39;Exception thrown in something&#39;, caplog.text, re.MULTILINE)</code></pre>
</details>
</dd>
<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>
<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;
&#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>
@@ -372,6 +414,26 @@ def test_pskToString_string():
assert pskToString(&#39;hunter123&#39;) == &#39;secret&#39;</code></pre>
</details>
</dd>
<dt id="meshtastic.tests.test_util.test_quoteBooleans"><code class="name flex">
<span>def <span class="ident">test_quoteBooleans</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>Test quoteBooleans</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.mark.unit
def test_quoteBooleans():
&#34;&#34;&#34;Test quoteBooleans&#34;&#34;&#34;
assert quoteBooleans(&#39;&#39;) == &#39;&#39;
assert quoteBooleans(&#39;foo&#39;) == &#39;foo&#39;
assert quoteBooleans(&#39;true&#39;) == &#39;true&#39;
assert quoteBooleans(&#39;false&#39;) == &#39;false&#39;
assert quoteBooleans(&#39;: true&#39;) == &#34;: &#39;true&#39;&#34;
assert quoteBooleans(&#39;: false&#39;) == &#34;: &#39;false&#39;&#34;</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>
@@ -429,6 +491,7 @@ def test_support_info(capsys):
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="meshtastic.tests.test_util.test_catchAndIgnore" href="#meshtastic.tests.test_util.test_catchAndIgnore">test_catchAndIgnore</a></code></li>
<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>
@@ -441,6 +504,7 @@ def test_support_info(capsys):
<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_quoteBooleans" href="#meshtastic.tests.test_util.test_quoteBooleans">test_quoteBooleans</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>

View File

@@ -45,6 +45,14 @@ import pkg_resources
blacklistVids = dict.fromkeys([0x1366])
def quoteBooleans(a_string):
&#34;&#34;&#34;Quote booleans
given a string that contains &#34;: true&#34;, replace with &#34;: &#39;true&#39;&#34; (or false)
&#34;&#34;&#34;
tmp = a_string.replace(&#34;: true&#34;, &#34;: &#39;true&#39;&#34;)
tmp = tmp.replace(&#34;: false&#34;, &#34;: &#39;false&#39;&#34;)
return tmp
def genPSK256():
&#34;&#34;&#34;Generate a random preshared key&#34;&#34;&#34;
return os.urandom(32)
@@ -123,7 +131,7 @@ def fixme(message):
def catchAndIgnore(reason, closure):
&#34;&#34;&#34;Call a closure but if it throws an excpetion print it and continue&#34;&#34;&#34;
&#34;&#34;&#34;Call a closure but if it throws an exception print it and continue&#34;&#34;&#34;
try:
closure()
except BaseException as ex:
@@ -235,13 +243,13 @@ def support_info():
<span>def <span class="ident">catchAndIgnore</span></span>(<span>reason, closure)</span>
</code></dt>
<dd>
<div class="desc"><p>Call a closure but if it throws an excpetion print it and continue</p></div>
<div class="desc"><p>Call a closure but if it throws an exception print it and continue</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def catchAndIgnore(reason, closure):
&#34;&#34;&#34;Call a closure but if it throws an excpetion print it and continue&#34;&#34;&#34;
&#34;&#34;&#34;Call a closure but if it throws an exception print it and continue&#34;&#34;&#34;
try:
closure()
except BaseException as ex:
@@ -413,6 +421,25 @@ return_value defaults to 1 (non-successful)</p></div>
return &#34;secret&#34;</code></pre>
</details>
</dd>
<dt id="meshtastic.util.quoteBooleans"><code class="name flex">
<span>def <span class="ident">quoteBooleans</span></span>(<span>a_string)</span>
</code></dt>
<dd>
<div class="desc"><p>Quote booleans
given a string that contains ": true", replace with ": 'true'" (or false)</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def quoteBooleans(a_string):
&#34;&#34;&#34;Quote booleans
given a string that contains &#34;: true&#34;, replace with &#34;: &#39;true&#39;&#34; (or false)
&#34;&#34;&#34;
tmp = a_string.replace(&#34;: true&#34;, &#34;: &#39;true&#39;&#34;)
tmp = tmp.replace(&#34;: false&#34;, &#34;: &#39;false&#39;&#34;)
return tmp</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>
@@ -626,6 +653,7 @@ return_value defaults to 1 (non-successful)</p></div>
<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.quoteBooleans" href="#meshtastic.util.quoteBooleans">quoteBooleans</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>

View File

@@ -1,389 +0,0 @@
# 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.

View File

@@ -1,17 +0,0 @@
# Stream protocol
Documentation on how out protobufs get encoded when placed onto any stream transport (i.e. TCP or serial, but not UDP or BLE)
## Wire encoding
When sending protobuf packets over serial or TCP each packet is preceded by uint32 sent in network byte order (big endian).
The upper 16 bits must be 0x94C3. The lower 16 bits are packet length (this encoding gives room to eventually allow quite large packets).
Implementations validate length against the maximum possible size of a BLE packet (our lowest common denominator) of 512 bytes. If the
length provided is larger than that we assume the packet is corrupted and begin again looking for 0x4403 framing.
The packets flowing towards the device are ToRadio protobufs, the packets flowing from the device are FromRadio protobufs.
The 0x94C3 marker can be used as framing to (eventually) resync if packets are corrupted over the wire.
Note: the 0x94C3 framing was chosen to prevent confusion with the 7 bit ascii character set. It also doesn't collide with any valid utf8 encoding. This makes it a bit easier to start a device outputting regular debug output on its serial port and then only after it has received a valid packet from the PC, turn off unencoded debug printing and switch to this
packet encoding.

View File

@@ -0,0 +1,18 @@
"""Simple program to demo how to use meshtastic library.
To run: python examples/hello_world_serial.py
"""
import sys
import meshtastic
import meshtastic.serial_interface
# simple arg check
if len(sys.argv) < 2:
print(f"usage: {sys.argv[0]} message")
sys.exit(3)
# By default will try to find a meshtastic device,
# otherwise provide a device path like /dev/ttyUSB0
iface = meshtastic.serial_interface.SerialInterface()
iface.sendText(sys.argv[1])
iface.close()

View File

@@ -0,0 +1,26 @@
"""Simple program to demo how to use meshtastic library.
To run: python examples/pub_sub_example.py
"""
import sys
from pubsub import pub
import meshtastic
import meshtastic.tcp_interface
# simple arg check
if len(sys.argv) < 2:
print(f"usage: {sys.argv[0]} host")
sys.exit(1)
def onConnection(interface, topic=pub.AUTO_TOPIC):
"""This is called when we (re)connect to the radio."""
print(interface.myInfo)
interface.close()
pub.subscribe(onConnection, "meshtastic.connection.established")
try:
iface = meshtastic.tcp_interface.TCPInterface(sys.argv[1])
except:
print(f"Error: Could not connect to {sys.argv[1]}")
sys.exit(1)

View File

@@ -0,0 +1,35 @@
"""Simple program to demo how to use meshtastic library.
To run: python examples/pub_sub_example2.py
"""
import sys
import time
from pubsub import pub
import meshtastic
import meshtastic.tcp_interface
# simple arg check
if len(sys.argv) < 2:
print(f"usage: {sys.argv[0]} host")
sys.exit(1)
def onReceive(packet, interface):
"""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")
try:
iface = meshtastic.tcp_interface.TCPInterface(hostname=sys.argv[1])
while True:
time.sleep(1000)
iface.close()
except(Exception) as ex:
print(f"Error: Could not connect to {sys.argv[1]} {ex}")
sys.exit(1)

View File

@@ -37,6 +37,7 @@ unicode scripts they can be different.
# Example Usage
```
import meshtastic
import meshtastic.serial_interface
from pubsub import pub
def onReceive(packet, interface): # called when a packet arrives
@@ -49,7 +50,7 @@ def onConnection(interface, topic=pub.AUTO_TOPIC): # called when we (re)connect
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()
interface = meshtastic.serial_interface.SerialInterface()
```
@@ -80,23 +81,23 @@ 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
# Note: To follow PEP224, comments should be after the module variable.
"""A special ID that means the local node"""
LOCAL_ADDR = "^local"
"""A special ID that means the local node"""
# if using 8 bit nodenums this will be shortend on the target
BROADCAST_NUM = 0xffffffff
"""if using 8 bit nodenums this will be shortend on the target"""
"""A special ID that means broadcast"""
BROADCAST_ADDR = "^all"
"""A special ID that means broadcast"""
OUR_APP_VERSION = 20200
"""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
publishingThread = DeferredExecution("publishing")
@@ -155,10 +156,11 @@ def _onNodeInfoReceive(iface, asDict):
def _receiveInfoUpdate(iface, asDict):
iface._getOrCreateByNum(asDict["from"])["lastReceived"] = asDict
iface._getOrCreateByNum(asDict["from"])["lastHeard"] = asDict.get("rxTime")
iface._getOrCreateByNum(asDict["from"])["snr"] = asDict.get("rxSnr")
iface._getOrCreateByNum(asDict["from"])["hopLimit"] = asDict.get("hopLimit")
if "from" in asDict:
iface._getOrCreateByNum(asDict["from"])["lastReceived"] = asDict
iface._getOrCreateByNum(asDict["from"])["lastHeard"] = asDict.get("rxTime")
iface._getOrCreateByNum(asDict["from"])["snr"] = asDict.get("rxSnr")
iface._getOrCreateByNum(asDict["from"])["hopLimit"] = asDict.get("hopLimit")
"""Well known message payloads can register decoders for automatic protobuf parsing"""

View File

@@ -18,8 +18,8 @@ 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. """
have_tunnel = platform.system() == 'Linux'
"""We only import the tunnel code if we are on a platform that can run it. """
def onReceive(packet, interface):
"""Callback invoked when a packet arrives"""
@@ -27,21 +27,20 @@ def onReceive(packet, interface):
args = our_globals.get_args()
try:
d = packet.get('decoded')
logging.debug(f'in onReceive() d:{d}')
# Exit once we receive a reply
if args.sendtext and packet["to"] == interface.myInfo.my_node_num and d["portnum"] == portnums_pb2.PortNum.TEXT_MESSAGE_APP:
if args and args.sendtext and packet["to"] == interface.myInfo.my_node_num and d["portnum"] == portnums_pb2.PortNum.TEXT_MESSAGE_APP:
interface.close() # after running command then exit
# Reply to every received message with some stats
if args.reply:
if args and args.reply:
msg = d.get('text')
if msg:
#shortName = packet['decoded']['shortName']
rxSnr = packet['rxSnr']
hopLimit = packet['hopLimit']
print(f"message: {msg}")
reply = "got msg \'{}\' with rxSnr: {} and hopLimit: {}".format(
msg, rxSnr, hopLimit)
reply = "got msg \'{}\' with rxSnr: {} and hopLimit: {}".format(msg, rxSnr, hopLimit)
print("Sending reply: ", reply)
interface.sendText(reply)
@@ -229,7 +228,7 @@ def onConnected(interface):
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 encryption on primary channel
getNode().turnOffEncryptionOnPrimaryChannel()
@@ -240,13 +239,19 @@ def onConnected(interface):
if args.sendtext:
closeNow = True
print(f"Sending text message {args.sendtext} to {args.destOrAll}")
interface.sendText(args.sendtext, args.destOrAll,
wantAck=True)
channelIndex = 0
if args.ch_index is not None:
channelIndex = int(args.ch_index)
ch = getNode().getChannelByChannelIndex(channelIndex)
if ch and ch.role != channel_pb2.Channel.Role.DISABLED:
print(f"Sending text message {args.sendtext} to {args.destOrAll} on channelIndex:{channelIndex}")
interface.sendText(args.sendtext, args.destOrAll, wantAck=True, channelIndex=channelIndex)
else:
meshtastic.util.our_exit(f"Warning: {channelIndex} is not a valid channel. Channel must not be DISABLED.")
if args.sendping:
print(f"Sending ping message {args.sendtext} to {args.destOrAll}")
payload = str.encode("test string")
print(f"Sending ping message to {args.destOrAll}")
interface.sendData(payload, args.destOrAll, portNum=portnums_pb2.PortNum.REPLY_APP,
wantAck=True, wantResponse=True)
@@ -259,27 +264,29 @@ def onConnected(interface):
for wrpair in (args.gpio_wrb or []):
bitmask |= 1 << int(wrpair[0])
bitval |= int(wrpair[1]) << int(wrpair[0])
print(
f"Writing GPIO mask 0x{bitmask:x} with value 0x{bitval:x} to {args.dest}")
print(f"Writing GPIO mask 0x{bitmask:x} with value 0x{bitval:x} to {args.dest}")
rhc.writeGPIOs(args.dest, bitmask, bitval)
closeNow = True
if args.gpio_rd:
bitmask = int(args.gpio_rd, 16)
print(f"Reading GPIO mask 0x{bitmask:x} from {args.dest}")
def onResponse(packet):
"""A closure to handle the response packet"""
hw = packet["decoded"]["remotehw"]
print(f'GPIO read response gpio_value={hw["gpioValue"]}')
sys.exit(0) # Just force an exit (FIXME - ugly)
rhc.readGPIOs(args.dest, bitmask, onResponse)
interface.mask = bitmask
rhc.readGPIOs(args.dest, bitmask, None)
if not interface.noProto:
# wait up to X seconds for a response
for _ in range(10):
time.sleep(1)
if interface.gotResponse:
break
logging.debug(f'end of gpio_rd')
if args.gpio_watch:
bitmask = int(args.gpio_watch, 16)
print(f"Watching GPIO mask 0x{bitmask:x} from {args.dest}")
rhc.watchGPIOs(args.dest, bitmask)
print(f"Watching GPIO mask 0x{bitmask:x} from {args.dest}. Press ctrl-c to exit")
while True:
rhc.watchGPIOs(args.dest, bitmask)
time.sleep(1)
# handle settings
if args.set:
@@ -335,6 +342,11 @@ def onConnected(interface):
print("Writing modified preferences to device")
getNode().writeConfig()
if args.export_config:
# export the configuration (the opposite of '--configure')
closeNow = True
export_config(interface)
if args.seturl:
closeNow = True
getNode().setURL(args.seturl)
@@ -515,12 +527,52 @@ def subscribe():
# pub.subscribe(onNode, "meshtastic.node")
def export_config(interface):
"""used in--export-config"""
owner = interface.getLongName()
channel_url = interface.localNode.getURL()
myinfo = interface.getMyNodeInfo()
pos = myinfo.get('position')
lat = None
lon = None
alt = None
if pos:
lat = pos.get('latitude')
lon = pos.get('longitude')
alt = pos.get('altitude')
config = "# start of Meshtastic configure yaml\n"
if owner:
config += f"owner: {owner}\n\n"
if channel_url:
config += f"channel_url: {channel_url}\n\n"
if lat or lon or alt:
config += "location:\n"
if lat:
config += f" lat: {lat}\n"
if lon:
config += f" lon: {lon}\n"
if alt:
config += f" alt: {alt}\n"
config += "\n"
preferences = f'{interface.localNode.radioConfig.preferences}'
prefs = preferences.splitlines()
if prefs:
config += "user_prefs:\n"
for pref in prefs:
config += f" {meshtastic.util.quoteBooleans(pref)}\n"
print(config)
return config
def common():
"""Shared code for all of our command line wrappers"""
logfile = None
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)
logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO,
format='%(levelname)s file:%(filename)s %(funcName)s line:%(lineno)s %(message)s')
if len(sys.argv) == 1:
parser.print_help(sys.stderr)
@@ -570,8 +622,8 @@ def common():
logging.info(f"Logging serial output to {args.seriallog}")
# Note: using "line buffering"
# pylint: disable=R1732
logfile = open(args.seriallog, 'w+',
buffering=1, encoding='utf8')
logfile = open(args.seriallog, 'w+', buffering=1, encoding='utf8')
our_globals.set_logfile(logfile)
subscribe()
if args.ble:
@@ -585,6 +637,8 @@ def common():
# We assume client is fully connected now
onConnected(client)
#if logfile:
#logfile.close()
if args.noproto or (have_tunnel and args.tunnel): # loop until someone presses ctrlc
while True:
@@ -605,6 +659,11 @@ def initParser():
help="Specify a path to a yaml(.yml) file containing the desired settings for the connected device.",
action='append')
parser.add_argument(
"--export-config",
help="Export the configuration in yaml(.yml) format.",
action='store_true')
parser.add_argument(
"--port",
help="The port the Meshtastic device is connected to, i.e. /dev/ttyUSB0. If unspecified, we'll try to find it.",
@@ -638,7 +697,7 @@ def initParser():
"--seturl", help="Set a channel URL", action="store")
parser.add_argument(
"--ch-index", help="Set the specified channel index", action="store")
"--ch-index", help="Set the specified channel index. Channels start at 0 (0 is the PRIMARY channel).", action="store")
parser.add_argument(
"--ch-add", help="Add a secondary channel, you must specify a channel name", default=None)
@@ -682,13 +741,13 @@ def initParser():
"--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")
"--set-ham", help="Set licensed Ham ID and turn off encryption", action="store")
parser.add_argument(
"--dest", help="The destination node id for any sent commands, if not set '^all' or '^local' is assumed as appropriate", default=None)
parser.add_argument(
"--sendtext", help="Send a text message")
"--sendtext", help="Send a text message. Can specify a destination '--dest' and/or channel index '--ch-index'.")
parser.add_argument(
"--sendping", help="Send a ping message (which requests a reply)", action="store_true")
@@ -696,21 +755,18 @@ def initParser():
parser.add_argument(
"--reboot", help="Tell the destination node to reboot", action="store_true")
# parser.add_argument(
# "--repeat", help="Normally the send commands send only one message, use this option to request repeated sends")
parser.add_argument(
"--reply", help="Reply to received messages",
action="store_true")
parser.add_argument(
"--gpio-wrb", nargs=2, help="Set a particlar GPIO # to 1 or 0", action='append')
"--gpio-wrb", nargs=2, help="Set a particular GPIO # to 1 or 0", action='append')
parser.add_argument(
"--gpio-rd", help="Read from a GPIO mask")
"--gpio-rd", help="Read from a GPIO mask (ex: '0x10')")
parser.add_argument(
"--gpio-watch", help="Start watching a GPIO mask for changes")
"--gpio-watch", help="Start watching a GPIO mask for changes (ex: '0x10')")
parser.add_argument(
"--no-time", help="Suppress sending the current time to the mesh", action="store_true")
@@ -775,6 +831,10 @@ def main():
our_globals.set_parser(parser)
initParser()
common()
logfile = our_globals.get_logfile()
if logfile:
logfile.close()
def tunnelMain():

View File

@@ -12,8 +12,8 @@ _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 mesh_pb2 as mesh__pb2
DESCRIPTOR = _descriptor.FileDescriptor(
@@ -21,9 +21,9 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\013AdminProtosH\003Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\x0b\x61\x64min.proto\x1a\rchannel.proto\x1a\nmesh.proto\x1a\x11radioconfig.proto\"\xfb\x02\n\x0c\x41\x64minMessage\x12!\n\tset_radio\x18\x01 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1a\n\tset_owner\x18\x02 \x01(\x0b\x32\x05.UserH\x00\x12\x1f\n\x0bset_channel\x18\x03 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_radio_request\x18\x04 \x01(\x08H\x00\x12*\n\x12get_radio_response\x18\x05 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1d\n\x13get_channel_request\x18\x06 \x01(\rH\x00\x12(\n\x14get_channel_response\x18\x07 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1d\n\x13\x63onfirm_set_channel\x18 \x01(\x08H\x00\x12\x1b\n\x11\x63onfirm_set_radio\x18! \x01(\x08H\x00\x12\x18\n\x0e\x65xit_simulator\x18\" \x01(\x08H\x00\x12\x18\n\x0ereboot_seconds\x18# \x01(\x05H\x00\x42\t\n\x07variantBG\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
serialized_pb=b'\n\x0b\x61\x64min.proto\x1a\rchannel.proto\x1a\x11radioconfig.proto\x1a\nmesh.proto\"\xbd\x03\n\x0c\x41\x64minMessage\x12!\n\tset_radio\x18\x01 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1a\n\tset_owner\x18\x02 \x01(\x0b\x32\x05.UserH\x00\x12\x1f\n\x0bset_channel\x18\x03 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_radio_request\x18\x04 \x01(\x08H\x00\x12*\n\x12get_radio_response\x18\x05 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1d\n\x13get_channel_request\x18\x06 \x01(\rH\x00\x12(\n\x14get_channel_response\x18\x07 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_owner_request\x18\x08 \x01(\x08H\x00\x12#\n\x12get_owner_response\x18\t \x01(\x0b\x32\x05.UserH\x00\x12\x1d\n\x13\x63onfirm_set_channel\x18 \x01(\x08H\x00\x12\x1b\n\x11\x63onfirm_set_radio\x18! \x01(\x08H\x00\x12\x18\n\x0e\x65xit_simulator\x18\" \x01(\x08H\x00\x12\x18\n\x0ereboot_seconds\x18# \x01(\x05H\x00\x42\t\n\x07variantBG\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
,
dependencies=[channel__pb2.DESCRIPTOR,mesh__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,])
dependencies=[channel__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,mesh__pb2.DESCRIPTOR,])
@@ -85,28 +85,42 @@ _ADMINMESSAGE = _descriptor.Descriptor(
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='confirm_set_channel', full_name='AdminMessage.confirm_set_channel', index=7,
name='get_owner_request', full_name='AdminMessage.get_owner_request', index=7,
number=8, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='get_owner_response', full_name='AdminMessage.get_owner_response', index=8,
number=9, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='confirm_set_channel', full_name='AdminMessage.confirm_set_channel', index=9,
number=32, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='confirm_set_radio', full_name='AdminMessage.confirm_set_radio', index=8,
name='confirm_set_radio', full_name='AdminMessage.confirm_set_radio', index=10,
number=33, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='exit_simulator', full_name='AdminMessage.exit_simulator', index=9,
name='exit_simulator', full_name='AdminMessage.exit_simulator', index=11,
number=34, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='reboot_seconds', full_name='AdminMessage.reboot_seconds', index=10,
name='reboot_seconds', full_name='AdminMessage.reboot_seconds', index=12,
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,
@@ -128,7 +142,7 @@ _ADMINMESSAGE = _descriptor.Descriptor(
index=0, containing_type=None, fields=[]),
],
serialized_start=62,
serialized_end=441,
serialized_end=507,
)
_ADMINMESSAGE.fields_by_name['set_radio'].message_type = radioconfig__pb2._RADIOCONFIG
@@ -136,6 +150,7 @@ _ADMINMESSAGE.fields_by_name['set_owner'].message_type = mesh__pb2._USER
_ADMINMESSAGE.fields_by_name['set_channel'].message_type = channel__pb2._CHANNEL
_ADMINMESSAGE.fields_by_name['get_radio_response'].message_type = radioconfig__pb2._RADIOCONFIG
_ADMINMESSAGE.fields_by_name['get_channel_response'].message_type = channel__pb2._CHANNEL
_ADMINMESSAGE.fields_by_name['get_owner_response'].message_type = mesh__pb2._USER
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['set_radio'])
_ADMINMESSAGE.fields_by_name['set_radio'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
@@ -157,6 +172,12 @@ _ADMINMESSAGE.fields_by_name['get_channel_request'].containing_oneof = _ADMINMES
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_channel_response'])
_ADMINMESSAGE.fields_by_name['get_channel_response'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_owner_request'])
_ADMINMESSAGE.fields_by_name['get_owner_request'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_owner_response'])
_ADMINMESSAGE.fields_by_name['get_owner_response'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['confirm_set_channel'])
_ADMINMESSAGE.fields_by_name['confirm_set_channel'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']

View File

@@ -29,6 +29,7 @@ class Globals:
self.parser = None
self.target_node = None
self.channel_index = None
self.logfile = None
def reset(self):
"""Reset all of our globals. If you add a member, add it to this method, too."""
@@ -37,6 +38,7 @@ class Globals:
self.target_node = None
self.channel_index = None
# setters
def set_args(self, args):
"""Set the args"""
self.args = args
@@ -53,6 +55,11 @@ class Globals:
"""Set the channel_index"""
self.channel_index = channel_index
def set_logfile(self, logfile):
"""Set the logfile"""
self.logfile = logfile
# getters
def get_args(self):
"""Get args"""
return self.args
@@ -68,3 +75,7 @@ class Globals:
def get_channel_index(self):
"""Get channel_index"""
return self.channel_index
def get_logfile(self):
"""Get logfile"""
return self.logfile

View File

@@ -1,4 +1,4 @@
""" Mesh Interface class
"""Mesh Interface class
"""
import sys
import random
@@ -16,15 +16,11 @@ from pubsub import pub
from google.protobuf.json_format import MessageToJson
import meshtastic.node
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
@@ -46,7 +42,7 @@ class MeshInterface:
self.nodes = None # FIXME
self.isConnected = threading.Event()
self.noProto = noProto
self.localNode = Node(self, -1) # We fixup nodenum later
self.localNode = meshtastic.node.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
@@ -56,6 +52,9 @@ class MeshInterface:
self.currentPacketId = random.randint(0, 0xffffffff)
self.nodesByNum = None
self.configId = None
self.defaultHopLimit = 3
self.gotResponse = False # used in gpio read
self.mask = None # used in gpio read and gpio watch
def close(self):
"""Shutdown this interface"""
@@ -106,6 +105,7 @@ class MeshInterface:
rows = []
if self.nodes:
logging.debug(f'self.nodes:{self.nodes}')
for node in self.nodes.values():
if not includeSelf and node['num'] == self.localNode.nodeNum:
continue
@@ -151,7 +151,8 @@ class MeshInterface:
if nodeId == LOCAL_ADDR:
return self.localNode
else:
n = Node(self, nodeId)
n = meshtastic.node.Node(self, nodeId)
logging.debug("About to requestConfig")
n.requestConfig()
if not n.waitForConfig():
our_exit("Error: Timed out waiting for node config")
@@ -161,7 +162,7 @@ class MeshInterface:
destinationId=BROADCAST_ADDR,
wantAck=False,
wantResponse=False,
hopLimit=defaultHopLimit,
hopLimit=None,
onResponse=None,
channelIndex=0):
"""Send a utf8 string to some other node, if the node has a display it
@@ -183,6 +184,9 @@ class MeshInterface:
Returns the sent packet. The id field will be populated in this packet
and can be used to track future message acks/naks.
"""
if hopLimit is None:
hopLimit = self.defaultHopLimit
return self.sendData(text.encode("utf-8"), destinationId,
portNum=portnums_pb2.PortNum.TEXT_MESSAGE_APP,
wantAck=wantAck,
@@ -194,7 +198,7 @@ class MeshInterface:
def sendData(self, data, destinationId=BROADCAST_ADDR,
portNum=portnums_pb2.PortNum.PRIVATE_APP, wantAck=False,
wantResponse=False,
hopLimit=defaultHopLimit,
hopLimit=None,
onResponse=None,
channelIndex=0):
"""Send a data packet to some other node
@@ -214,16 +218,22 @@ class MeshInterface:
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)
channelIndex - channel number to use
Returns the sent packet. The id field will be populated in this packet
and can be used to track future message acks/naks.
"""
if hopLimit is None:
hopLimit = self.defaultHopLimit
if getattr(data, "SerializeToString", None):
logging.debug(f"Serializing protobuf as data: {stripnl(data)}")
data = data.SerializeToString()
logging.debug(f"len(data): {len(data)}")
logging.debug(f"mesh_pb2.Constants.DATA_PAYLOAD_LEN: {mesh_pb2.Constants.DATA_PAYLOAD_LEN}")
if len(data) > mesh_pb2.Constants.DATA_PAYLOAD_LEN:
Exception("Data payload too big")
raise 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")
@@ -256,16 +266,20 @@ class MeshInterface:
p = mesh_pb2.Position()
if latitude != 0.0:
p.latitude_i = int(latitude / 1e-7)
logging.debug(f'p.latitude_i:{p.latitude_i}')
if longitude != 0.0:
p.longitude_i = int(longitude / 1e-7)
logging.debug(f'p.longitude_i:{p.longitude_i}')
if altitude != 0:
p.altitude = int(altitude)
logging.debug(f'p.altitude:{p.altitude}')
if timeSec == 0:
timeSec = time.time() # returns unix timestamp in seconds
p.time = int(timeSec)
logging.debug(f'p.time:{p.time}')
return self.sendData(p, destinationId,
portNum=portnums_pb2.PortNum.POSITION_APP,
@@ -277,13 +291,15 @@ class MeshInterface:
def _sendPacket(self, meshPacket,
destinationId=BROADCAST_ADDR,
wantAck=False, hopLimit=defaultHopLimit):
wantAck=False, hopLimit=None):
"""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.
"""
if hopLimit is None:
hopLimit = self.defaultHopLimit
# 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):
@@ -291,6 +307,7 @@ class MeshInterface:
toRadio = mesh_pb2.ToRadio()
nodeNum = 0
if destinationId is None:
our_exit("Warning: destinationId must not be None")
elif isinstance(destinationId, int):
@@ -298,15 +315,21 @@ class MeshInterface:
elif destinationId == BROADCAST_ADDR:
nodeNum = BROADCAST_NUM
elif destinationId == LOCAL_ADDR:
nodeNum = self.myInfo.my_node_num
if self.myInfo:
nodeNum = self.myInfo.my_node_num
else:
our_exit("Warning: No myInfo found.")
# 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']
if self.nodes:
node = self.nodes.get(destinationId)
if not node:
our_exit(f"Warning: NodeId {destinationId} not found in DB")
nodeNum = node['num']
else:
logging.warning("Warning: There were no self.nodes.")
meshPacket.to = nodeNum
meshPacket.want_ack = wantAck
@@ -318,8 +341,11 @@ class MeshInterface:
meshPacket.id = self._generatePacketId()
toRadio.packet.CopyFrom(meshPacket)
#logging.debug(f"Sending packet: {stripnl(meshPacket)}")
self._sendToRadio(toRadio)
if self.noProto:
logging.warning(f"Not sending packet because protocol use is disabled by noProto")
else:
logging.debug(f"Sending packet: {stripnl(meshPacket)}")
self._sendToRadio(toRadio)
return meshPacket
def waitForConfig(self):
@@ -332,6 +358,7 @@ class MeshInterface:
"""Get info about my node."""
if self.myInfo is None:
return None
logging.debug(f'self.nodesByNum:{self.nodesByNum}')
return self.nodesByNum.get(self.myInfo.my_node_num)
def getMyUser(self):
@@ -358,8 +385,9 @@ class MeshInterface:
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 not self.noProto:
if not self.isConnected.wait(15.0): # timeout after x seconds
raise Exception("Timed out waiting for connection completion")
# If we failed while connecting, raise the connection to the client
if self.failure:
@@ -449,8 +477,9 @@ class MeshInterface:
Called by subclasses."""
fromRadio = mesh_pb2.FromRadio()
fromRadio.ParseFromString(fromRadioBytes)
logging.debug(f"in mesh_interface.py _handleFromRadio() fromRadioBytes: {fromRadioBytes}")
asDict = google.protobuf.json_format.MessageToDict(fromRadio)
#logging.debug(f"Received from radio: {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
@@ -483,7 +512,8 @@ class MeshInterface:
self.nodesByNum[node["num"]] = node
if "user" in node: # Some nodes might not have user/ids assigned yet
self.nodes[node["user"]["id"]] = node
if "id" in node["user"]:
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:
@@ -543,16 +573,22 @@ class MeshInterface:
self.nodesByNum[nodeNum] = n
return n
def _handlePacketFromRadio(self, meshPacket):
def _handlePacketFromRadio(self, meshPacket, hack=False):
"""Handle a MeshPacket that just arrived from the radio
hack - well, since we used 'from', which is a python keyword,
as an attribute to MeshPacket in protobufs,
there really is no way to do something like this:
meshPacket = mesh_pb2.MeshPacket()
meshPacket.from = 123
If hack is True, we can unit test this code.
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
@@ -561,10 +597,10 @@ class MeshInterface:
asDict["raw"] = meshPacket
# from might be missing if the nodenum was zero.
if "from" not in asDict:
if not hack and "from" not in asDict:
asDict["from"] = 0
logging.error(
f"Device returned a packet we sent, ignoring: {stripnl(asDict)}")
logging.error(f"Device returned a packet we sent, ignoring: {stripnl(asDict)}")
print(f"Error: Device returned a packet we sent, ignoring: {stripnl(asDict)}")
return
if "to" not in asDict:
asDict["to"] = 0
@@ -592,9 +628,10 @@ class MeshInterface:
# 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)
if "portnum" not in decoded:
new_portnum = portnums_pb2.PortNum.Name(portnums_pb2.PortNum.UNKNOWN_APP)
decoded["portnum"] = new_portnum
logging.warning(f"portnum was not in decoded. Setting to:{new_portnum}")
portnum = decoded["portnum"]

View File

File diff suppressed because one or more lines are too long

View File

@@ -8,26 +8,31 @@ from . import portnums_pb2, apponly_pb2, admin_pb2, channel_pb2
from .util import pskToString, stripnl, Timeout, our_exit, fromPSK
class Node:
"""A model of a (local or remote) node in the mesh
Includes methods for radioConfig and channels
"""
def __init__(self, iface, nodeNum):
def __init__(self, iface, nodeNum, noProto=False):
"""Constructor"""
self.iface = iface
self.nodeNum = nodeNum
self.radioConfig = None
self.channels = None
self._timeout = Timeout(maxSecs=60)
self._timeout = Timeout(maxSecs=300)
self.partialChannels = None
self.noProto = noProto
def showChannels(self):
"""Show human readable description of our channels."""
print("Channels:")
if self.channels:
logging.debug(f'self.channels:{self.channels}')
for c in self.channels:
#print('c.settings.psk:', c.settings.psk)
cStr = stripnl(MessageToJson(c.settings))
# only show if there is no psk (meaning disabled channel)
if c.settings.psk:
@@ -48,6 +53,7 @@ class Node:
def requestConfig(self):
"""Send regular MeshPackets to ask for settings and channels."""
logging.debug(f"requestConfig for nodeNum:{self.nodeNum}")
self.radioConfig = None
self.channels = None
self.partialChannels = [] # We keep our channels in a temp array until finished
@@ -84,6 +90,16 @@ class Node:
self._sendAdmin(p, adminIndex=adminIndex)
logging.debug(f"Wrote channel {channelIndex}")
def getChannelByChannelIndex(self, channelIndex):
"""Get channel by channelIndex
channelIndex: number, typically 0-7; based on max number channels
returns: None if there is no channel found
"""
ch = None
if self.channels and 0 <= channelIndex < len(self.channels):
ch = self.channels[channelIndex]
return ch
def deleteChannel(self, channelIndex):
"""Delete the specifed channelIndex and shift other channels up"""
ch = self.channels[channelIndex]
@@ -106,7 +122,7 @@ class Node:
# *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
# (and written it), so we can start finding it by name again
adminIndex = 0
def getChannelByName(self, name):
@@ -133,6 +149,7 @@ class Node:
def setOwner(self, long_name=None, short_name=None, is_licensed=False, team=None):
"""Set device owner name"""
logging.debug(f"in setOwner nodeNum:{self.nodeNum}")
nChars = 3
minChars = 2
if long_name is not None:
@@ -162,6 +179,11 @@ class Node:
if team is not None:
p.set_owner.team = team
# Note: These debug lines are used in unit tests
logging.debug(f'p.set_owner.long_name:{p.set_owner.long_name}:')
logging.debug(f'p.set_owner.short_name:{p.set_owner.short_name}:')
logging.debug(f'p.set_owner.is_licensed:{p.set_owner.is_licensed}')
logging.debug(f'p.set_owner.team:{p.set_owner.team}')
return self._sendAdmin(p)
def getURL(self, includeAll: bool = True):
@@ -197,6 +219,7 @@ class Node:
channelSet = apponly_pb2.ChannelSet()
channelSet.ParseFromString(decodedURL)
if len(channelSet.settings) == 0:
our_exit("Warning: There were no settings.")
@@ -207,39 +230,51 @@ class Node:
ch.index = i
ch.settings.CopyFrom(chs)
self.channels[ch.index] = ch
logging.debug(f'Channel i:{i} ch:{ch}')
self.writeChannel(ch.index)
i = i + 1
def onResponseRequestSettings(self, p):
"""Handle the response packet for requesting settings _requestSettings()"""
logging.debug(f'onResponseRequestSetting() p:{p}')
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(f'self.radioConfig:{self.radioConfig}')
logging.debug("Received radio config, now fetching channels...")
self._timeout.reset() # We made foreward progress
self._requestChannel(0) # now start fetching channels
def _requestSettings(self):
"""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"""
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:
print("Requesting preferences from remote node (this could take a while)")
print("Requesting preferences from remote node.")
print("Be sure:")
print(" 1. There is a SECONDARY channel named 'admin'.")
print(" 2. The '--seturl' was used to configure.")
print(" 3. All devices have the same modem config. (i.e., '--ch-longfast')")
print(" 4. All devices have been rebooted after all of the above. (optional, but recommended)")
print("Note: This could take a while (it requests remote channel configs, then writes config)")
return self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
return self._sendAdmin(p, wantResponse=True, onResponse=self.onResponseRequestSettings)
def exitSimulator(self):
"""Tell a simulator node to exit (this message
is ignored for other nodes)"""
p = admin_pb2.AdminMessage()
p.exit_simulator = True
logging.debug('in exitSimulator()')
return self._sendAdmin(p)
@@ -273,6 +308,34 @@ class Node:
self.channels.append(ch)
index += 1
def onResponseRequestChannel(self, p):
"""Handle the response packet for requesting a channel _requestChannel()"""
logging.debug(f'onResponseRequestChannel() p:{p}')
c = p["decoded"]["admin"]["raw"].get_channel_response
self.partialChannels.append(c)
self._timeout.reset() # We made foreward progress
logging.debug(f"Received channel {stripnl(c)}")
index = c.index
# 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
if quitEarly or index >= self.iface.myInfo.max_channels - 1:
logging.debug("Finished downloading channels")
self.channels = self.partialChannels
self._fixupChannels()
# FIXME, the following should only be called after we have settings and channels
self.iface._connected() # Tell everyone else we are ready to go
else:
self._requestChannel(index + 1)
def _requestChannel(self, channelNum: int):
"""Done with initial config messages, now send regular
MeshPackets to ask for settings"""
@@ -281,48 +344,29 @@ class Node:
# 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)")
print(f"Requesting channel {channelNum} info from remote node (this could take a while)")
logging.debug(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 for requesting a channel"""
c = p["decoded"]["admin"]["raw"].get_channel_response
self.partialChannels.append(c)
self._timeout.reset() # We made foreward progress
logging.debug(f"Received channel {stripnl(c)}")
index = c.index
return self._sendAdmin(p, wantResponse=True, onResponse=self.onResponseRequestChannel)
# 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
if quitEarly or index >= self.iface.myInfo.max_channels - 1:
logging.debug("Finished downloading channels")
self.channels = self.partialChannels
self._fixupChannels()
# FIXME, the following should only be called after we have settings and channels
self.iface._connected() # Tell everone else we are ready to go
else:
self._requestChannel(index + 1)
return self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
# pylint: disable=R1710
def _sendAdmin(self, p: admin_pb2.AdminMessage, wantResponse=False,
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
adminIndex = self.iface.localNode._getAdminChannelIndex()
if self.noProto:
logging.warning(f"Not sending packet because protocol use is disabled by noProto")
else:
if adminIndex == 0: # unless a special channel index was used, we want to use the admin index
adminIndex = self.iface.localNode._getAdminChannelIndex()
logging.debug(f'adminIndex:{adminIndex}')
return self.iface.sendData(p, self.nodeNum,
portNum=portnums_pb2.PortNum.ADMIN_APP,
wantAck=True,
wantResponse=wantResponse,
onResponse=onResponse,
channelIndex=adminIndex)
return self.iface.sendData(p, self.nodeNum,
portNum=portnums_pb2.PortNum.ADMIN_APP,
wantAck=True,
wantResponse=wantResponse,
onResponse=onResponse,
channelIndex=adminIndex)

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,15 +1,21 @@
""" Remote hardware
"""Remote hardware
"""
import logging
from pubsub import pub
from . import portnums_pb2, remote_hardware_pb2
from .util import our_exit
def onGPIOreceive(packet, interface):
"""Callback for received GPIO responses
FIXME figure out how to do closures with methods in python"""
"""
logging.debug(f"packet:{packet} interface:{interface}")
hw = packet["decoded"]["remotehw"]
print(f'Received RemoteHardware typ={hw["typ"]}, gpio_value={hw["gpioValue"]}')
gpioValue = hw["gpioValue"]
#print(f'mask:{interface.mask}')
value = int(gpioValue) & int(interface.mask)
print(f'Received RemoteHardware typ={hw["typ"]}, gpio_value={gpioValue} value={value}')
interface.gotResponse = True
class RemoteHardwareClient:
@@ -28,26 +34,28 @@ class RemoteHardwareClient:
self.iface = iface
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)")
our_exit(
"Warning: No channel named 'gpio' was found.\n"\
"On the sending and receive nodes create a channel named 'gpio'.\n"\
"For example, run '--ch-add gpio' on one device, then '--seturl' on\n"\
"the other devices using the url from the device where the channel was added.")
self.channelIndex = ch.index
pub.subscribe(
onGPIOreceive, "meshtastic.receive.remotehw")
pub.subscribe(onGPIOreceive, "meshtastic.receive.remotehw")
def _sendHardware(self, nodeid, r, wantResponse=False, onResponse=None):
if not nodeid:
raise Exception(
r"You must set a destination node ID for this operation (use --dest \!xxxxxxxxx)")
our_exit(r"Warning: Must use 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)
wantAck=True, channelIndex=self.channelIndex,
wantResponse=wantResponse, onResponse=onResponse)
def writeGPIOs(self, nodeid, mask, vals):
"""
Write the specified vals bits to the device GPIOs. Only bits in mask that
are 1 will be changed
"""
logging.debug(f'writeGPIOs nodeid:{nodeid} mask:{mask} vals:{vals}')
r = remote_hardware_pb2.HardwareMessage()
r.typ = remote_hardware_pb2.HardwareMessage.Type.WRITE_GPIOS
r.gpio_mask = mask
@@ -56,6 +64,7 @@ class RemoteHardwareClient:
def readGPIOs(self, nodeid, mask, onResponse = None):
"""Read the specified bits from GPIO inputs on the device"""
logging.debug(f'readGPIOs nodeid:{nodeid} mask:{mask}')
r = remote_hardware_pb2.HardwareMessage()
r.typ = remote_hardware_pb2.HardwareMessage.Type.READ_GPIOS
r.gpio_mask = mask
@@ -63,7 +72,9 @@ class RemoteHardwareClient:
def watchGPIOs(self, nodeid, mask):
"""Watch the specified bits from GPIO inputs on the device for changes"""
logging.debug(f'watchGPIOs nodeid:{nodeid} mask:{mask}')
r = remote_hardware_pb2.HardwareMessage()
r.typ = remote_hardware_pb2.HardwareMessage.Type.WATCH_GPIOS
r.gpio_mask = mask
self.iface.mask = mask
return self._sendHardware(nodeid, r)

View File

@@ -1,14 +1,16 @@
""" Serial interface class
"""
import logging
import time
import platform
import os
import stat
import serial
import meshtastic.util
from .stream_interface import StreamInterface
if platform.system() != 'Windows':
import termios
class SerialInterface(StreamInterface):
"""Interface class for meshtastic devices over a serial link"""
@@ -20,6 +22,7 @@ class SerialInterface(StreamInterface):
devPath {string} -- A filepath to a device, i.e. /dev/ttyUSB0 (default: {None})
debugOut {stream} -- If a stream is provided, any debug serial output from the device will be emitted to that stream. (default: {None})
"""
self.noProto = noProto
if devPath is None:
ports = meshtastic.util.findPorts()
@@ -35,43 +38,30 @@ class SerialInterface(StreamInterface):
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)
# first we need to set the HUPCL so the device will not reboot based on RTS and/or DTR
# see https://github.com/pyserial/pyserial/issues/124
if not self.noProto:
if platform.system() != 'Windows':
with open(devPath, encoding='utf8') as f:
attrs = termios.tcgetattr(f)
attrs[2] = attrs[2] & ~termios.HUPCL
termios.tcsetattr(f, termios.TCSAFLUSH, attrs)
f.close()
time.sleep(0.1)
# rts=False Needed to prevent TBEAMs resetting on OSX, because rts is connected to reset
self.stream.port = devPath
self.stream = serial.Serial(devPath, 921600, exclusive=True, timeout=0.5, write_timeout=0)
if not self.noProto:
self.stream.flush()
time.sleep(0.1)
# HACK: If the platform driving the serial port is unable to leave the RTS pin in high-impedance
# 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)
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()
def close(self):
"""Close a connection to the device"""
if not self.noProto:
self.stream.flush()
time.sleep(0.1)
self.stream.flush()
time.sleep(0.1)
logging.debug("Closing Serial stream")
StreamInterface.close(self)

View File

@@ -18,7 +18,7 @@ DESCRIPTOR = _descriptor.FileDescriptor(
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'
serialized_pb=b'\n\x12storeforward.proto\"\x8a\x06\n\x0fStoreAndForward\x12,\n\x02rr\x18\x01 \x01(\x0e\x32 .StoreAndForward.RequestResponse\x12*\n\x05stats\x18\x02 \x01(\x0b\x32\x1b.StoreAndForward.Statistics\x12)\n\x07history\x18\x03 \x01(\x0b\x32\x18.StoreAndForward.History\x12-\n\theartbeat\x18\x04 \x01(\x0b\x32\x1a.StoreAndForward.Heartbeat\x1a\xcd\x01\n\nStatistics\x12\x16\n\x0emessages_total\x18\x01 \x01(\r\x12\x16\n\x0emessages_saved\x18\x02 \x01(\r\x12\x14\n\x0cmessages_max\x18\x03 \x01(\r\x12\x0f\n\x07up_time\x18\x04 \x01(\r\x12\x10\n\x08requests\x18\x05 \x01(\r\x12\x18\n\x10requests_history\x18\x06 \x01(\r\x12\x11\n\theartbeat\x18\x07 \x01(\x08\x12\x12\n\nreturn_max\x18\x08 \x01(\r\x12\x15\n\rreturn_window\x18\t \x01(\r\x1aI\n\x07History\x12\x18\n\x10history_messages\x18\x01 \x01(\r\x12\x0e\n\x06window\x18\x02 \x01(\r\x12\x14\n\x0clast_request\x18\x03 \x01(\r\x1a.\n\tHeartbeat\x12\x0e\n\x06period\x18\x01 \x01(\r\x12\x11\n\tsecondary\x18\x02 \x01(\r\"\xf7\x01\n\x0fRequestResponse\x12\t\n\x05UNSET\x10\x00\x12\x10\n\x0cROUTER_ERROR\x10\x01\x12\x14\n\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n\x0bROUTER_PING\x10\x03\x12\x0f\n\x0bROUTER_PONG\x10\x04\x12\x0f\n\x0bROUTER_BUSY\x10\x05\x12\x12\n\x0eROUTER_HISTORY\x10\x06\x12\x10\n\x0c\x43LIENT_ERROR\x10\x65\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x66\x12\x10\n\x0c\x43LIENT_STATS\x10g\x12\x0f\n\x0b\x43LIENT_PING\x10h\x12\x0f\n\x0b\x43LIENT_PONG\x10i\x12\x10\n\x0c\x43LIENT_ABORT\x10jBQ\n\x13\x63om.geeksville.meshB\x15StoreAndForwardProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
)
@@ -54,30 +54,38 @@ _STOREANDFORWARD_REQUESTRESPONSE = _descriptor.EnumDescriptor(
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='CLIENT_ERROR', index=6, number=101,
name='ROUTER_HISTORY', index=6, number=6,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='CLIENT_HISTORY', index=7, number=102,
name='CLIENT_ERROR', index=7, number=101,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='CLIENT_STATS', index=8, number=103,
name='CLIENT_HISTORY', index=8, number=102,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='CLIENT_PING', index=9, number=104,
name='CLIENT_STATS', index=9, number=103,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='CLIENT_PONG', index=10, number=105,
name='CLIENT_PING', index=10, number=104,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='CLIENT_PONG', index=11, number=105,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='CLIENT_ABORT', index=12, number=106,
serialized_options=None,
type=None),
],
containing_type=None,
serialized_options=None,
serialized_start=429,
serialized_end=638,
serialized_start=554,
serialized_end=801,
)
_sym_db.RegisterEnumDescriptor(_STOREANDFORWARD_REQUESTRESPONSE)
@@ -90,63 +98,63 @@ _STOREANDFORWARD_STATISTICS = _descriptor.Descriptor(
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='MessagesTotal', full_name='StoreAndForward.Statistics.MessagesTotal', index=0,
name='messages_total', full_name='StoreAndForward.Statistics.messages_total', 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,
name='messages_saved', full_name='StoreAndForward.Statistics.messages_saved', 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,
name='messages_max', full_name='StoreAndForward.Statistics.messages_max', 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,
name='up_time', full_name='StoreAndForward.Statistics.up_time', 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,
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,
name='requests_history', full_name='StoreAndForward.Statistics.requests_history', 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,
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,
name='return_max', full_name='StoreAndForward.Statistics.return_max', 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,
name='return_window', full_name='StoreAndForward.Statistics.return_window', 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,
@@ -164,8 +172,8 @@ _STOREANDFORWARD_STATISTICS = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=176,
serialized_end=374,
serialized_start=223,
serialized_end=428,
)
_STOREANDFORWARD_HISTORY = _descriptor.Descriptor(
@@ -176,14 +184,58 @@ _STOREANDFORWARD_HISTORY = _descriptor.Descriptor(
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='HistoryMessages', full_name='StoreAndForward.History.HistoryMessages', index=0,
name='history_messages', full_name='StoreAndForward.History.history_messages', 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,
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),
_descriptor.FieldDescriptor(
name='last_request', full_name='StoreAndForward.History.last_request', 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),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=430,
serialized_end=503,
)
_STOREANDFORWARD_HEARTBEAT = _descriptor.Descriptor(
name='Heartbeat',
full_name='StoreAndForward.Heartbeat',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='period', full_name='StoreAndForward.Heartbeat.period', 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='secondary', full_name='StoreAndForward.Heartbeat.secondary', 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,
@@ -201,8 +253,8 @@ _STOREANDFORWARD_HISTORY = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=376,
serialized_end=426,
serialized_start=505,
serialized_end=551,
)
_STOREANDFORWARD = _descriptor.Descriptor(
@@ -233,10 +285,17 @@ _STOREANDFORWARD = _descriptor.Descriptor(
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.heartbeat', index=3,
number=4, 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, ],
nested_types=[_STOREANDFORWARD_STATISTICS, _STOREANDFORWARD_HISTORY, _STOREANDFORWARD_HEARTBEAT, ],
enum_types=[
_STOREANDFORWARD_REQUESTRESPONSE,
],
@@ -247,14 +306,16 @@ _STOREANDFORWARD = _descriptor.Descriptor(
oneofs=[
],
serialized_start=23,
serialized_end=638,
serialized_end=801,
)
_STOREANDFORWARD_STATISTICS.containing_type = _STOREANDFORWARD
_STOREANDFORWARD_HISTORY.containing_type = _STOREANDFORWARD
_STOREANDFORWARD_HEARTBEAT.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.fields_by_name['heartbeat'].message_type = _STOREANDFORWARD_HEARTBEAT
_STOREANDFORWARD_REQUESTRESPONSE.containing_type = _STOREANDFORWARD
DESCRIPTOR.message_types_by_name['StoreAndForward'] = _STOREANDFORWARD
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
@@ -274,6 +335,13 @@ StoreAndForward = _reflection.GeneratedProtocolMessageType('StoreAndForward', (_
# @@protoc_insertion_point(class_scope:StoreAndForward.History)
})
,
'Heartbeat' : _reflection.GeneratedProtocolMessageType('Heartbeat', (_message.Message,), {
'DESCRIPTOR' : _STOREANDFORWARD_HEARTBEAT,
'__module__' : 'storeforward_pb2'
# @@protoc_insertion_point(class_scope:StoreAndForward.Heartbeat)
})
,
'DESCRIPTOR' : _STOREANDFORWARD,
'__module__' : 'storeforward_pb2'
# @@protoc_insertion_point(class_scope:StoreAndForward)
@@ -281,6 +349,7 @@ StoreAndForward = _reflection.GeneratedProtocolMessageType('StoreAndForward', (_
_sym_db.RegisterMessage(StoreAndForward)
_sym_db.RegisterMessage(StoreAndForward.Statistics)
_sym_db.RegisterMessage(StoreAndForward.History)
_sym_db.RegisterMessage(StoreAndForward.Heartbeat)
DESCRIPTOR._options = None

View File

@@ -24,7 +24,6 @@ class StreamInterface(MeshInterface):
"""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})
@@ -33,15 +32,14 @@ class StreamInterface(MeshInterface):
Exception: [description]
"""
if not hasattr(self, 'stream'):
if not hasattr(self, 'stream') and not noProto:
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)
self._rxThread = threading.Thread(target=self.__reader, args=(), daemon=True)
MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto)
@@ -64,7 +62,8 @@ class StreamInterface(MeshInterface):
# 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
if not self.noProto:
time.sleep(0.1) # wait 100ms to give device time to start running
self._rxThread.start()
@@ -90,10 +89,16 @@ class StreamInterface(MeshInterface):
if self.stream: # ignore writes when stream is closed
self.stream.write(b)
self.stream.flush()
# we sleep here to give the TBeam a chance to work
if not self.noProto:
time.sleep(0.1)
def _readBytes(self, length):
"""Read an array of bytes from our stream"""
return self.stream.read(length)
if self.stream:
return self.stream.read(length)
else:
return None
def _sendToRadioImpl(self, toRadio):
"""Send a ToRadio protobuf to the device"""
@@ -102,6 +107,7 @@ class StreamInterface(MeshInterface):
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])
logging.debug(f'sending header:{header} b:{b}')
self._writeBytes(header + b)
def close(self):
@@ -116,16 +122,18 @@ class StreamInterface(MeshInterface):
def __reader(self):
"""The reader thread that reads bytes from our stream"""
logging.debug('in __reader()')
empty = bytes()
try:
while not self._wantExit:
# logging.debug("reading character")
logging.debug("reading character")
b = self._readBytes(1)
# logging.debug("In reader loop")
# logging.debug(f"read returned {b}")
logging.debug("In reader loop")
#logging.debug(f"read returned {b}")
if len(b) > 0:
c = b[0]
#logging.debug(f'c:{c}')
ptr = len(self._rxBuf)
# Assume we want to append this byte, fixme use bytearray instead
@@ -144,12 +152,13 @@ class StreamInterface(MeshInterface):
if c != START2:
self._rxBuf = empty # failed to find start2
elif ptr >= HEADER_LEN - 1: # we've at least got a header
# big endian length follos header
#logging.debug('at least we received a header')
# big endian length follows 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
self._rxBuf = empty # length was out out bounds, restart
if len(self._rxBuf) != 0 and ptr + 1 >= packetlen + HEADER_LEN:
try:

View File

@@ -17,18 +17,29 @@ class TCPInterface(StreamInterface):
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)
self.hostname = hostname
self.portNumber = portNumber
if connectNow:
logging.debug(f"Connecting to {hostname}")
server_address = (hostname, portNumber)
sock = socket.create_connection(server_address)
self.socket = sock
else:
self.socket = None
StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto,
connectNow=connectNow)
def myConnect(self):
"""Connect to socket"""
server_address = (self.hostname, self.portNumber)
sock = socket.create_connection(server_address)
self.socket = sock
def close(self):
"""Close a connection to the device"""

View File

@@ -2,9 +2,12 @@
import argparse
from unittest.mock import MagicMock
import pytest
from meshtastic.__main__ import Globals
from ..mesh_interface import MeshInterface
@pytest.fixture
def reset_globals():
@@ -13,3 +16,46 @@ def reset_globals():
parser = argparse.ArgumentParser()
Globals.getInstance().reset()
Globals.getInstance().set_parser(parser)
@pytest.fixture
def iface_with_nodes():
"""Fixture to setup some nodes."""
nodesById = {
'!9388f81c': {
'num': 2475227164,
'user': {
'id': '!9388f81c',
'longName': 'Unknown f81c',
'shortName': '?1C',
'macaddr': 'RBeTiPgc',
'hwModel': 'TBEAM'
},
'position': {},
'lastHeard': 1640204888
}
}
nodesByNum = {
2475227164: {
'num': 2475227164,
'user': {
'id': '!9388f81c',
'longName': 'Unknown f81c',
'shortName': '?1C',
'macaddr': 'RBeTiPgc',
'hwModel': 'TBEAM'
},
'position': {
'time': 1640206266
},
'lastHeard': 1640206266
}
}
iface = MeshInterface(noProto=True)
iface.nodes = nodesById
iface.nodesByNum = nodesByNum
myInfo = MagicMock()
iface.myInfo = myInfo
iface.myInfo.my_node_num = 2475227164
return iface

View File

@@ -0,0 +1,24 @@
"""Meshtastic test that the examples run as expected.
We assume you have a python virtual environment in current directory.
If not, you need to run: "python3 -m venv venv", "source venv/bin/activate", "pip install ."
"""
import subprocess
import pytest
@pytest.mark.examples
def test_examples_hello_world_serial_no_arg():
"""Test hello_world_serial without any args"""
return_value, _ = subprocess.getstatusoutput('source venv/bin/activate; python3 examples/hello_world_serial.py')
assert return_value == 3
@pytest.mark.examples
def test_examples_hello_world_serial_with_arg(capsys):
"""Test hello_world_serial with arg"""
return_value, _ = subprocess.getstatusoutput('source venv/bin/activate; python3 examples/hello_world_serial.py hello')
assert return_value == 1
_, err = capsys.readouterr()
assert err == ''
# TODO: Why does this not work?
# assert out == 'Warning: No Meshtastic devices detected.'

View File

@@ -1,19 +1,23 @@
"""Meshtastic unit tests for __main__.py"""
# pylint: disable=C0302
import sys
import os
import re
import logging
from unittest.mock import patch, MagicMock
import pytest
from meshtastic.__main__ import initParser, main, Globals, onReceive, onConnection
from meshtastic.__main__ import initParser, main, Globals, onReceive, onConnection, export_config, getPref, setPref
#from ..radioconfig_pb2 import UserPreferences
import meshtastic.radioconfig_pb2
from ..serial_interface import SerialInterface
from ..tcp_interface import TCPInterface
from ..ble_interface import BLEInterface
from ..node import Node
from ..channel_pb2 import Channel
from ..remote_hardware import onGPIOreceive
@pytest.mark.unit
@@ -366,7 +370,7 @@ def test_main_set_ham_to_KI123(capsys, reset_globals):
main()
out, err = capsys.readouterr()
assert re.search(r'Connected to radio', out, re.MULTILINE)
assert re.search(r'Setting HAM ID to KI123', out, re.MULTILINE)
assert re.search(r'Setting Ham ID to KI123', out, re.MULTILINE)
assert re.search(r'inside mocked setOwner', out, re.MULTILINE)
assert re.search(r'inside mocked turnOffEncryptionOnPrimaryChannel', out, re.MULTILINE)
assert err == ''
@@ -403,7 +407,7 @@ def test_main_sendtext(capsys, reset_globals):
Globals.getInstance().set_args(sys.argv)
iface = MagicMock(autospec=SerialInterface)
def mock_sendText(text, dest, wantAck):
def mock_sendText(text, dest, wantAck, channelIndex):
print('inside mocked sendText')
iface.sendText.side_effect = mock_sendText
@@ -417,6 +421,67 @@ def test_main_sendtext(capsys, reset_globals):
mo.assert_called()
@pytest.mark.unit
def test_main_sendtext_with_channel(capsys, reset_globals):
"""Test --sendtext"""
sys.argv = ['', '--sendtext', 'hello', '--ch-index', '1']
Globals.getInstance().set_args(sys.argv)
iface = MagicMock(autospec=SerialInterface)
def mock_sendText(text, dest, wantAck, channelIndex):
print('inside mocked sendText')
iface.sendText.side_effect = mock_sendText
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
main()
out, err = capsys.readouterr()
assert re.search(r'Connected to radio', out, re.MULTILINE)
assert re.search(r'Sending text message', out, re.MULTILINE)
assert re.search(r'on channelIndex:1', out, re.MULTILINE)
assert re.search(r'inside mocked sendText', out, re.MULTILINE)
assert err == ''
mo.assert_called()
@pytest.mark.unit
def test_main_sendtext_with_invalid_channel(capsys, reset_globals):
"""Test --sendtext"""
sys.argv = ['', '--sendtext', 'hello', '--ch-index', '-1']
Globals.getInstance().set_args(sys.argv)
iface = MagicMock(autospec=SerialInterface)
iface.getChannelByChannelIndex.return_value = None
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
iface.getNode.return_value.getChannelByChannelIndex.return_value = None
with pytest.raises(SystemExit) as pytest_wrapped_e:
main()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r'is not a valid channel', out, re.MULTILINE)
assert err == ''
mo.assert_called()
@pytest.mark.unit
def test_main_sendtext_with_invalid_channel_nine(capsys, reset_globals):
"""Test --sendtext"""
sys.argv = ['', '--sendtext', 'hello', '--ch-index', '9']
Globals.getInstance().set_args(sys.argv)
iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
iface.getNode.return_value.getChannelByChannelIndex.return_value = None
with pytest.raises(SystemExit) as pytest_wrapped_e:
main()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r'is not a valid channel', out, re.MULTILINE)
assert err == ''
mo.assert_called()
@pytest.mark.unit
def test_main_sendtext_with_dest(capsys, reset_globals):
"""Test --sendtext with --dest"""
@@ -424,7 +489,7 @@ def test_main_sendtext_with_dest(capsys, reset_globals):
Globals.getInstance().set_args(sys.argv)
iface = MagicMock(autospec=SerialInterface)
def mock_sendText(text, dest, wantAck):
def mock_sendText(text, dest, wantAck, channelIndex):
print('inside mocked sendText')
iface.sendText.side_effect = mock_sendText
@@ -1170,14 +1235,113 @@ def test_main_setchan(capsys, reset_globals):
@pytest.mark.unit
def test_main_onReceive_empty(reset_globals):
def test_main_onReceive_empty(caplog, reset_globals):
"""Test onReceive"""
sys.argv = ['']
Globals.getInstance().set_args(sys.argv)
iface = MagicMock(autospec=SerialInterface)
packet = {'decoded': 'foo'}
onReceive(packet, iface)
# TODO: how do we know we actually called it?
with caplog.at_level(logging.DEBUG):
onReceive(packet, iface)
assert re.search(r'in onReceive', caplog.text, re.MULTILINE)
# TODO: use this captured position app message (might want/need in the future)
# packet = {
# 'to': 4294967295,
# 'decoded': {
# 'portnum': 'POSITION_APP',
# 'payload': "M69\306a"
# },
# 'id': 334776976,
# 'hop_limit': 3
# }
@pytest.mark.unit
def test_main_onReceive_with_sendtext(caplog, reset_globals):
"""Test onReceive with sendtext
The entire point of this test is to make sure the interface.close() call
is made in onReceive().
"""
sys.argv = ['', '--sendtext', 'hello']
Globals.getInstance().set_args(sys.argv)
# Note: 'TEXT_MESSAGE_APP' value is 1
packet = {
'to': 4294967295,
'decoded': {
'portnum': 1,
'payload': "hello"
},
'id': 334776977,
'hop_limit': 3,
'want_ack': True
}
iface = MagicMock(autospec=SerialInterface)
iface.myInfo.my_node_num = 4294967295
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
with caplog.at_level(logging.DEBUG):
main()
onReceive(packet, iface)
assert re.search(r'in onReceive', caplog.text, re.MULTILINE)
mo.assert_called()
@pytest.mark.unit
def test_main_onReceive_with_reply(caplog, capsys, reset_globals):
"""Test onReceive with a reply
To capture: on one device run '--sendtext aaa --reply' and on another
device run '--sendtext bbb --reply', then back to the first device and
run '--sendtext aaa2 --reply'. You should now see a "Sending reply" message.
"""
sys.argv = ['', '--sendtext', 'hello', '--reply']
Globals.getInstance().set_args(sys.argv)
# Note: 'TEXT_MESSAGE_APP' value is 1
send_packet = {
'to': 4294967295,
'decoded': {
'portnum': 1,
'payload': "hello"
},
'id': 334776977,
'hop_limit': 3,
'want_ack': True
}
reply_packet = {
'from': 682968668,
'to': 4294967295,
'decoded': {
'portnum': 'TEXT_MESSAGE_APP',
'payload': b'bbb',
'text': 'bbb'
},
'id': 1709936182,
'rxTime': 1640381999,
'rxSnr': 6.0,
'hopLimit': 3,
'raw': 'faked',
'fromId': '!28b5465c',
'toId': '^all'
}
iface = MagicMock(autospec=SerialInterface)
iface.myInfo.my_node_num = 4294967295
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
with caplog.at_level(logging.DEBUG):
main()
onReceive(send_packet, iface)
onReceive(reply_packet, iface)
assert re.search(r'in onReceive', caplog.text, re.MULTILINE)
out, err = capsys.readouterr()
assert re.search(r'got msg ', out, re.MULTILINE)
assert err == ''
mo.assert_called()
@pytest.mark.unit
@@ -1196,3 +1360,247 @@ def test_main_onConnection(reset_globals, capsys):
out, err = capsys.readouterr()
assert re.search(r'Connection changed: foo', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
def test_main_export_config(reset_globals, capsys):
"""Test export_config() function directly"""
iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
mo.getLongName.return_value = 'foo'
mo.localNode.getURL.return_value = 'bar'
mo.getMyNodeInfo().get.return_value = { 'latitudeI': 1100000000, 'longitudeI': 1200000000,
'altitude': 100, 'batteryLevel': 34, 'latitude': 110.0,
'longitude': 120.0}
mo.localNode.radioConfig.preferences = """phone_timeout_secs: 900
ls_secs: 300
position_broadcast_smart: true
fixed_position: true
position_flags: 35"""
export_config(mo)
out, err = capsys.readouterr()
assert re.search(r'owner: foo', out, re.MULTILINE)
assert re.search(r'channel_url: bar', out, re.MULTILINE)
assert re.search(r'location:', out, re.MULTILINE)
assert re.search(r'lat: 110.0', out, re.MULTILINE)
assert re.search(r'lon: 120.0', out, re.MULTILINE)
assert re.search(r'alt: 100', out, re.MULTILINE)
assert re.search(r'user_prefs:', out, re.MULTILINE)
assert re.search(r'phone_timeout_secs: 900', out, re.MULTILINE)
assert re.search(r'ls_secs: 300', out, re.MULTILINE)
assert re.search(r"position_broadcast_smart: 'true'", out, re.MULTILINE)
assert re.search(r"fixed_position: 'true'", out, re.MULTILINE)
assert re.search(r"position_flags: 35", out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
def test_main_export_config_called_from_main(capsys, reset_globals):
"""Test --export-config"""
sys.argv = ['', '--export-config']
Globals.getInstance().set_args(sys.argv)
iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
main()
out, err = capsys.readouterr()
assert re.search(r'Connected to radio', out, re.MULTILINE)
assert re.search(r'# start of Meshtastic configure yaml', out, re.MULTILINE)
assert err == ''
mo.assert_called()
@pytest.mark.unit
def test_main_gpio_rd_no_gpio_channel(capsys, reset_globals):
"""Test --gpio_rd with no named gpio channel"""
sys.argv = ['', '--gpio-rd', '0x10']
Globals.getInstance().set_args(sys.argv)
iface = MagicMock(autospec=SerialInterface)
iface.localNode.getChannelByName.return_value = None
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface):
with pytest.raises(SystemExit) as pytest_wrapped_e:
main()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r'Warning: No channel named', out)
assert err == ''
@pytest.mark.unit
def test_main_gpio_rd_no_dest(capsys, reset_globals):
"""Test --gpio_rd with a named gpio channel but no dest was specified"""
sys.argv = ['', '--gpio-rd', '0x2000']
Globals.getInstance().set_args(sys.argv)
channel = Channel(index=1, role=1)
channel.settings.modem_config = 3
channel.settings.psk = b'\x01'
iface = MagicMock(autospec=SerialInterface)
iface.localNode.getChannelByName.return_value = channel
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface):
with pytest.raises(SystemExit) as pytest_wrapped_e:
main()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r'Warning: Must use a destination node ID', out)
assert err == ''
@pytest.mark.unit
def test_main_gpio_rd(caplog, capsys, reset_globals):
"""Test --gpio_rd with a named gpio channel"""
# Note: On the Heltec v2.1, there is a GPIO pin GPIO 13 that does not have a
# red arrow (meaning ok to use for our purposes)
# See https://resource.heltec.cn/download/WiFi_LoRa_32/WIFI_LoRa_32_V2.pdf
# To find out the mask for GPIO 13, let us assign n as 13.
# 1. Subtract 1 from n (n is now 12)
# 2. Find the 2^n or 2^12 (4096)
# 3. Convert 4096 decimal to hex (0x1000)
# You can use python:
# >>> print(hex(2**12))
# 0x1000
sys.argv = ['', '--gpio-rd', '0x1000', '--dest', '!1234']
Globals.getInstance().set_args(sys.argv)
channel = Channel(index=1, role=1)
channel.settings.modem_config = 3
channel.settings.psk = b'\x01'
packet = {
'from': 682968668,
'to': 682968612,
'channel': 1,
'decoded': {
'portnum': 'REMOTE_HARDWARE_APP',
'payload': b'\x08\x05\x18\x80 ',
'requestId': 1629980484,
'remotehw': {
'typ': 'READ_GPIOS_REPLY',
'gpioValue': '4096',
'raw': 'faked',
'id': 1693085229,
'rxTime': 1640294262,
'rxSnr': 4.75,
'hopLimit': 3,
'wantAck': True,
}
}
}
iface = MagicMock(autospec=SerialInterface)
iface.localNode.getChannelByName.return_value = channel
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
with caplog.at_level(logging.DEBUG):
main()
onGPIOreceive(packet, mo)
assert re.search(r'readGPIOs nodeid:!1234 mask:4096', caplog.text, re.MULTILINE)
out, err = capsys.readouterr()
assert re.search(r'Connected to radio', out, re.MULTILINE)
assert re.search(r'Reading GPIO mask 0x1000 ', out, re.MULTILINE)
assert re.search(r'Received RemoteHardware typ=READ_GPIOS_REPLY, gpio_value=4096', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
def test_main_getPref_valid_field(capsys, reset_globals):
"""Test getPref() with a valid field"""
prefs = MagicMock()
prefs.DESCRIPTOR.fields_by_name.get.return_value = 'ls_secs'
prefs.wifi_ssid = 'foo'
prefs.ls_secs = 300
prefs.fixed_position = False
getPref(prefs, 'ls_secs')
out, err = capsys.readouterr()
assert re.search(r'ls_secs: 300', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
def test_main_getPref_invalid_field(capsys, reset_globals):
"""Test getPref() with an invalid field"""
class Field:
"""Simple class for testing."""
def __init__(self, name):
"""constructor"""
self.name = name
prefs = MagicMock()
prefs.DESCRIPTOR.fields_by_name.get.return_value = None
# Note: This is a subset of the real fields
ls_secs_field = Field('ls_secs')
is_router = Field('is_router')
fixed_position = Field('fixed_position')
fields = [ ls_secs_field, is_router, fixed_position ]
prefs.DESCRIPTOR.fields = fields
getPref(prefs, 'foo')
out, err = capsys.readouterr()
assert re.search(r'does not have an attribute called foo', out, re.MULTILINE)
# ensure they are sorted
assert re.search(r'fixed_position\s+is_router\s+ls_secs', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
def test_main_setPref_valid_field(capsys, reset_globals):
"""Test setPref() with a valid field"""
class Field:
"""Simple class for testing."""
def __init__(self, name, enum_type):
"""constructor"""
self.name = name
self.enum_type = enum_type
ls_secs_field = Field('ls_secs', 'int')
prefs = MagicMock()
prefs.DESCRIPTOR.fields_by_name.get.return_value = ls_secs_field
setPref(prefs, 'ls_secs', '300')
out, err = capsys.readouterr()
assert re.search(r'Set ls_secs to 300', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
def test_main_setPref_invalid_field(capsys, reset_globals):
"""Test setPref() with a invalid field"""
class Field:
"""Simple class for testing."""
def __init__(self, name):
"""constructor"""
self.name = name
prefs = MagicMock()
prefs.DESCRIPTOR.fields_by_name.get.return_value = None
# Note: This is a subset of the real fields
ls_secs_field = Field('ls_secs')
is_router = Field('is_router')
fixed_position = Field('fixed_position')
fields = [ ls_secs_field, is_router, fixed_position ]
prefs.DESCRIPTOR.fields = fields
setPref(prefs, 'foo', '300')
out, err = capsys.readouterr()
assert re.search(r'does not have an attribute called foo', out, re.MULTILINE)
# ensure they are sorted
assert re.search(r'fixed_position\s+is_router\s+ls_secs', out, re.MULTILINE)
assert err == ''

View File

@@ -1,19 +1,48 @@
"""Meshtastic unit tests for mesh_interface.py"""
import re
import logging
from unittest.mock import patch, MagicMock
import pytest
from ..mesh_interface import MeshInterface
from ..node import Node
from .. import mesh_pb2
from ..__init__ import LOCAL_ADDR, BROADCAST_ADDR
@pytest.mark.unit
def test_MeshInterface(capsys):
def test_MeshInterface(capsys, reset_globals):
"""Test that we can instantiate a MeshInterface"""
iface = MeshInterface(noProto=True)
anode = Node('foo', 'bar')
nodes = {
'!9388f81c': {
'num': 2475227164,
'user': {
'id': '!9388f81c',
'longName': 'Unknown f81c',
'shortName': '?1C',
'macaddr': 'RBeTiPgc',
'hwModel': 'TBEAM'
},
'position': {},
'lastHeard': 1640204888
}
}
iface.nodesByNum = {1: anode }
iface.nodes = nodes
myInfo = MagicMock()
iface.myInfo = myInfo
iface.showInfo()
iface.localNode.showInfo()
iface.showNodes()
iface.sendText('hello')
iface.close()
out, err = capsys.readouterr()
assert re.search(r'Owner: None \(None\)', out, re.MULTILINE)
@@ -22,3 +51,412 @@ def test_MeshInterface(capsys):
assert re.search(r'Channels', out, re.MULTILINE)
assert re.search(r'Primary channel URL', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
def test_getMyUser(reset_globals, iface_with_nodes):
"""Test getMyUser()"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
myuser = iface.getMyUser()
print(f'myuser:{myuser}')
assert myuser is not None
assert myuser["id"] == '!9388f81c'
@pytest.mark.unit
def test_getLongName(reset_globals, iface_with_nodes):
"""Test getLongName()"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
mylongname = iface.getLongName()
assert mylongname == 'Unknown f81c'
@pytest.mark.unit
def test_getShortName(reset_globals, iface_with_nodes):
"""Test getShortName()."""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
myshortname = iface.getShortName()
assert myshortname == '?1C'
@pytest.mark.unit
def test_handlePacketFromRadio_no_from(capsys, reset_globals):
"""Test _handlePacketFromRadio with no 'from' in the mesh packet."""
iface = MeshInterface(noProto=True)
meshPacket = mesh_pb2.MeshPacket()
iface._handlePacketFromRadio(meshPacket)
out, err = capsys.readouterr()
assert re.search(r'Device returned a packet we sent, ignoring', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
def test_handlePacketFromRadio_with_a_portnum(caplog, reset_globals):
"""Test _handlePacketFromRadio with a portnum
Since we have an attribute called 'from', we cannot simply 'set' it.
Had to implement a hack just to be able to test some code.
"""
iface = MeshInterface(noProto=True)
meshPacket = mesh_pb2.MeshPacket()
meshPacket.decoded.payload = b''
meshPacket.decoded.portnum = 1
with caplog.at_level(logging.WARNING):
iface._handlePacketFromRadio(meshPacket, hack=True)
assert re.search(r'Not populating fromId', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_handlePacketFromRadio_no_portnum(caplog, reset_globals):
"""Test _handlePacketFromRadio without a portnum"""
iface = MeshInterface(noProto=True)
meshPacket = mesh_pb2.MeshPacket()
meshPacket.decoded.payload = b''
with caplog.at_level(logging.WARNING):
iface._handlePacketFromRadio(meshPacket, hack=True)
assert re.search(r'Not populating fromId', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_getNode_with_local(reset_globals):
"""Test getNode"""
iface = MeshInterface(noProto=True)
anode = iface.getNode(LOCAL_ADDR)
assert anode == iface.localNode
@pytest.mark.unit
def test_getNode_not_local(reset_globals, caplog):
"""Test getNode not local"""
iface = MeshInterface(noProto=True)
anode = MagicMock(autospec=Node)
with caplog.at_level(logging.DEBUG):
with patch('meshtastic.node.Node', return_value=anode):
another_node = iface.getNode('bar2')
assert another_node != iface.localNode
assert re.search(r'About to requestConfig', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_getNode_not_local_timeout(reset_globals, capsys):
"""Test getNode not local, simulate timeout"""
iface = MeshInterface(noProto=True)
anode = MagicMock(autospec=Node)
anode.waitForConfig.return_value = False
with patch('meshtastic.node.Node', return_value=anode):
with pytest.raises(SystemExit) as pytest_wrapped_e:
iface.getNode('bar2')
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.match(r'Error: Timed out waiting for node config', out)
assert err == ''
@pytest.mark.unit
def test_sendPosition(reset_globals, caplog):
"""Test sendPosition"""
iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG):
iface.sendPosition()
iface.close()
assert re.search(r'p.time:', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_handleFromRadio_empty_payload(reset_globals, caplog):
"""Test _handleFromRadio"""
iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG):
iface._handleFromRadio(b'')
iface.close()
assert re.search(r'Unexpected FromRadio payload', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_handleFromRadio_with_my_info(reset_globals, caplog):
"""Test _handleFromRadio with my_info"""
# Note: I captured the '--debug --info' for the bytes below.
# It "translates" to this:
# my_info {
# my_node_num: 682584012
# num_bands: 13
# firmware_version: "1.2.49.5354c49"
# reboot_count: 13
# bitrate: 17.088470458984375
# message_timeout_msec: 300000
# min_app_version: 20200
# max_channels: 8
# }
from_radio_bytes = b'\x1a,\x08\xcc\xcf\xbd\xc5\x02\x18\r2\x0e1.2.49.5354c49P\r]0\xb5\x88Ah\xe0\xa7\x12p\xe8\x9d\x01x\x08\x90\x01\x01'
iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG):
iface._handleFromRadio(from_radio_bytes)
iface.close()
assert re.search(r'Received myinfo', caplog.text, re.MULTILINE)
assert re.search(r'num_bands: 13', caplog.text, re.MULTILINE)
assert re.search(r'max_channels: 8', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_handleFromRadio_with_node_info(reset_globals, caplog, capsys):
"""Test _handleFromRadio with node_info"""
# Note: I captured the '--debug --info' for the bytes below.
# It "translates" to this:
# node_info {
# num: 682584012
# user {
# id: "!28af67cc"
# long_name: "Unknown 67cc"
# short_name: "?CC"
# macaddr: "$o(\257g\314"
# hw_model: HELTEC_V2_1
# }
# position {
# }
# }
from_radio_bytes = b'"2\x08\xcc\xcf\xbd\xc5\x02\x12(\n\t!28af67cc\x12\x0cUnknown 67cc\x1a\x03?CC"\x06$o(\xafg\xcc0\n\x1a\x00'
iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG):
iface._startConfig()
iface._handleFromRadio(from_radio_bytes)
assert re.search(r'Received nodeinfo', caplog.text, re.MULTILINE)
assert re.search(r'682584012', caplog.text, re.MULTILINE)
assert re.search(r'HELTEC_V2_1', caplog.text, re.MULTILINE)
# validate some of showNodes() output
iface.showNodes()
out, err = capsys.readouterr()
assert re.search(r' 1 ', out, re.MULTILINE)
assert re.search(r'│ Unknown 67cc │ ', out, re.MULTILINE)
assert re.search(r'│ !28af67cc │ N/A │ N/A │ N/A', out, re.MULTILINE)
assert err == ''
iface.close()
@pytest.mark.unit
def test_handleFromRadio_with_node_info_tbeam1(reset_globals, caplog, capsys):
"""Test _handleFromRadio with node_info"""
# Note: Captured the '--debug --info' for the bytes below.
# pylint: disable=C0301
from_radio_bytes = b'"=\x08\x80\xf8\xc8\xf6\x07\x12"\n\t!7ed23c00\x12\x07TBeam 1\x1a\x02T1"\x06\x94\xb9~\xd2<\x000\x04\x1a\x07 ]MN\x01\xbea%\xad\x01\xbea=\x00\x00,A'
iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG):
iface._startConfig()
iface._handleFromRadio(from_radio_bytes)
assert re.search(r'Received nodeinfo', caplog.text, re.MULTILINE)
assert re.search(r'TBeam 1', caplog.text, re.MULTILINE)
assert re.search(r'2127707136', caplog.text, re.MULTILINE)
# validate some of showNodes() output
iface.showNodes()
out, err = capsys.readouterr()
assert re.search(r' 1 ', out, re.MULTILINE)
assert re.search(r'│ TBeam 1 │ ', out, re.MULTILINE)
assert re.search(r'│ !7ed23c00 │', out, re.MULTILINE)
assert err == ''
iface.close()
@pytest.mark.unit
def test_handleFromRadio_with_node_info_tbeam_with_bad_data(reset_globals, caplog, capsys):
"""Test _handleFromRadio with node_info with some bad data (issue#172) - ensure we do not throw exception"""
# Note: Captured the '--debug --info' for the bytes below.
from_radio_bytes = b'"\x17\x08\xdc\x8a\x8a\xae\x02\x12\x08"\x06\x00\x00\x00\x00\x00\x00\x1a\x00=\x00\x00\xb8@'
iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG):
iface._startConfig()
iface._handleFromRadio(from_radio_bytes)
@pytest.mark.unit
def test_MeshInterface_sendToRadioImpl(caplog, reset_globals):
"""Test _sendToRadioImp()"""
iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG):
iface._sendToRadioImpl('foo')
assert re.search(r'Subclass must provide toradio', caplog.text, re.MULTILINE)
iface.close()
@pytest.mark.unit
def test_MeshInterface_sendToRadio_no_proto(caplog, reset_globals):
"""Test sendToRadio()"""
iface = MeshInterface()
with caplog.at_level(logging.DEBUG):
iface._sendToRadioImpl('foo')
assert re.search(r'Subclass must provide toradio', caplog.text, re.MULTILINE)
iface.close()
@pytest.mark.unit
def test_sendData_too_long(caplog, reset_globals):
"""Test when data payload is too big"""
iface = MeshInterface(noProto=True)
some_large_text = b'This is a long text that will be too long for send text.'
some_large_text += b'This is a long text that will be too long for send text.'
some_large_text += b'This is a long text that will be too long for send text.'
some_large_text += b'This is a long text that will be too long for send text.'
some_large_text += b'This is a long text that will be too long for send text.'
some_large_text += b'This is a long text that will be too long for send text.'
some_large_text += b'This is a long text that will be too long for send text.'
some_large_text += b'This is a long text that will be too long for send text.'
some_large_text += b'This is a long text that will be too long for send text.'
some_large_text += b'This is a long text that will be too long for send text.'
some_large_text += b'This is a long text that will be too long for send text.'
some_large_text += b'This is a long text that will be too long for send text.'
with caplog.at_level(logging.DEBUG):
with pytest.raises(Exception) as pytest_wrapped_e:
iface.sendData(some_large_text)
assert re.search('Data payload too big', caplog.text, re.MULTILINE)
assert pytest_wrapped_e.type == Exception
iface.close()
@pytest.mark.unit
def test_sendData_unknown_app(capsys, reset_globals):
"""Test sendData when unknown app"""
iface = MeshInterface(noProto=True)
with pytest.raises(SystemExit) as pytest_wrapped_e:
iface.sendData(b'hello', portNum=0)
out, err = capsys.readouterr()
assert re.search(r'Warning: A non-zero port number', out, re.MULTILINE)
assert err == ''
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
@pytest.mark.unit
def test_sendPosition_with_a_position(caplog, reset_globals):
"""Test sendPosition when lat/long/alt"""
iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG):
iface.sendPosition(latitude=40.8, longitude=-111.86, altitude=201)
assert re.search(r'p.latitude_i:408', caplog.text, re.MULTILINE)
assert re.search(r'p.longitude_i:-11186', caplog.text, re.MULTILINE)
assert re.search(r'p.altitude:201', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_sendPacket_with_no_destination(capsys, reset_globals):
"""Test _sendPacket()"""
iface = MeshInterface(noProto=True)
with pytest.raises(SystemExit) as pytest_wrapped_e:
iface._sendPacket(b'', destinationId=None)
out, err = capsys.readouterr()
assert re.search(r'Warning: destinationId must not be None', out, re.MULTILINE)
assert err == ''
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
@pytest.mark.unit
def test_sendPacket_with_destination_as_int(caplog, reset_globals):
"""Test _sendPacket() with int as a destination"""
iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG):
meshPacket = mesh_pb2.MeshPacket()
iface._sendPacket(meshPacket, destinationId=123)
assert re.search(r'Not sending packet', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_sendPacket_with_destination_starting_with_a_bang(caplog, reset_globals):
"""Test _sendPacket() with int as a destination"""
iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG):
meshPacket = mesh_pb2.MeshPacket()
iface._sendPacket(meshPacket, destinationId='!1234')
assert re.search(r'Not sending packet', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_sendPacket_with_destination_as_BROADCAST_ADDR(caplog, reset_globals):
"""Test _sendPacket() with BROADCAST_ADDR as a destination"""
iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG):
meshPacket = mesh_pb2.MeshPacket()
iface._sendPacket(meshPacket, destinationId=BROADCAST_ADDR)
assert re.search(r'Not sending packet', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_sendPacket_with_destination_as_LOCAL_ADDR_no_myInfo(capsys, reset_globals):
"""Test _sendPacket() with LOCAL_ADDR as a destination with no myInfo"""
iface = MeshInterface(noProto=True)
with pytest.raises(SystemExit) as pytest_wrapped_e:
meshPacket = mesh_pb2.MeshPacket()
iface._sendPacket(meshPacket, destinationId=LOCAL_ADDR)
out, err = capsys.readouterr()
assert re.search(r'Warning: No myInfo', out, re.MULTILINE)
assert err == ''
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
@pytest.mark.unit
def test_sendPacket_with_destination_as_LOCAL_ADDR_with_myInfo(caplog, reset_globals):
"""Test _sendPacket() with LOCAL_ADDR as a destination with myInfo"""
iface = MeshInterface(noProto=True)
myInfo = MagicMock()
iface.myInfo = myInfo
with caplog.at_level(logging.DEBUG):
meshPacket = mesh_pb2.MeshPacket()
iface._sendPacket(meshPacket, destinationId=LOCAL_ADDR)
assert re.search(r'Not sending packet', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_sendPacket_with_destination_is_blank_with_nodes(capsys, reset_globals, iface_with_nodes):
"""Test _sendPacket() with '' as a destination with myInfo"""
iface = iface_with_nodes
meshPacket = mesh_pb2.MeshPacket()
with pytest.raises(SystemExit) as pytest_wrapped_e:
iface._sendPacket(meshPacket, destinationId='')
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.match(r'Warning: NodeId not found in DB', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
def test_sendPacket_with_destination_is_blank_without_nodes(caplog, reset_globals, iface_with_nodes):
"""Test _sendPacket() with '' as a destination with myInfo"""
iface = iface_with_nodes
iface.nodes = None
meshPacket = mesh_pb2.MeshPacket()
with caplog.at_level(logging.WARNING):
iface._sendPacket(meshPacket, destinationId='')
assert re.search(r'Warning: There were no self.nodes.', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_getMyNodeInfo(reset_globals):
"""Test getMyNodeInfo()"""
iface = MeshInterface(noProto=True)
anode = iface.getNode(LOCAL_ADDR)
iface.nodesByNum = {1: anode }
assert iface.nodesByNum.get(1) == anode
myInfo = MagicMock()
iface.myInfo = myInfo
iface.myInfo.my_node_num = 1
myinfo = iface.getMyNodeInfo()
assert myinfo == anode
@pytest.mark.unit
def test_generatePacketId(capsys, reset_globals):
"""Test _generatePacketId() when no currentPacketId (not connected)"""
iface = MeshInterface(noProto=True)
# not sure when this condition would ever happen... but we can simulate it
iface.currentPacketId = None
assert iface.currentPacketId is None
with pytest.raises(Exception) as pytest_wrapped_e:
iface._generatePacketId()
out, err = capsys.readouterr()
assert re.search(r'Not connected yet, can not generate packet', out, re.MULTILINE)
assert err == ''
assert pytest_wrapped_e.type == Exception

View File

@@ -1,6 +1,7 @@
"""Meshtastic unit tests for node.py"""
import re
import logging
from unittest.mock import patch, MagicMock
import pytest
@@ -8,12 +9,16 @@ import pytest
from ..node import Node
from ..serial_interface import SerialInterface
from ..admin_pb2 import AdminMessage
from ..channel_pb2 import Channel
from ..radioconfig_pb2 import RadioConfig
@pytest.mark.unit
def test_node(capsys):
"""Test that we can instantiate a Node"""
anode = Node('foo', 'bar')
radioConfig = RadioConfig()
anode.radioConfig = radioConfig
anode.showChannels()
anode.showInfo()
out, err = capsys.readouterr()
@@ -32,3 +37,823 @@ def test_node_reqquestConfig():
with patch('meshtastic.admin_pb2.AdminMessage', return_value=amesg):
anode = Node(mo, 'bar')
anode.requestConfig()
@pytest.mark.unit
def test_setOwner_and_team(caplog):
"""Test setOwner"""
anode = Node('foo', 'bar', noProto=True)
with caplog.at_level(logging.DEBUG):
anode.setOwner(long_name ='Test123', short_name='123', team=1)
assert re.search(r'p.set_owner.long_name:Test123:', caplog.text, re.MULTILINE)
assert re.search(r'p.set_owner.short_name:123:', caplog.text, re.MULTILINE)
assert re.search(r'p.set_owner.is_licensed:False', caplog.text, re.MULTILINE)
assert re.search(r'p.set_owner.team:1', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_setOwner_no_short_name(caplog):
"""Test setOwner"""
anode = Node('foo', 'bar', noProto=True)
with caplog.at_level(logging.DEBUG):
anode.setOwner(long_name ='Test123')
assert re.search(r'p.set_owner.long_name:Test123:', caplog.text, re.MULTILINE)
assert re.search(r'p.set_owner.short_name:Tst:', caplog.text, re.MULTILINE)
assert re.search(r'p.set_owner.is_licensed:False', caplog.text, re.MULTILINE)
assert re.search(r'p.set_owner.team:0', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_setOwner_no_short_name_and_long_name_is_short(caplog):
"""Test setOwner"""
anode = Node('foo', 'bar', noProto=True)
with caplog.at_level(logging.DEBUG):
anode.setOwner(long_name ='Tnt')
assert re.search(r'p.set_owner.long_name:Tnt:', caplog.text, re.MULTILINE)
assert re.search(r'p.set_owner.short_name:Tnt:', caplog.text, re.MULTILINE)
assert re.search(r'p.set_owner.is_licensed:False', caplog.text, re.MULTILINE)
assert re.search(r'p.set_owner.team:0', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_setOwner_no_short_name_and_long_name_has_words(caplog):
"""Test setOwner"""
anode = Node('foo', 'bar', noProto=True)
with caplog.at_level(logging.DEBUG):
anode.setOwner(long_name ='A B C', is_licensed=True)
assert re.search(r'p.set_owner.long_name:A B C:', caplog.text, re.MULTILINE)
assert re.search(r'p.set_owner.short_name:ABC:', caplog.text, re.MULTILINE)
assert re.search(r'p.set_owner.is_licensed:True', caplog.text, re.MULTILINE)
assert re.search(r'p.set_owner.team:0', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_exitSimulator(caplog):
"""Test exitSimulator"""
anode = Node('foo', 'bar', noProto=True)
with caplog.at_level(logging.DEBUG):
anode.exitSimulator()
assert re.search(r'in exitSimulator', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_reboot(caplog):
"""Test reboot"""
anode = Node('foo', 'bar', noProto=True)
with caplog.at_level(logging.DEBUG):
anode.reboot()
assert re.search(r'Telling node to reboot', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_setURL_empty_url():
"""Test reboot"""
anode = Node('foo', 'bar', noProto=True)
with pytest.raises(SystemExit) as pytest_wrapped_e:
anode.setURL('')
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
@pytest.mark.unit
def test_setURL_valid_URL(caplog):
"""Test setURL"""
iface = MagicMock(autospec=SerialInterface)
url = "https://www.meshtastic.org/d/#CgUYAyIBAQ"
with caplog.at_level(logging.DEBUG):
anode = Node(iface, 'bar', noProto=True)
anode.radioConfig = 'baz'
channels = ['zoo']
anode.channels = channels
anode.setURL(url)
assert re.search(r'Channel i:0', caplog.text, re.MULTILINE)
assert re.search(r'modem_config: Bw125Cr48Sf4096', caplog.text, re.MULTILINE)
assert re.search(r'psk: "\\001"', caplog.text, re.MULTILINE)
assert re.search(r'role: PRIMARY', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_setURL_valid_URL_but_no_settings(caplog):
"""Test setURL"""
iface = MagicMock(autospec=SerialInterface)
url = "https://www.meshtastic.org/d/#"
with pytest.raises(SystemExit) as pytest_wrapped_e:
anode = Node(iface, 'bar', noProto=True)
anode.radioConfig = 'baz'
anode.setURL(url)
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
@pytest.mark.unit
def test_showChannels(capsys):
"""Test showChannels"""
anode = Node('foo', 'bar')
# primary channel
# role: 0=Disabled, 1=Primary, 2=Secondary
# modem_config: 0-5
# role: 0=Disabled, 1=Primary, 2=Secondary
channel1 = Channel(index=1, role=1)
channel1.settings.modem_config = 3
channel1.settings.psk = b'\x01'
channel2 = Channel(index=2, role=2)
channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
channel2.settings.name = 'testing'
channel3 = Channel(index=3, role=0)
channel4 = Channel(index=4, role=0)
channel5 = Channel(index=5, role=0)
channel6 = Channel(index=6, role=0)
channel7 = Channel(index=7, role=0)
channel8 = Channel(index=8, role=0)
channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
anode.channels = channels
anode.showChannels()
out, err = capsys.readouterr()
assert re.search(r'Channels:', out, re.MULTILINE)
# primary channel
assert re.search(r'Primary channel URL', out, re.MULTILINE)
assert re.search(r'PRIMARY psk=default ', out, re.MULTILINE)
assert re.search(r'"modemConfig": "Bw125Cr48Sf4096"', out, re.MULTILINE)
assert re.search(r'"psk": "AQ=="', out, re.MULTILINE)
# secondary channel
assert re.search(r'SECONDARY psk=secret ', out, re.MULTILINE)
assert re.search(r'"psk": "ipR5DsbJHjWREkCmMKi0M4cA8ksO539Bes31sJAwqDQ="', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
def test_getChannelByChannelIndex():
"""Test getChannelByChannelIndex()"""
anode = Node('foo', 'bar')
channel1 = Channel(index=1, role=1) # primary channel
channel2 = Channel(index=2, role=2) # secondary channel
channel3 = Channel(index=3, role=0)
channel4 = Channel(index=4, role=0)
channel5 = Channel(index=5, role=0)
channel6 = Channel(index=6, role=0)
channel7 = Channel(index=7, role=0)
channel8 = Channel(index=8, role=0)
channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
anode.channels = channels
# test primary
assert anode.getChannelByChannelIndex(0) is not None
# test secondary
assert anode.getChannelByChannelIndex(1) is not None
# test disabled
assert anode.getChannelByChannelIndex(2) is not None
# test invalid values
assert anode.getChannelByChannelIndex(-1) is None
assert anode.getChannelByChannelIndex(9) is None
@pytest.mark.unit
def test_deleteChannel_try_to_delete_primary_channel(capsys):
"""Try to delete primary channel."""
anode = Node('foo', 'bar')
channel1 = Channel(index=1, role=1)
channel1.settings.modem_config = 3
channel1.settings.psk = b'\x01'
# no secondary channels
channel2 = Channel(index=2, role=0)
channel3 = Channel(index=3, role=0)
channel4 = Channel(index=4, role=0)
channel5 = Channel(index=5, role=0)
channel6 = Channel(index=6, role=0)
channel7 = Channel(index=7, role=0)
channel8 = Channel(index=8, role=0)
channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
anode.channels = channels
with pytest.raises(SystemExit) as pytest_wrapped_e:
anode.deleteChannel(0)
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r'Warning: Only SECONDARY channels can be deleted', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
def test_deleteChannel_secondary():
"""Try to delete a secondary channel."""
channel1 = Channel(index=1, role=1)
channel1.settings.modem_config = 3
channel1.settings.psk = b'\x01'
channel2 = Channel(index=2, role=2)
channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
channel2.settings.name = 'testing'
channel3 = Channel(index=3, role=0)
channel4 = Channel(index=4, role=0)
channel5 = Channel(index=5, role=0)
channel6 = Channel(index=6, role=0)
channel7 = Channel(index=7, role=0)
channel8 = Channel(index=8, role=0)
channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
mo.localNode.getChannelByName.return_value = None
mo.myInfo.max_channels = 8
anode = Node(mo, 'bar', noProto=True)
anode.channels = channels
assert len(anode.channels) == 8
assert channels[0].settings.modem_config == 3
assert channels[1].settings.name == 'testing'
assert channels[2].settings.name == ''
assert channels[3].settings.name == ''
assert channels[4].settings.name == ''
assert channels[5].settings.name == ''
assert channels[6].settings.name == ''
assert channels[7].settings.name == ''
anode.deleteChannel(1)
assert len(anode.channels) == 8
assert channels[0].settings.modem_config == 3
assert channels[1].settings.name == ''
assert channels[2].settings.name == ''
assert channels[3].settings.name == ''
assert channels[4].settings.name == ''
assert channels[5].settings.name == ''
assert channels[6].settings.name == ''
assert channels[7].settings.name == ''
@pytest.mark.unit
def test_deleteChannel_secondary_with_admin_channel_after_testing():
"""Try to delete a secondary channel where there is an admin channel."""
channel1 = Channel(index=1, role=1)
channel1.settings.modem_config = 3
channel1.settings.psk = b'\x01'
channel2 = Channel(index=2, role=2)
channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
channel2.settings.name = 'testing'
channel3 = Channel(index=3, role=2)
channel3.settings.name = 'admin'
channel4 = Channel(index=4, role=0)
channel5 = Channel(index=5, role=0)
channel6 = Channel(index=6, role=0)
channel7 = Channel(index=7, role=0)
channel8 = Channel(index=8, role=0)
channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
mo.localNode.getChannelByName.return_value = None
mo.myInfo.max_channels = 8
anode = Node(mo, 'bar', noProto=True)
# Note: Have to do this next line because every call to MagicMock object/method returns a new magic mock
mo.localNode = anode
assert mo.localNode == anode
anode.channels = channels
assert len(anode.channels) == 8
assert channels[0].settings.modem_config == 3
assert channels[1].settings.name == 'testing'
assert channels[2].settings.name == 'admin'
assert channels[3].settings.name == ''
assert channels[4].settings.name == ''
assert channels[5].settings.name == ''
assert channels[6].settings.name == ''
assert channels[7].settings.name == ''
anode.deleteChannel(1)
assert len(anode.channels) == 8
assert channels[0].settings.modem_config == 3
assert channels[1].settings.name == 'admin'
assert channels[2].settings.name == ''
assert channels[3].settings.name == ''
assert channels[4].settings.name == ''
assert channels[5].settings.name == ''
assert channels[6].settings.name == ''
assert channels[7].settings.name == ''
@pytest.mark.unit
def test_deleteChannel_secondary_with_admin_channel_before_testing():
"""Try to delete a secondary channel where there is an admin channel."""
channel1 = Channel(index=1, role=1)
channel1.settings.modem_config = 3
channel1.settings.psk = b'\x01'
channel2 = Channel(index=2, role=2)
channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
channel2.settings.name = 'admin'
channel3 = Channel(index=3, role=2)
channel3.settings.name = 'testing'
channel4 = Channel(index=4, role=0)
channel5 = Channel(index=5, role=0)
channel6 = Channel(index=6, role=0)
channel7 = Channel(index=7, role=0)
channel8 = Channel(index=8, role=0)
channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
mo.localNode.getChannelByName.return_value = None
mo.myInfo.max_channels = 8
anode = Node(mo, 'bar', noProto=True)
anode.channels = channels
assert len(anode.channels) == 8
assert channels[0].settings.modem_config == 3
assert channels[1].settings.name == 'admin'
assert channels[2].settings.name == 'testing'
assert channels[3].settings.name == ''
assert channels[4].settings.name == ''
assert channels[5].settings.name == ''
assert channels[6].settings.name == ''
assert channels[7].settings.name == ''
anode.deleteChannel(2)
assert len(anode.channels) == 8
assert channels[0].settings.modem_config == 3
assert channels[1].settings.name == 'admin'
assert channels[2].settings.name == ''
assert channels[3].settings.name == ''
assert channels[4].settings.name == ''
assert channels[5].settings.name == ''
assert channels[6].settings.name == ''
assert channels[7].settings.name == ''
@pytest.mark.unit
def test_getChannelByName(capsys):
"""Get a channel by the name."""
anode = Node('foo', 'bar')
channel1 = Channel(index=1, role=1)
channel1.settings.modem_config = 3
channel1.settings.psk = b'\x01'
channel2 = Channel(index=2, role=2)
channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
channel2.settings.name = 'admin'
channel3 = Channel(index=3, role=0)
channel4 = Channel(index=4, role=0)
channel5 = Channel(index=5, role=0)
channel6 = Channel(index=6, role=0)
channel7 = Channel(index=7, role=0)
channel8 = Channel(index=8, role=0)
channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
anode.channels = channels
ch = anode.getChannelByName('admin')
assert ch.index == 2
@pytest.mark.unit
def test_getChannelByName_invalid_name(capsys):
"""Get a channel by the name but one that is not present."""
anode = Node('foo', 'bar')
channel1 = Channel(index=1, role=1)
channel1.settings.modem_config = 3
channel1.settings.psk = b'\x01'
channel2 = Channel(index=2, role=2)
channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
channel2.settings.name = 'admin'
channel3 = Channel(index=3, role=0)
channel4 = Channel(index=4, role=0)
channel5 = Channel(index=5, role=0)
channel6 = Channel(index=6, role=0)
channel7 = Channel(index=7, role=0)
channel8 = Channel(index=8, role=0)
channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
anode.channels = channels
ch = anode.getChannelByName('testing')
assert ch is None
@pytest.mark.unit
def test_getDisabledChannel(capsys):
"""Get the first disabled channel."""
anode = Node('foo', 'bar')
channel1 = Channel(index=1, role=1)
channel1.settings.modem_config = 3
channel1.settings.psk = b'\x01'
channel2 = Channel(index=2, role=2)
channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
channel2.settings.name = 'testingA'
channel3 = Channel(index=3, role=2)
channel3.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
channel3.settings.name = 'testingB'
channel4 = Channel(index=4, role=0)
channel5 = Channel(index=5, role=0)
channel6 = Channel(index=6, role=0)
channel7 = Channel(index=7, role=0)
channel8 = Channel(index=8, role=0)
channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
anode.channels = channels
ch = anode.getDisabledChannel()
assert ch.index == 4
@pytest.mark.unit
def test_getDisabledChannel_where_all_channels_are_used(capsys):
"""Get the first disabled channel."""
anode = Node('foo', 'bar')
channel1 = Channel(index=1, role=1)
channel1.settings.modem_config = 3
channel1.settings.psk = b'\x01'
channel2 = Channel(index=2, role=2)
channel3 = Channel(index=3, role=2)
channel4 = Channel(index=4, role=2)
channel5 = Channel(index=5, role=2)
channel6 = Channel(index=6, role=2)
channel7 = Channel(index=7, role=2)
channel8 = Channel(index=8, role=2)
channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
anode.channels = channels
ch = anode.getDisabledChannel()
assert ch is None
@pytest.mark.unit
def test_getAdminChannelIndex(capsys):
"""Get the 'admin' channel index."""
anode = Node('foo', 'bar')
channel1 = Channel(index=1, role=1)
channel1.settings.modem_config = 3
channel1.settings.psk = b'\x01'
channel2 = Channel(index=2, role=2)
channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
channel2.settings.name = 'admin'
channel3 = Channel(index=3, role=0)
channel4 = Channel(index=4, role=0)
channel5 = Channel(index=5, role=0)
channel6 = Channel(index=6, role=0)
channel7 = Channel(index=7, role=0)
channel8 = Channel(index=8, role=0)
channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
anode.channels = channels
i = anode._getAdminChannelIndex()
assert i == 2
@pytest.mark.unit
def test_getAdminChannelIndex_when_no_admin_named_channel(capsys):
"""Get the 'admin' channel when there is not one."""
anode = Node('foo', 'bar')
channel1 = Channel(index=1, role=1)
channel1.settings.modem_config = 3
channel1.settings.psk = b'\x01'
channel2 = Channel(index=2, role=0)
channel3 = Channel(index=3, role=0)
channel4 = Channel(index=4, role=0)
channel5 = Channel(index=5, role=0)
channel6 = Channel(index=6, role=0)
channel7 = Channel(index=7, role=0)
channel8 = Channel(index=8, role=0)
channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
anode.channels = channels
i = anode._getAdminChannelIndex()
assert i == 0
# TODO: should we check if we need to turn it off?
@pytest.mark.unit
def test_turnOffEncryptionOnPrimaryChannel(capsys):
"""Turn off encryption when there is a psk."""
anode = Node('foo', 'bar', noProto=True)
channel1 = Channel(index=1, role=1)
channel1.settings.modem_config = 3
# value from using "--ch-set psk 0x1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b "
channel1.settings.psk = b'\x1a\x1a\x1a\x1a++++\x1a\x1a\x1a\x1a++++\x1a\x1a\x1a\x1a++++\x1a\x1a\x1a\x1a++++'
channel2 = Channel(index=2, role=0)
channel3 = Channel(index=3, role=0)
channel4 = Channel(index=4, role=0)
channel5 = Channel(index=5, role=0)
channel6 = Channel(index=6, role=0)
channel7 = Channel(index=7, role=0)
channel8 = Channel(index=8, role=0)
channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
anode.channels = channels
anode.turnOffEncryptionOnPrimaryChannel()
out, err = capsys.readouterr()
assert re.search(r'Writing modified channels to device', out)
assert err == ''
@pytest.mark.unit
def test_writeConfig_with_no_radioConfig(capsys):
"""Test writeConfig with no radioConfig."""
anode = Node('foo', 'bar', noProto=True)
with pytest.raises(SystemExit) as pytest_wrapped_e:
anode.writeConfig()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r'Error: No RadioConfig has been read', out)
assert err == ''
@pytest.mark.unit
def test_writeConfig(caplog):
"""Test writeConfig"""
anode = Node('foo', 'bar', noProto=True)
radioConfig = RadioConfig()
anode.radioConfig = radioConfig
with caplog.at_level(logging.DEBUG):
anode.writeConfig()
assert re.search(r'Wrote config', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_requestChannel_not_localNode(caplog):
"""Test _requestChannel()"""
iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
mo.localNode.getChannelByName.return_value = None
mo.myInfo.max_channels = 8
anode = Node(mo, 'bar', noProto=True)
with caplog.at_level(logging.DEBUG):
anode._requestChannel(0)
assert re.search(r'Requesting channel 0 info from remote node', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_requestChannel_localNode(caplog):
"""Test _requestChannel()"""
iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
mo.localNode.getChannelByName.return_value = None
mo.myInfo.max_channels = 8
anode = Node(mo, 'bar', noProto=True)
# Note: Have to do this next line because every call to MagicMock object/method returns a new magic mock
mo.localNode = anode
with caplog.at_level(logging.DEBUG):
anode._requestChannel(0)
assert re.search(r'Requesting channel 0', caplog.text, re.MULTILINE)
assert not re.search(r'from remote node', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_onResponseRequestChannel(caplog):
"""Test onResponseRequestChannel()"""
channel1 = Channel(index=1, role=1)
channel1.settings.modem_config = 3
channel1.settings.psk = b'\x01'
msg1 = MagicMock(autospec=AdminMessage)
msg1.get_channel_response = channel1
msg2 = MagicMock(autospec=AdminMessage)
channel2 = Channel(index=2, role=0) # disabled
msg2.get_channel_response = channel2
# default primary channel
packet1 = {
'from': 2475227164,
'to': 2475227164,
'decoded': {
'portnum': 'ADMIN_APP',
'payload': b':\t\x12\x05\x18\x03"\x01\x01\x18\x01',
'requestId': 2615094405,
'admin': {
'getChannelResponse': {
'settings': {
'modemConfig': 'Bw125Cr48Sf4096',
'psk': 'AQ=='
},
'role': 'PRIMARY'
},
'raw': msg1,
}
},
'id': 1692918436,
'hopLimit': 3,
'priority':
'RELIABLE',
'raw': 'fake',
'fromId': '!9388f81c',
'toId': '!9388f81c'
}
# no other channels
packet2 = {
'from': 2475227164,
'to': 2475227164,
'decoded': {
'portnum': 'ADMIN_APP',
'payload': b':\x04\x08\x02\x12\x00',
'requestId': 743049663,
'admin': {
'getChannelResponse': {
'index': 2,
'settings': {}
},
'raw': msg2,
}
},
'id': 1692918456,
'rxTime': 1640202239,
'hopLimit': 3,
'priority': 'RELIABLE',
'raw': 'faked',
'fromId': '!9388f81c',
'toId': '!9388f81c'
}
iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
mo.localNode.getChannelByName.return_value = None
mo.myInfo.max_channels = 8
anode = Node(mo, 'bar', noProto=True)
radioConfig = RadioConfig()
anode.radioConfig = radioConfig
# Note: Have to do this next line because every call to MagicMock object/method returns a new magic mock
mo.localNode = anode
with caplog.at_level(logging.DEBUG):
anode.requestConfig()
anode.onResponseRequestChannel(packet1)
assert re.search(r'Received channel', caplog.text, re.MULTILINE)
anode.onResponseRequestChannel(packet2)
assert re.search(r'Received channel', caplog.text, re.MULTILINE)
assert re.search(r'Finished downloading channels', caplog.text, re.MULTILINE)
assert len(anode.channels) == 8
assert anode.channels[0].settings.modem_config == 3
assert anode.channels[1].settings.name == ''
assert anode.channels[2].settings.name == ''
assert anode.channels[3].settings.name == ''
assert anode.channels[4].settings.name == ''
assert anode.channels[5].settings.name == ''
assert anode.channels[6].settings.name == ''
assert anode.channels[7].settings.name == ''
@pytest.mark.unit
def test_onResponseRequestSetting(caplog):
"""Test onResponseRequestSetting()"""
# Note: Split out the get_radio_response to a MagicMock
# so it could be "returned" (not really sure how to do that
# in a python dict.
amsg = MagicMock(autospec=AdminMessage)
amsg.get_radio_response = """{
preferences {
phone_timeout_secs: 900
ls_secs: 300
position_broadcast_smart: true
position_flags: 35
}
}"""
packet = {
'from': 2475227164,
'to': 2475227164,
'decoded': {
'portnum': 'ADMIN_APP',
'payload': b'*\x0e\n\x0c0\x84\x07P\xac\x02\x88\x01\x01\xb0\t#',
'requestId': 3145147848,
'admin': {
'getRadioResponse': {
'preferences': {
'phoneTimeoutSecs': 900,
'lsSecs': 300,
'positionBroadcastSmart': True,
'positionFlags': 35
}
},
'raw': amsg
},
'id': 365963704,
'rxTime': 1640195197,
'hopLimit': 3,
'priority': 'RELIABLE',
'raw': 'faked',
'fromId': '!9388f81c',
'toId': '!9388f81c'
}
}
iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
mo.localNode.getChannelByName.return_value = None
mo.myInfo.max_channels = 8
anode = Node(mo, 'bar', noProto=True)
radioConfig = RadioConfig()
anode.radioConfig = radioConfig
# Note: Have to do this next line because every call to MagicMock object/method returns a new magic mock
mo.localNode = anode
with caplog.at_level(logging.DEBUG):
anode.onResponseRequestSettings(packet)
assert re.search(r'Received radio config, now fetching channels..', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_onResponseRequestSetting_with_error(capsys):
"""Test onResponseRequestSetting() with an error"""
packet = {
'from': 2475227164,
'to': 2475227164,
'decoded': {
'portnum': 'ADMIN_APP',
'payload': b'*\x0e\n\x0c0\x84\x07P\xac\x02\x88\x01\x01\xb0\t#',
'requestId': 3145147848,
'routing': {
'errorReason': 'some made up error',
},
'admin': {
'getRadioResponse': {
'preferences': {
'phoneTimeoutSecs': 900,
'lsSecs': 300,
'positionBroadcastSmart': True,
'positionFlags': 35
}
},
},
'id': 365963704,
'rxTime': 1640195197,
'hopLimit': 3,
'priority': 'RELIABLE',
'fromId': '!9388f81c',
'toId': '!9388f81c'
}
}
iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
mo.localNode.getChannelByName.return_value = None
mo.myInfo.max_channels = 8
anode = Node(mo, 'bar', noProto=True)
radioConfig = RadioConfig()
anode.radioConfig = radioConfig
# Note: Have to do this next line because every call to MagicMock object/method returns a new magic mock
mo.localNode = anode
anode.onResponseRequestSettings(packet)
out, err = capsys.readouterr()
assert re.search(r'Error on response', out)
assert err == ''

View File

@@ -0,0 +1,89 @@
"""Meshtastic unit tests for remote_hardware.py"""
import logging
import re
from unittest.mock import patch, MagicMock
import pytest
from ..remote_hardware import RemoteHardwareClient, onGPIOreceive
from ..serial_interface import SerialInterface
@pytest.mark.unit
def test_RemoteHardwareClient():
"""Test that we can instantiate a RemoteHardwareClient instance"""
iface = MagicMock(autospec=SerialInterface)
rhw = RemoteHardwareClient(iface)
assert rhw.iface == iface
iface.close()
@pytest.mark.unit
def test_onGPIOreceive(capsys):
"""Test onGPIOreceive"""
iface = MagicMock(autospec=SerialInterface)
packet = {'decoded': {'remotehw': {'typ': 'foo', 'gpioValue': '4096' }}}
onGPIOreceive(packet, iface)
out, err = capsys.readouterr()
assert re.search(r'Received RemoteHardware', out)
assert err == ''
@pytest.mark.unit
def test_RemoteHardwareClient_no_gpio_channel(capsys):
"""Test that we can instantiate a RemoteHardwareClient instance but there is no channel named channel 'gpio'"""
iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
mo.localNode.getChannelByName.return_value = None
with pytest.raises(SystemExit) as pytest_wrapped_e:
RemoteHardwareClient(mo)
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r'Warning: No channel named', out)
assert err == ""
@pytest.mark.unit
def test_readGPIOs(caplog):
"""Test readGPIOs"""
iface = MagicMock(autospec=SerialInterface)
rhw = RemoteHardwareClient(iface)
with caplog.at_level(logging.DEBUG):
rhw.readGPIOs('0x10', 123)
assert re.search(r'readGPIOs', caplog.text, re.MULTILINE)
iface.close()
@pytest.mark.unit
def test_writeGPIOs(caplog):
"""Test writeGPIOs"""
iface = MagicMock(autospec=SerialInterface)
rhw = RemoteHardwareClient(iface)
with caplog.at_level(logging.DEBUG):
rhw.writeGPIOs('0x10', 123, 1)
assert re.search(r'writeGPIOs', caplog.text, re.MULTILINE)
iface.close()
@pytest.mark.unit
def test_watchGPIOs(caplog):
"""Test watchGPIOs"""
iface = MagicMock(autospec=SerialInterface)
rhw = RemoteHardwareClient(iface)
with caplog.at_level(logging.DEBUG):
rhw.watchGPIOs('0x10', 123)
assert re.search(r'watchGPIOs', caplog.text, re.MULTILINE)
iface.close()
@pytest.mark.unit
def test_sendHardware_no_nodeid():
"""Test sending no nodeid to _sendHardware()"""
iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
with pytest.raises(SystemExit) as pytest_wrapped_e:
rhw = RemoteHardwareClient(mo)
rhw._sendHardware(None, None)
assert pytest_wrapped_e.type == SystemExit

View File

@@ -2,6 +2,7 @@
import re
import subprocess
import time
import platform
import os
# Do not like using hard coded sleeps, but it probably makes
@@ -104,7 +105,7 @@ 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 re.search(r'^DEBUG file', out, re.MULTILINE)
assert return_value == 0
@@ -140,8 +141,9 @@ 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)
if platform.system() != 'Windows':
assert re.search(r' User ', out, re.MULTILINE)
assert re.search(r' 1 ', out, re.MULTILINE)
assert return_value == 0
@@ -267,11 +269,11 @@ def test_smoke1_ch_values():
"""
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-longfast': 'Bw31_25Cr48Sf512',
'--ch-mediumslow': 'Bw250Cr46Sf2048',
'--ch-mediumfast': 'Bw250Cr47Sf1024',
# for some reason, this value does not show any modemConfig
'--ch-shortslow': '{ "psk',
'--ch-shortfast': 'Bw500Cr45Sf128'
}
@@ -662,7 +664,7 @@ def test_smoke1_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 re.search(r'Setting Ham ID', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)

View File

@@ -1,6 +1,9 @@
"""Meshtastic unit tests for stream_interface.py"""
import logging
import re
from unittest.mock import MagicMock
import pytest
from ..stream_interface import StreamInterface
@@ -8,7 +11,71 @@ from ..stream_interface import StreamInterface
@pytest.mark.unit
def test_StreamInterface():
"""Test that we can instantiate a StreamInterface"""
"""Test that we cannot instantiate a StreamInterface based on noProto"""
with pytest.raises(Exception) as pytest_wrapped_e:
StreamInterface(noProto=True)
StreamInterface()
assert pytest_wrapped_e.type == Exception
# Note: This takes a bit, so moving from unit to slow
@pytest.mark.unitslow
def test_StreamInterface_with_noProto(caplog, reset_globals):
"""Test that we can instantiate a StreamInterface based on nonProto
and we can read/write bytes from a mocked stream
"""
stream = MagicMock()
test_data = b'hello'
stream.read.return_value = test_data
with caplog.at_level(logging.DEBUG):
iface = StreamInterface(noProto=True, connectNow=False)
iface.stream = stream
iface._writeBytes(test_data)
data = iface._readBytes(len(test_data))
assert data == test_data
# Note: This takes a bit, so moving from unit to slow
# Tip: If you want to see the print output, run with '-s' flag:
# pytest -s meshtastic/tests/test_stream_interface.py::test_sendToRadioImpl
@pytest.mark.unitslow
def test_sendToRadioImpl(caplog, reset_globals):
"""Test _sendToRadioImpl()"""
# def add_header(b):
# """Add header stuffs for radio"""
# bufLen = len(b)
# header = bytes([START1, START2, (bufLen >> 8) & 0xff, bufLen & 0xff])
# return header + b
# captured raw bytes of a Heltec2.1 radio with 2 channels (primary and a secondary channel named "gpio")
raw_1_my_info = b'\x1a,\x08\xdc\x8c\xd5\xc5\x02\x18\r2\x0e1.2.49.5354c49P\x15]\xe1%\x17Eh\xe0\xa7\x12p\xe8\x9d\x01x\x08\x90\x01\x01'
raw_2_node_info = b'"9\x08\xdc\x8c\xd5\xc5\x02\x12(\n\t!28b5465c\x12\x0cUnknown 465c\x1a\x03?5C"\x06$o(\xb5F\\0\n\x1a\x02 1%M<\xc6a'
# pylint: disable=C0301
raw_3_node_info = b'"C\x08\xa4\x8c\xd5\xc5\x02\x12(\n\t!28b54624\x12\x0cUnknown 4624\x1a\x03?24"\x06$o(\xb5F$0\n\x1a\x07 5MH<\xc6a%G<\xc6a=\x00\x00\xc0@'
raw_4_complete = b'@\xcf\xe5\xd1\x8c\x0e'
# pylint: disable=C0301
raw_5_prefs = b'Z6\r\\F\xb5(\x15\\F\xb5("\x1c\x08\x06\x12\x13*\x11\n\x0f0\x84\x07P\xac\x02\x88\x01\x01\xb0\t#\xb8\t\x015]$\xddk5\xd5\x7f!b=M<\xc6aP\x03`F'
# pylint: disable=C0301
raw_6_channel0 = b'Z.\r\\F\xb5(\x15\\F\xb5("\x14\x08\x06\x12\x0b:\t\x12\x05\x18\x01"\x01\x01\x18\x015^$\xddk5\xd6\x7f!b=M<\xc6aP\x03`F'
# pylint: disable=C0301
raw_7_channel1 = b'ZS\r\\F\xb5(\x15\\F\xb5("9\x08\x06\x120:.\x08\x01\x12(" \xb4&\xb3\xc7\x06\xd8\xe39%\xba\xa5\xee\x8eH\x06\xf6\xf4H\xe8\xd5\xc1[ao\xb5Y\\\xb4"\xafmi*\x04gpio\x18\x025_$\xddk5\xd7\x7f!b=M<\xc6aP\x03`F'
raw_8_channel2 = b'Z)\r\\F\xb5(\x15\\F\xb5("\x0f\x08\x06\x12\x06:\x04\x08\x02\x12\x005`$\xddk5\xd8\x7f!b=M<\xc6aP\x03`F'
raw_blank = b''
test_data = b'hello'
stream = MagicMock()
#stream.read.return_value = add_header(test_data)
stream.read.side_effect = [ raw_1_my_info, raw_2_node_info, raw_3_node_info, raw_4_complete,
raw_5_prefs, raw_6_channel0, raw_7_channel1, raw_8_channel2,
raw_blank, raw_blank]
toRadio = MagicMock()
toRadio.SerializeToString.return_value = test_data
with caplog.at_level(logging.DEBUG):
iface = StreamInterface(noProto=True, connectNow=False)
iface.stream = stream
iface.connect()
iface._sendToRadioImpl(toRadio)
assert re.search(r'Sending: ', caplog.text, re.MULTILINE)
assert re.search(r'reading character', caplog.text, re.MULTILINE)
assert re.search(r'In reader loop', caplog.text, re.MULTILINE)
print(caplog.text)

View File

@@ -1,10 +1,13 @@
"""Meshtastic unit tests for util.py"""
import re
import logging
import pytest
from meshtastic.util import fixme, stripnl, pskToString, our_exit, support_info, genPSK256, fromStr, fromPSK
from meshtastic.util import (fixme, stripnl, pskToString, our_exit,
support_info, genPSK256, fromStr, fromPSK,
quoteBooleans, catchAndIgnore)
@pytest.mark.unit
@@ -35,6 +38,16 @@ def test_fromStr():
assert fromStr('abc') == 'abc'
@pytest.mark.unit
def test_quoteBooleans():
"""Test quoteBooleans"""
assert quoteBooleans('') == ''
assert quoteBooleans('foo') == 'foo'
assert quoteBooleans('true') == 'true'
assert quoteBooleans('false') == 'false'
assert quoteBooleans(': true') == ": 'true'"
assert quoteBooleans(': false') == ": 'false'"
@pytest.mark.unit
def test_fromPSK():
"""Test fromPSK"""
@@ -110,7 +123,7 @@ def test_our_exit_non_zero_return_value():
@pytest.mark.unit
def test_fixme():
"""Test fixme"""
"""Test fixme()"""
with pytest.raises(Exception) as pytest_wrapped_e:
fixme("some exception")
assert pytest_wrapped_e.type == Exception
@@ -126,3 +139,13 @@ def test_support_info(capsys):
assert re.search(r'Machine', out, re.MULTILINE)
assert re.search(r'Executable', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
def test_catchAndIgnore(caplog):
"""Test catchAndIgnore() does not actually throw an exception, but just logs"""
def some_closure():
raise Exception('foo')
with caplog.at_level(logging.DEBUG):
catchAndIgnore("something", some_closure)
assert re.search(r'Exception thrown in something', caplog.text, re.MULTILINE)

View File

@@ -16,6 +16,14 @@ import pkg_resources
blacklistVids = dict.fromkeys([0x1366])
def quoteBooleans(a_string):
"""Quote booleans
given a string that contains ": true", replace with ": 'true'" (or false)
"""
tmp = a_string.replace(": true", ": 'true'")
tmp = tmp.replace(": false", ": 'false'")
return tmp
def genPSK256():
"""Generate a random preshared key"""
return os.urandom(32)
@@ -94,7 +102,7 @@ def fixme(message):
def catchAndIgnore(reason, closure):
"""Call a closure but if it throws an excpetion print it and continue"""
"""Call a closure but if it throws an exception print it and continue"""
try:
closure()
except BaseException as ex:

2
proto

Submodule proto updated: 10e6857b1b...1d3b4806ab

View File

@@ -1,10 +1,12 @@
[pytest]
addopts = -m "not smoke1 and not smoke2 and not smokewifi"
addopts = -m "not int and not smoke1 and not smoke2 and not smokewifi and not examples"
markers =
unit: marks tests as unit tests
unitslow: marks slow 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
examples: runs the examples tests which validates the library

View File

@@ -16,3 +16,5 @@ pytest
pytest-cov
pyyaml
pytap2
pdoc3
pypubsub

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.45",
version="1.2.47",
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"]
},