Compare commits

..

15 Commits

Author SHA1 Message Date
mkinney
f31297194a Merge pull request #305 from mkinney/1.2-legacy
fix the vendor and product id for tlora v1
2022-03-15 19:53:29 -07:00
Mike Kinney
4dc3bea674 fix the vendor and product id for tlora v1 2022-03-15 19:52:45 -07:00
github-actions
95f5d77c47 bump version 2022-03-16 02:34:58 +00:00
mkinney
5034e2ca43 Merge pull request #304 from mkinney/1.2-legacy
fix some tlora entries
2022-03-15 19:33:47 -07:00
Mike Kinney
eb4f68fccc fix some tlora entries 2022-03-15 19:32:27 -07:00
github-actions
de03f8e1fb bump version 2022-03-08 18:57:44 +00:00
mkinney
56f3e8a893 Merge pull request #297 from mkinney/1.2-legacy
refactor util; add duplicate check
2022-03-08 10:38:37 -08:00
mkinney
fbe0c09909 Merge branch '1.2-legacy' into 1.2-legacy 2022-03-08 10:37:15 -08:00
Mike Kinney
20c65974e9 refactor code to util 2022-03-08 10:34:46 -08:00
Mike Kinney
96e42ac3f2 add duplicate check 2022-03-08 10:24:44 -08:00
github-actions
3912f5728a bump version 2022-03-08 06:40:00 +00:00
mkinney
c016176520 Update release.yml 2022-03-07 22:38:45 -08:00
Mike Kinney
e6999ba5ad keep sha ref and use it for subsequent checkouts 2022-03-07 22:38:27 -08:00
Mike Kinney
5590dbeb6f do not checkout the code again 2022-03-07 22:38:06 -08:00
mkinney
e8a2909173 Merge pull request #294 from mkinney/bug_when_ports_not_sorted
fix when ports are not sorted
2022-03-07 22:37:49 -08:00
100 changed files with 8636 additions and 7284 deletions

4
.gitattributes vendored
View File

@@ -1,4 +0,0 @@
* text=auto eol=lf
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf
*.{sh,[sS][hH]} text eol=lf

View File

@@ -1,4 +1,4 @@
name: CI
name: Linting and Tests
on:
push:
branches:
@@ -35,21 +35,21 @@ jobs:
which meshtastic
meshtastic --version
- name: Run pylint
run: pylint meshtastic examples/ --ignore-patterns ".*_pb2.py$"
run: pylint meshtastic examples/
- name: Run tests with pytest
run: pytest --cov=meshtastic
- name: Generate coverage report
run: |
pytest --cov=meshtastic --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
yml: ./codecov.yml
fail_ci_if_error: true
verbose: true
validate:
runs-on: ubuntu-latest
strategy:

View File

@@ -1,20 +0,0 @@
name: Remove old artifacts
on:
schedule:
# Every day at 1am
- cron: "0 1 * * *"
workflow_dispatch:
jobs:
remove-old-artifacts:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Remove old artifacts
uses: c-hive/gha-remove-artifacts@v1
with:
age: "1 month"
skip-tags: true

View File

@@ -11,73 +11,76 @@ jobs:
new_sha: ${{ steps.commit_updated.outputs.sha }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Bump version
run: >-
bin/bump_version.py
- name: Checkout
uses: actions/checkout@v2
- name: Commit updated version.py
id: commit_updated
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 setup.py
git commit -m "bump version" && git push || echo "No changes to commit"
git log -n 1 --pretty=format:"%H" | tail -n 1 | awk '{print "::set-output name=sha::"$0}'
- name: Bump version
run: >-
bin/bump_version.py
- name: Get version
id: get_version
run: >-
bin/show_version.py
- name: Commit updated version.py
id: commit_updated
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 setup.py
git commit -m "bump version" && git push || echo "No changes to commit"
git log -n 1 --pretty=format:"%H" | tail -n 1 | awk '{print "::set-output name=sha::"$0}'
- name: Create GitHub release
uses: actions/create-release@v1
id: create_release
- name: Get version
id: get_version
run: >-
bin/show_version.py
with:
draft: true
prerelease: true
release_name: Meshtastic Python ${{ steps.get_version.outputs.version }}
tag_name: ${{ steps.get_version.outputs.version }}
body: |
Autogenerated by github action, developer should edit as required before publishing...
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create GitHub release
uses: actions/create-release@v1
id: create_release
- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9
with:
draft: true
prerelease: true
release_name: Meshtastic Python ${{ steps.get_version.outputs.version }}
tag_name: ${{ steps.get_version.outputs.version }}
body: |
Autogenerated by github action, developer should edit as required before publishing...
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install pypa/build
run: >-
python -m
pip install
build
--user
- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Build a binary wheel and a source tarball
run: >-
python -m
build
--sdist
--wheel
--outdir dist/
.
- name: Install pypa/build
run: >-
python -m
pip install
build
--user
- name: Build a binary wheel and a source tarball
run: >-
python -m
build
--sdist
--wheel
--outdir dist/
.
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@master
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@master
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
build-and-publish-mac:
runs-on: macos-latest
needs: release_create
steps:
- name: Checkout
uses: actions/checkout@v3
with:
@@ -124,6 +127,7 @@ jobs:
runs-on: ubuntu-latest
needs: release_create
steps:
- name: Checkout
uses: actions/checkout@v3
with:
@@ -165,6 +169,7 @@ jobs:
runs-on: windows-latest
needs: release_create
steps:
- name: Checkout
uses: actions/checkout@v3
with:

View File

@@ -15,22 +15,22 @@ jobs:
run: |
git pull --recurse-submodules
git submodule update --remote --recursive
- name: Download nanopb
run: |
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.6-linux-x86.tar.gz
tar xvzf nanopb-0.4.6-linux-x86.tar.gz
mv nanopb-0.4.6-linux-x86 nanopb-0.4.6
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.4-linux-x86.tar.gz
tar xvzf nanopb-0.4.4-linux-x86.tar.gz
mv nanopb-0.4.4-linux-x86 nanopb-0.4.4
- name: Re-generate protocol buffers
run: |
./bin/regen-protobufs.sh
./bin/regen-protos.sh
- name: Commit update
run: |
git config --global user.name 'github-actions'
git config --global user.email 'bot@noreply.github.com'
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
git add protobufs
git add proto
git add meshtastic
git commit -m "Update protobuf submodule" && git push || echo "No changes to commit"

3
.gitignore vendored
View File

@@ -5,7 +5,8 @@ dist
*.egg-info
log_*
.eggs
nanopb-*
nanopb-0.4.4
nanopb-0.4.5
.*swp
.coverage
*.py-E

6
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "protobufs"]
path = protobufs
url = http://github.com/meshtastic/protobufs
[submodule "proto"]
path = proto
url = https://github.com/meshtastic/Meshtastic-protobufs.git

View File

@@ -7,7 +7,7 @@
# Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths.
ignore-patterns=mqtt_pb2.py,channel_pb2.py,telemetry_pb2.py,admin_pb2.py,config_pb2.py,deviceonly_pb2.py,apponly_pb2.py,remote_hardware_pb2.py,portnums_pb2.py,mesh_pb2.py,storeforward_pb2.py,cannedmessages_pb2.py,module_config_pb2.py,localonly_pb2.py,node.py,device_metadata_pb2.py
ignore-patterns=mqtt_pb2.py,channel_pb2.py,environmental_measurement_pb2.py,admin_pb2.py,radioconfig_pb2.py,deviceonly_pb2.py,apponly_pb2.py,remote_hardware_pb2.py,portnums_pb2.py,mesh_pb2.py,storeforward_pb2.py,cannedmessages_pb2.py
@@ -23,7 +23,8 @@ ignore-patterns=mqtt_pb2.py,channel_pb2.py,telemetry_pb2.py,admin_pb2.py,config_
# 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,pointless-string-statement,too-few-public-methods,broad-except,no-else-return,no-else-raise,bare-except,too-many-public-methods
disable=invalid-name,fixme,logging-fstring-interpolation,too-many-statements,too-many-branches,too-many-locals,no-member,f-string-without-interpolation,protected-access,no-self-use,pointless-string-statement,too-few-public-methods,broad-except,no-else-return,no-else-raise,bare-except,too-many-public-methods
[BASIC]
@@ -40,7 +41,7 @@ bad-names=foo,bar,baz,toto,tutu,tata
max-line-length=150
# Maximum number of lines in a module
max-module-lines=1600
max-module-lines=1400

7
.trunk/.gitignore vendored
View File

@@ -1,7 +0,0 @@
*out
*logs
*actions
*notifications
plugins
user_trunk.yaml
user.yaml

View File

@@ -1,2 +0,0 @@
[settings]
profile=black

View File

@@ -1,10 +0,0 @@
# Autoformatter friendly markdownlint config (all formatting rules disabled)
default: true
blank_lines: false
bullet: false
html: false
indentation: false
line_length: false
spaces: false
url: false
whitespace: false

View File

@@ -1,7 +0,0 @@
enable=all
source-path=SCRIPTDIR
disable=SC2154
# If you're having issues with shellcheck following source, disable the errors via:
# disable=SC1090
# disable=SC1091

View File

@@ -1,10 +0,0 @@
rules:
quoted-strings:
required: only-when-needed
extra-allowed: ["{|}"]
empty-values:
forbid-in-block-mappings: true
forbid-in-flow-mappings: true
key-duplicates: {}
octal-values:
forbid-implicit-octal: true

View File

@@ -1,5 +0,0 @@
# Generic, formatter-friendly config.
select = ["B", "D3", "D4", "E", "F"]
# Never enforce `E501` (line length violations). This should be handled by formatters.
ignore = ["E501"]

View File

@@ -1,39 +0,0 @@
version: 0.1
cli:
version: 1.7.0
plugins:
sources:
- id: trunk
ref: v0.0.14
uri: https://github.com/trunk-io/plugins
lint:
ignore:
- linters: [ALL]
paths:
# Ignore generated files
- meshtastic/*_pb2.py
enabled:
- actionlint@1.6.23
- black@23.3.0
- git-diff-check
- gitleaks@8.16.2
- isort@5.12.0
- markdownlint@0.33.0
- prettier@2.8.7
- pylint@2.17.1
- ruff@0.0.260
- shellcheck@0.9.0
- shfmt@3.5.0
- yamllint@1.30.0
runtimes:
enabled:
- go@1.19.5
- node@18.12.1
- python@3.10.8
actions:
disabled:
- trunk-announce
- trunk-check-pre-push
- trunk-fmt-pre-commit
enabled:
- trunk-upgrade-available

93
.vscode/launch.json vendored
View File

@@ -42,87 +42,7 @@
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug"]
},
{
"name": "meshtastic debug getPref",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--get", "power.is_power_saving"]
},
{
"name": "meshtastic debug getPref telemetry",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--get", "telemetry.environment_update_interval"]
},
{
"name": "meshtastic debug info",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--info"]
},
{
"name": "meshtastic debug set region",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--set", "lora.region", "TW"]
},
{
"name": "meshtastic debug set bluetooth fixed pin",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--set", "bluetooth.fixed_pin", "555555"]
},
{
"name": "meshtastic debug get bluetooth fixed pin",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--get", "bluetooth.fixed_pin"]
},
{
"name": "meshtastic debug setPref",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--set", "power.is_power_saving", "1"]
},
{
"name": "meshtastic debug setPref telemetry.environment_measurement_enabled",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--set", "telemetry.environment_measurement_enabled", "1"]
},
{
"name": "meshtastic debug setPref telemetry.environment_screen_enabled",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--set", "telemetry.environment_screen_enabled", "1"]
},
{
"name": "meshtastic debug setPref telemetry",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--set", "telemetry.environment_measurement_enabled", "1"]
"args": ["--debug" ]
},
{
"name": "meshtastic setpref",
@@ -132,15 +52,6 @@
"justMyCode": true,
"args": ["--debug", "--setchan", "psk", ""]
},
{
"name": "meshtastic --ch-set",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--ch-set", "channel_num", "0", "--ch-index", "0"]
},
{
"name": "meshtastic seturl",
"type": "python",
@@ -181,7 +92,7 @@
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--sendtext", "pytest"]
},
}
{
"name": "meshtastic showNodes",
"type": "python",

View File

@@ -6,10 +6,6 @@ test:
virt:
pytest -m smokevirt
# run the smoke1 test (after doing a factory reset and unplugging/replugging in device)
smoke1:
pytest -m smoke1 -s -vv
# local install
install:
pip install .
@@ -26,11 +22,11 @@ lint:
slow:
pytest -m unit --durations=5
protobufs: FORCE
proto: FORCE
git submodule update --init --recursive
git pull --rebase
git submodule update --remote --merge
./bin/regen-protobufs.sh
./bin/regen-protos.sh
# run the coverage report and open results in a browser
cov:

View File

@@ -1,23 +1,15 @@
# Meshtastic Python
# 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)
[![codecov](https://codecov.io/gh/meshtastic/Meshtastic-python/branch/master/graph/badge.svg?token=TIWPJL73KV)](https://codecov.io/gh/meshtastic/Meshtastic-python)
![PyPI - Downloads](https://img.shields.io/pypi/dm/meshtastic)
[![CI](https://img.shields.io/github/actions/workflow/status/meshtastic/python/ci.yml?branch=master&label=actions&logo=github&color=yellow)](https://github.com/meshtastic/python/actions/workflows/ci.yml)
[![CLA assistant](https://cla-assistant.io/readme/badge/meshtastic/python)](https://cla-assistant.io/meshtastic/python)
[![Fiscal Contributors](https://opencollective.com/meshtastic/tiers/badge.svg?label=Fiscal%20Contributors&color=deeppink)](https://opencollective.com/meshtastic/)
[![Vercel](https://img.shields.io/static/v1?label=Powered%20by&message=Vercel&style=flat&logo=vercel&color=000000)](https://vercel.com?utm_source=meshtastic&utm_campaign=oss)
## Overview
A python client for using [Meshtastic](https://www.meshtastic.org) devices. This small library (and example application) provides an easy API for sending and receiving messages over mesh radios. It also provides access to any of the operations/data available in the device user interface or the Android application. Events are delivered using a publish-subscribe model, and you can subscribe to only the message types you are interested in.
A Python client for use with Meshtastic devices.
This small library (and example application) provides an easy API for sending and receiving messages over mesh radios.
It also provides access to any of the operations/data available in the device user interface or the Android application.
Events are delivered using a publish-subscribe model, and you can subscribe to only the message types you are interested in.
Full documentation including examples [here](https://meshtastic.org/docs/software/python/python-installation).
**[Getting Started Guide](https://meshtastic.org/docs/software/python/cli/installation)**
The library api is documented [here](https://meshtastic-python.vercel.app/meshtastic/index.html)
**[Documentation/API Reference](https://python.meshtastic.org/)**
## Stats
![Alt](https://repobeats.axiom.co/api/embed/c71ee8fc4a79690402e5d2807a41eec5e96d9039.svg "Repobeats analytics image")
[![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

@@ -34,7 +34,7 @@ Basic functionality is complete now.
- DONE add fromId and toId to received messages dictionaries
- make command line options for displaying/changing config
- update nodedb as nodes change
- localConfig - getter/setter syntax: https://www.python-course.eu/python3_properties.php
- radioConfig - getter/setter syntax: https://www.python-course.eu/python3_properties.php
- let user change radio params via commandline options
- keep nodedb up-to-date based on received MeshPackets
- handle radio reboots and redownload db when that happens. Look for a special FromRadio.rebooted packet

View File

@@ -1,30 +1,25 @@
#!/usr/bin/env python
"""Bump the version number"""
import re
version_filename = "setup.py"
lines = None
with open(version_filename, "r", encoding="utf-8") as f:
with open(version_filename, 'r', encoding='utf-8') as f:
lines = f.readlines()
with open(version_filename, "w", encoding="utf-8") as f:
with open(version_filename, 'w', encoding='utf-8') as f:
for line in lines:
if line.lstrip().startswith("version="):
# get rid of quotes around the version
line = line.replace('"', "")
line = line.replace('"', '')
# get rid of trailing comma
line = line.replace(",", "")
# split on '='
words = line.split("=")
# split the version into parts (by period)
v = words[1].split(".")
build_num = re.findall(r"\d+", v[2])[0]
new_build_num = str(int(build_num) + 1)
ver = f"{v[0]}.{v[1]}.{v[2].replace(build_num, new_build_num)}".replace(
"\n", ""
)
ver = f'{v[0]}.{v[1]}.{int(v[2]) + 1}'
f.write(f' version="{ver}",\n')
else:
f.write(line)

View File

@@ -1,19 +0,0 @@
#!/bin/bash
#Uncomment to run hack
#gsed -i 's/import "\//import ".\//g' ./protobufs/meshtastic/*
#gsed -i 's/package meshtastic;//g' ./protobufs/meshtastic/*
./nanopb-0.4.6/generator-bin/protoc -I=protobufs --python_out ./ ./protobufs/meshtastic/*.proto
# workaround for import bug in protoc https://github.com/protocolbuffers/protobuf/issues/1491#issuecomment-690618628
if [[ $OSTYPE == 'darwin'* ]]; then
sed -i '' -E 's/^(import.*_pb2)/from . \1/' meshtastic/*.py
# automate the current workaround (may be related to Meshtastic-protobufs issue #27 https://github.com/meshtastic/protobufs/issues/27)
sed -i '' -E "s/^None = 0/globals()['None'] = 0/" meshtastic/mesh_pb2.py
else
sed -i -e 's/^import.*_pb2/from . \0/' meshtastic/*.py
# automate the current workaround (may be related to Meshtastic-protobufs issue #27 https://github.com/meshtastic/protobufs/issues/27)
sed -i -e "s/^None = 0/globals()['None'] = 0/" meshtastic/mesh_pb2.py
fi

15
bin/regen-protos.sh Executable file
View File

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

View File

@@ -5,16 +5,16 @@ version_filename = "setup.py"
lines = None
with open(version_filename, "r", encoding="utf-8") as f:
with open(version_filename, 'r', encoding='utf-8') as f:
lines = f.readlines()
for line in lines:
if line.lstrip().startswith("version="):
# get rid of quotes around the version
line2 = line.replace('"', "")
line2 = line.replace('"', '')
# get rid of the trailing comma
line2 = line2.replace(",", "")
line2 = line2.replace(',', '')
# split on =
words = line2.split("=")
# Note: This format is for github actions
print(f"::set-output name=version::{words[1].strip()}")
print(f'::set-output name=version::{words[1].strip()}')

View File

@@ -7,4 +7,4 @@ python3 setup.py sdist bdist_wheel
python3 -m twine check dist/*
# test the upload
python3 -m twine upload --repository-url https://test.pypi.org/legacy/ dist/*
echo "view the upload at https://test.pypi.org/ it it looks good upload for real"
echo "view the upload at https://test.pypi.org/ it it looks good upload for real"

View File

@@ -11,6 +11,7 @@ location:
userPrefs:
region: 1
isAlwaysPowered: "true"
isAlwaysPowered: 'true'
sendOwnerInterval: 2
screenOnSecs: 31536000
waitBluetoothSecs: 31536000

View File

@@ -2,42 +2,17 @@
owner: Bob TBeam
owner_short: BOB
channel_url: https://www.meshtastic.org/e/#CgMSAQESCDgBQANIAVAe
channel_url: https://www.meshtastic.org/d/#CgUYAyIBAQ
location:
lat: 35.88888
lon: -93.88888
alt: 304
config:
bluetooth:
enabled: true
fixedPin: 123456
device:
serialEnabled: true
display:
screenOnSecs: 600
lora:
region: US
hopLimit: 3
txEnabled: true
txPower: 30
network:
ntpServer: 0.pool.ntp.org
position:
gpsAttemptTime: 900
gpsEnabled: true
gpsUpdateInterval: 120
positionBroadcastSecs: 900
positionBroadcastSmartEnabled: true
positionFlags: 3
power:
lsSecs: 300
meshSdsTimeoutSecs: 7200
minWakeSecs: 10
sdsSecs: 4294967295
module_config:
telemetry:
deviceUpdateInterval: 900
environmentUpdateInterval: 900
user_prefs:
region: 1
is_always_powered: 'true'
send_owner_interval: 2
screen_on_secs: 31536000
wait_bluetooth_secs: 31536000
location_share: 'LocEnabled'

View File

@@ -3,7 +3,6 @@
"""
import sys
import meshtastic
import meshtastic.serial_interface
@@ -16,6 +15,6 @@ if len(sys.argv) != 1:
iface = meshtastic.serial_interface.SerialInterface()
if iface.nodes:
for n in iface.nodes.values():
if n["num"] == iface.myInfo.my_node_num:
print(n["user"]["hwModel"])
if n['num'] == iface.myInfo.my_node_num:
print(n['user']['hwModel'])
iface.close()

View File

@@ -3,7 +3,6 @@
"""
import sys
import meshtastic
import meshtastic.serial_interface

View File

@@ -8,16 +8,16 @@ import meshtastic.serial_interface
iface = meshtastic.serial_interface.SerialInterface()
# call showInfo() just to ensure values are populated
# info = iface.showInfo()
#info = iface.showInfo()
if iface.myInfo:
# print(f'myInfo:{iface.myInfo}')
print(f"firmware_version:{iface.myInfo.firmware_version}")
#print(f'myInfo:{iface.myInfo}')
print(f'firmware_version:{iface.myInfo.firmware_version}')
if iface.nodes:
for n in iface.nodes.values():
if n["num"] == iface.myInfo.my_node_num:
print(n["user"]["hwModel"])
if n['num'] == iface.myInfo.my_node_num:
print(n['user']['hwModel'])
break
iface.close()

View File

@@ -3,7 +3,6 @@
"""
import sys
from pubsub import pub
import meshtastic
@@ -14,13 +13,10 @@ if len(sys.argv) < 2:
print(f"usage: {sys.argv[0]} host")
sys.exit(1)
def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=unused-argument
def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=unused-argument
"""This is called when we (re)connect to the radio."""
print(interface.myInfo)
interface.close()
pub.subscribe(onConnection, "meshtastic.connection.established")
try:

View File

@@ -4,7 +4,6 @@
import sys
import time
from pubsub import pub
import meshtastic
@@ -15,18 +14,15 @@ if len(sys.argv) < 2:
print(f"usage: {sys.argv[0]} host")
sys.exit(1)
def onReceive(packet, interface): # pylint: disable=unused-argument
def onReceive(packet, interface): # pylint: disable=unused-argument
"""called when a packet arrives"""
print(f"Received: {packet}")
def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=unused-argument
def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=unused-argument
"""called when we (re)connect to the radio"""
# defaults to broadcast, specify a destination ID if you wish
interface.sendText("hello mesh")
pub.subscribe(onReceive, "meshtastic.receive")
pub.subscribe(onConnection, "meshtastic.connection.established")
try:
@@ -34,6 +30,6 @@ try:
while True:
time.sleep(1000)
iface.close()
except Exception as ex:
except(Exception) as ex:
print(f"Error: Could not connect to {sys.argv[1]} {ex}")
sys.exit(1)

View File

@@ -3,12 +3,7 @@
"""
import sys
from meshtastic.util import (
active_ports_on_supported_devices,
detect_supported_devices,
get_unique_vendor_ids,
)
from meshtastic.util import detect_supported_devices, get_unique_vendor_ids, active_ports_on_supported_devices
# simple arg check
if len(sys.argv) != 1:
@@ -17,13 +12,13 @@ if len(sys.argv) != 1:
sys.exit(3)
vids = get_unique_vendor_ids()
print(f"Searching for all devices with these vendor ids {vids}")
print(f'Searching for all devices with these vendor ids {vids}')
sds = detect_supported_devices()
if len(sds) > 0:
print("Detected possible devices:")
print('Detected possible devices:')
for d in sds:
print(f" name:{d.name}{d.version} firmware:{d.for_firmware}")
print(f' name:{d.name}{d.version} firmware:{d.for_firmware}')
ports = active_ports_on_supported_devices(sds)
print(f"ports:{ports}")
print(f'ports:{ports}')

View File

@@ -3,7 +3,6 @@
"""
import sys
import meshtastic
import meshtastic.serial_interface

View File

@@ -1,14 +0,0 @@
"""Demonstration of how to look up a radio's location via its LAN connection.
Before running, connect your machine to the same WiFi network as the radio.
"""
import meshtastic
import meshtastic.tcp_interface
radio_hostname = "meshtastic.local" # Can also be an IP
iface = meshtastic.tcp_interface.TCPInterface(radio_hostname)
my_node_num = iface.myInfo.my_node_num
pos = iface.nodesByNum[my_node_num]["position"]
print(pos)
iface.close()

View File

@@ -1,30 +0,0 @@
meshtastic detected port: /dev/cu.wchusbserial53820208781
ioreg -p IOUSB
shows this:
| +-o USB Single Serial@14300000 <class AppleUSBDevice, id 0x1000407a5, registered, matched, active, busy 0 (18 ms), retain 14>
system_profiler SPUSBDataType > /tmp/a
with device plugged in
system_profiler SPUSBDataType > /tmp/b
with device not plugged in
diff /tmp/a /tmp/b
< USB Single Serial:
<
< Product ID: 0x55d4
< Vendor ID: 0x1a86
< Version: 4.43
< Serial Number: 5382020878
< Speed: Up to 12 Mb/s
< Location ID: 0x14300000 / 63
< Current Available (mA): 500
< Current Required (mA): 134
< Extra Operating Current (mA): 0

View File

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

View File

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

View File

@@ -3,11 +3,11 @@
Primary class: SerialInterface
Install with pip: "[pip3 install meshtastic](https://pypi.org/project/meshtastic/)"
Source code on [github](https://github.com/meshtastic/python)
Source code on [github](https://github.com/meshtastic/Meshtastic-python)
properties of SerialInterface:
- localConfig - Current radio configuration and device settings, if you write to this the new settings will be applied to
- radioConfig - Current radio configuration and device settings, if you write to this the new settings will be applied to
the device.
- nodes - The database of received nodes. Includes always up-to-date location and username information for each
node in the mesh. This is a read-only datastructure.
@@ -62,48 +62,39 @@ import os
import platform
import random
import socket
import stat
import sys
import stat
import threading
import time
import traceback
import time
from datetime import datetime
from typing import *
import google.protobuf.json_format
import serial
import timeago
from dotmap import DotMap
from google.protobuf.json_format import MessageToJson
import google.protobuf.json_format
from pubsub import pub
from dotmap import DotMap
from tabulate import tabulate
from meshtastic import (
admin_pb2,
apponly_pb2,
channel_pb2,
config_pb2,
mesh_pb2,
portnums_pb2,
remote_hardware_pb2,
telemetry_pb2,
util,
)
from google.protobuf.json_format import MessageToJson
from meshtastic.util import fixme, catchAndIgnore, stripnl, DeferredExecution, Timeout
from meshtastic.node import Node
from meshtastic.util import DeferredExecution, Timeout, catchAndIgnore, fixme, stripnl
from meshtastic import (mesh_pb2, portnums_pb2, apponly_pb2, admin_pb2,
environmental_measurement_pb2, remote_hardware_pb2,
channel_pb2, radioconfig_pb2, util)
# Note: To follow PEP224, comments should be after the module variable.
LOCAL_ADDR = "^local"
"""A special ID that means the local node"""
BROADCAST_NUM = 0xFFFFFFFF
BROADCAST_NUM = 0xffffffff
"""if using 8 bit nodenums this will be shortend on the target"""
BROADCAST_ADDR = "^all"
"""A special ID that means broadcast"""
OUR_APP_VERSION = 20300
OUR_APP_VERSION = 20200
"""The numeric buildnumber (shared with android apps) specifying the
level of device code we are guaranteed to understand
@@ -115,7 +106,6 @@ publishingThread = DeferredExecution("publishing")
class ResponseHandler(NamedTuple):
"""A pending response callback, waiting for a response to one of our messages"""
# requestId: int - used only as a key
callback: Callable
# FIXME, add timestamp and age out old requests
@@ -123,7 +113,6 @@ class ResponseHandler(NamedTuple):
class KnownProtocol(NamedTuple):
"""Used to automatically decode known protocol payloads"""
name: str
# portnum: int, now a key
# If set, will be called to prase as a protocol buffer
@@ -140,7 +129,7 @@ def _onTextReceive(iface, asDict):
#
# Usually btw this problem is caused by apps sending binary data but setting the payload type to
# text.
logging.debug(f"in _onTextReceive() asDict:{asDict}")
logging.debug(f'in _onTextReceive() asDict:{asDict}')
try:
asBytes = asDict["decoded"]["payload"]
asDict["decoded"]["text"] = asBytes.decode("utf-8")
@@ -151,22 +140,22 @@ def _onTextReceive(iface, asDict):
def _onPositionReceive(iface, asDict):
"""Special auto parsing for received messages"""
logging.debug(f"in _onPositionReceive() asDict:{asDict}")
if "decoded" in asDict:
if "position" in asDict["decoded"] and "from" in asDict:
logging.debug(f'in _onPositionReceive() asDict:{asDict}')
if 'decoded' in asDict:
if 'position' in asDict['decoded'] and 'from' in asDict:
p = asDict["decoded"]["position"]
logging.debug(f"p:{p}")
logging.debug(f'p:{p}')
p = iface._fixupPosition(p)
logging.debug(f"after fixup p:{p}")
logging.debug(f'after fixup p:{p}')
# update node DB as needed
iface._getOrCreateByNum(asDict["from"])["position"] = p
def _onNodeInfoReceive(iface, asDict):
"""Special auto parsing for received messages"""
logging.debug(f"in _onNodeInfoReceive() asDict:{asDict}")
if "decoded" in asDict:
if "user" in asDict["decoded"] and "from" in asDict:
logging.debug(f'in _onNodeInfoReceive() asDict:{asDict}')
if 'decoded' in asDict:
if 'user' in asDict['decoded'] and 'from' in asDict:
p = asDict["decoded"]["user"]
# decode user protobufs and update nodedb, provide decoded version as "position" in the published msg
# update node DB as needed
@@ -187,25 +176,12 @@ def _receiveInfoUpdate(iface, asDict):
"""Well known message payloads can register decoders for automatic protobuf parsing"""
protocols = {
portnums_pb2.PortNum.TEXT_MESSAGE_APP: KnownProtocol(
"text", onReceive=_onTextReceive
),
portnums_pb2.PortNum.POSITION_APP: KnownProtocol(
"position", mesh_pb2.Position, _onPositionReceive
),
portnums_pb2.PortNum.NODEINFO_APP: KnownProtocol(
"user", mesh_pb2.User, _onNodeInfoReceive
),
portnums_pb2.PortNum.TEXT_MESSAGE_APP: KnownProtocol("text", onReceive=_onTextReceive),
portnums_pb2.PortNum.POSITION_APP: KnownProtocol("position", mesh_pb2.Position, _onPositionReceive),
portnums_pb2.PortNum.NODEINFO_APP: KnownProtocol("user", mesh_pb2.User, _onNodeInfoReceive),
portnums_pb2.PortNum.ADMIN_APP: KnownProtocol("admin", admin_pb2.AdminMessage),
portnums_pb2.PortNum.ROUTING_APP: KnownProtocol("routing", mesh_pb2.Routing),
portnums_pb2.PortNum.TELEMETRY_APP: KnownProtocol(
"telemetry", telemetry_pb2.Telemetry
),
portnums_pb2.PortNum.ENVIRONMENTAL_MEASUREMENT_APP: KnownProtocol("environmental", environmental_measurement_pb2.EnvironmentalMeasurement),
portnums_pb2.PortNum.REMOTE_HARDWARE_APP: KnownProtocol(
"remotehw", remote_hardware_pb2.HardwareMessage
),
portnums_pb2.PortNum.SIMULATOR_APP: KnownProtocol("simulator", mesh_pb2.Compressed),
portnums_pb2.PortNum.TRACEROUTE_APP: KnownProtocol(
"traceroute", mesh_pb2.RouteDiscovery
),
"remotehw", remote_hardware_pb2.HardwareMessage)
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/admin.proto
"""Generated protocol buffer code."""
# source: admin.proto
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
@@ -12,56 +11,325 @@ from google.protobuf import symbol_database as _symbol_database
_sym_db = _symbol_database.Default()
from meshtastic import channel_pb2 as meshtastic_dot_channel__pb2
from meshtastic import config_pb2 as meshtastic_dot_config__pb2
from meshtastic import connection_status_pb2 as meshtastic_dot_connection__status__pb2
from meshtastic import deviceonly_pb2 as meshtastic_dot_deviceonly__pb2
from meshtastic import mesh_pb2 as meshtastic_dot_mesh__pb2
from meshtastic import module_config_pb2 as meshtastic_dot_module__config__pb2
from . import channel_pb2 as channel__pb2
from . import mesh_pb2 as mesh__pb2
from . import radioconfig_pb2 as radioconfig__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16meshtastic/admin.proto\x1a\x18meshtastic/channel.proto\x1a\x17meshtastic/config.proto\x1a\"meshtastic/connection_status.proto\x1a\x1bmeshtastic/deviceonly.proto\x1a\x15meshtastic/mesh.proto\x1a\x1emeshtastic/module_config.proto\"\xdc\r\n\x0c\x41\x64minMessage\x12\x1d\n\x13get_channel_request\x18\x01 \x01(\rH\x00\x12(\n\x14get_channel_response\x18\x02 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_owner_request\x18\x03 \x01(\x08H\x00\x12#\n\x12get_owner_response\x18\x04 \x01(\x0b\x32\x05.UserH\x00\x12\x36\n\x12get_config_request\x18\x05 \x01(\x0e\x32\x18.AdminMessage.ConfigTypeH\x00\x12&\n\x13get_config_response\x18\x06 \x01(\x0b\x32\x07.ConfigH\x00\x12\x43\n\x19get_module_config_request\x18\x07 \x01(\x0e\x32\x1e.AdminMessage.ModuleConfigTypeH\x00\x12\x33\n\x1aget_module_config_response\x18\x08 \x01(\x0b\x32\r.ModuleConfigH\x00\x12\x34\n*get_canned_message_module_messages_request\x18\n \x01(\x08H\x00\x12\x35\n+get_canned_message_module_messages_response\x18\x0b \x01(\tH\x00\x12%\n\x1bget_device_metadata_request\x18\x0c \x01(\x08H\x00\x12\x37\n\x1cget_device_metadata_response\x18\r \x01(\x0b\x32\x0f.DeviceMetadataH\x00\x12\x1e\n\x14get_ringtone_request\x18\x0e \x01(\x08H\x00\x12\x1f\n\x15get_ringtone_response\x18\x0f \x01(\tH\x00\x12.\n$get_device_connection_status_request\x18\x10 \x01(\x08H\x00\x12H\n%get_device_connection_status_response\x18\x11 \x01(\x0b\x32\x17.DeviceConnectionStatusH\x00\x12&\n\x0cset_ham_mode\x18\x12 \x01(\x0b\x32\x0e.HamParametersH\x00\x12/\n%get_node_remote_hardware_pins_request\x18\x13 \x01(\x08H\x00\x12Q\n&get_node_remote_hardware_pins_response\x18\x14 \x01(\x0b\x32\x1f.NodeRemoteHardwarePinsResponseH\x00\x12\x1a\n\tset_owner\x18 \x01(\x0b\x32\x05.UserH\x00\x12\x1f\n\x0bset_channel\x18! \x01(\x0b\x32\x08.ChannelH\x00\x12\x1d\n\nset_config\x18\" \x01(\x0b\x32\x07.ConfigH\x00\x12*\n\x11set_module_config\x18# \x01(\x0b\x32\r.ModuleConfigH\x00\x12,\n\"set_canned_message_module_messages\x18$ \x01(\tH\x00\x12\x1e\n\x14set_ringtone_message\x18% \x01(\tH\x00\x12\x1d\n\x13\x62\x65gin_edit_settings\x18@ \x01(\x08H\x00\x12\x1e\n\x14\x63ommit_edit_settings\x18\x41 \x01(\x08H\x00\x12\x1c\n\x12reboot_ota_seconds\x18_ \x01(\x05H\x00\x12\x18\n\x0e\x65xit_simulator\x18` \x01(\x08H\x00\x12\x18\n\x0ereboot_seconds\x18\x61 \x01(\x05H\x00\x12\x1a\n\x10shutdown_seconds\x18\x62 \x01(\x05H\x00\x12\x17\n\rfactory_reset\x18\x63 \x01(\x05H\x00\x12\x16\n\x0cnodedb_reset\x18\x64 \x01(\x05H\x00\"\x95\x01\n\nConfigType\x12\x11\n\rDEVICE_CONFIG\x10\x00\x12\x13\n\x0fPOSITION_CONFIG\x10\x01\x12\x10\n\x0cPOWER_CONFIG\x10\x02\x12\x12\n\x0eNETWORK_CONFIG\x10\x03\x12\x12\n\x0e\x44ISPLAY_CONFIG\x10\x04\x12\x0f\n\x0bLORA_CONFIG\x10\x05\x12\x14\n\x10\x42LUETOOTH_CONFIG\x10\x06\"\xd3\x01\n\x10ModuleConfigType\x12\x0f\n\x0bMQTT_CONFIG\x10\x00\x12\x11\n\rSERIAL_CONFIG\x10\x01\x12\x13\n\x0f\x45XTNOTIF_CONFIG\x10\x02\x12\x17\n\x13STOREFORWARD_CONFIG\x10\x03\x12\x14\n\x10RANGETEST_CONFIG\x10\x04\x12\x14\n\x10TELEMETRY_CONFIG\x10\x05\x12\x14\n\x10\x43\x41NNEDMSG_CONFIG\x10\x06\x12\x10\n\x0c\x41UDIO_CONFIG\x10\x07\x12\x19\n\x15REMOTEHARDWARE_CONFIG\x10\x08\x42\x11\n\x0fpayload_variant\"[\n\rHamParameters\x12\x11\n\tcall_sign\x18\x01 \x01(\t\x12\x10\n\x08tx_power\x18\x02 \x01(\x05\x12\x11\n\tfrequency\x18\x03 \x01(\x02\x12\x12\n\nshort_name\x18\x04 \x01(\t\"[\n\x1eNodeRemoteHardwarePinsResponse\x12\x39\n\x19node_remote_hardware_pins\x18\x01 \x03(\x0b\x32\x16.NodeRemoteHardwarePinB`\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
DESCRIPTOR = _descriptor.FileDescriptor(
name='admin.proto',
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\013AdminProtosH\003Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\x0b\x61\x64min.proto\x1a\rchannel.proto\x1a\nmesh.proto\x1a\x11radioconfig.proto\"\xa1\x08\n\x0c\x41\x64minMessage\x12!\n\tset_radio\x18\x01 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1a\n\tset_owner\x18\x02 \x01(\x0b\x32\x05.UserH\x00\x12\x1f\n\x0bset_channel\x18\x03 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_radio_request\x18\x04 \x01(\x08H\x00\x12*\n\x12get_radio_response\x18\x05 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1d\n\x13get_channel_request\x18\x06 \x01(\rH\x00\x12(\n\x14get_channel_response\x18\x07 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_owner_request\x18\x08 \x01(\x08H\x00\x12#\n\x12get_owner_response\x18\t \x01(\x0b\x32\x05.UserH\x00\x12\x1d\n\x13\x63onfirm_set_channel\x18 \x01(\x08H\x00\x12\x1b\n\x11\x63onfirm_set_radio\x18! \x01(\x08H\x00\x12\x18\n\x0e\x65xit_simulator\x18\" \x01(\x08H\x00\x12\x18\n\x0ereboot_seconds\x18# \x01(\x05H\x00\x12\x31\n\'get_canned_message_plugin_part1_request\x18$ \x01(\x08H\x00\x12\x32\n(get_canned_message_plugin_part1_response\x18% \x01(\tH\x00\x12\x31\n\'get_canned_message_plugin_part2_request\x18& \x01(\x08H\x00\x12\x32\n(get_canned_message_plugin_part2_response\x18\' \x01(\tH\x00\x12\x31\n\'get_canned_message_plugin_part3_request\x18( \x01(\x08H\x00\x12\x32\n(get_canned_message_plugin_part3_response\x18) \x01(\tH\x00\x12\x31\n\'get_canned_message_plugin_part4_request\x18* \x01(\x08H\x00\x12\x32\n(get_canned_message_plugin_part4_response\x18+ \x01(\tH\x00\x12)\n\x1fset_canned_message_plugin_part1\x18, \x01(\tH\x00\x12)\n\x1fset_canned_message_plugin_part2\x18- \x01(\tH\x00\x12)\n\x1fset_canned_message_plugin_part3\x18. \x01(\tH\x00\x12)\n\x1fset_canned_message_plugin_part4\x18/ \x01(\tH\x00\x12\x1a\n\x10shutdown_seconds\x18\x33 \x01(\x05H\x00\x42\t\n\x07variantBG\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
,
dependencies=[channel__pb2.DESCRIPTOR,mesh__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,])
_ADMINMESSAGE = DESCRIPTOR.message_types_by_name['AdminMessage']
_HAMPARAMETERS = DESCRIPTOR.message_types_by_name['HamParameters']
_NODEREMOTEHARDWAREPINSRESPONSE = DESCRIPTOR.message_types_by_name['NodeRemoteHardwarePinsResponse']
_ADMINMESSAGE_CONFIGTYPE = _ADMINMESSAGE.enum_types_by_name['ConfigType']
_ADMINMESSAGE_MODULECONFIGTYPE = _ADMINMESSAGE.enum_types_by_name['ModuleConfigType']
_ADMINMESSAGE = _descriptor.Descriptor(
name='AdminMessage',
full_name='AdminMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='set_radio', full_name='AdminMessage.set_radio', index=0,
number=1, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='set_owner', full_name='AdminMessage.set_owner', index=1,
number=2, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='set_channel', full_name='AdminMessage.set_channel', index=2,
number=3, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='get_radio_request', full_name='AdminMessage.get_radio_request', index=3,
number=4, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='get_radio_response', full_name='AdminMessage.get_radio_response', index=4,
number=5, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='get_channel_request', full_name='AdminMessage.get_channel_request', index=5,
number=6, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='get_channel_response', full_name='AdminMessage.get_channel_response', index=6,
number=7, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='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=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=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=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,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='get_canned_message_plugin_part1_request', full_name='AdminMessage.get_canned_message_plugin_part1_request', index=13,
number=36, 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_canned_message_plugin_part1_response', full_name='AdminMessage.get_canned_message_plugin_part1_response', index=14,
number=37, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='get_canned_message_plugin_part2_request', full_name='AdminMessage.get_canned_message_plugin_part2_request', index=15,
number=38, 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_canned_message_plugin_part2_response', full_name='AdminMessage.get_canned_message_plugin_part2_response', index=16,
number=39, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='get_canned_message_plugin_part3_request', full_name='AdminMessage.get_canned_message_plugin_part3_request', index=17,
number=40, 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_canned_message_plugin_part3_response', full_name='AdminMessage.get_canned_message_plugin_part3_response', index=18,
number=41, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='get_canned_message_plugin_part4_request', full_name='AdminMessage.get_canned_message_plugin_part4_request', index=19,
number=42, 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_canned_message_plugin_part4_response', full_name='AdminMessage.get_canned_message_plugin_part4_response', index=20,
number=43, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='set_canned_message_plugin_part1', full_name='AdminMessage.set_canned_message_plugin_part1', index=21,
number=44, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='set_canned_message_plugin_part2', full_name='AdminMessage.set_canned_message_plugin_part2', index=22,
number=45, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='set_canned_message_plugin_part3', full_name='AdminMessage.set_canned_message_plugin_part3', index=23,
number=46, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='set_canned_message_plugin_part4', full_name='AdminMessage.set_canned_message_plugin_part4', index=24,
number=47, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='shutdown_seconds', full_name='AdminMessage.shutdown_seconds', index=25,
number=51, type=5, cpp_type=1, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
_descriptor.OneofDescriptor(
name='variant', full_name='AdminMessage.variant',
index=0, containing_type=None, fields=[]),
],
serialized_start=62,
serialized_end=1119,
)
_ADMINMESSAGE.fields_by_name['set_radio'].message_type = radioconfig__pb2._RADIOCONFIG
_ADMINMESSAGE.fields_by_name['set_owner'].message_type = mesh__pb2._USER
_ADMINMESSAGE.fields_by_name['set_channel'].message_type = channel__pb2._CHANNEL
_ADMINMESSAGE.fields_by_name['get_radio_response'].message_type = radioconfig__pb2._RADIOCONFIG
_ADMINMESSAGE.fields_by_name['get_channel_response'].message_type = channel__pb2._CHANNEL
_ADMINMESSAGE.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']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['set_owner'])
_ADMINMESSAGE.fields_by_name['set_owner'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['set_channel'])
_ADMINMESSAGE.fields_by_name['set_channel'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_radio_request'])
_ADMINMESSAGE.fields_by_name['get_radio_request'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_radio_response'])
_ADMINMESSAGE.fields_by_name['get_radio_response'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_channel_request'])
_ADMINMESSAGE.fields_by_name['get_channel_request'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_channel_response'])
_ADMINMESSAGE.fields_by_name['get_channel_response'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['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']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['confirm_set_radio'])
_ADMINMESSAGE.fields_by_name['confirm_set_radio'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['exit_simulator'])
_ADMINMESSAGE.fields_by_name['exit_simulator'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['reboot_seconds'])
_ADMINMESSAGE.fields_by_name['reboot_seconds'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_canned_message_plugin_part1_request'])
_ADMINMESSAGE.fields_by_name['get_canned_message_plugin_part1_request'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_canned_message_plugin_part1_response'])
_ADMINMESSAGE.fields_by_name['get_canned_message_plugin_part1_response'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_canned_message_plugin_part2_request'])
_ADMINMESSAGE.fields_by_name['get_canned_message_plugin_part2_request'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_canned_message_plugin_part2_response'])
_ADMINMESSAGE.fields_by_name['get_canned_message_plugin_part2_response'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_canned_message_plugin_part3_request'])
_ADMINMESSAGE.fields_by_name['get_canned_message_plugin_part3_request'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_canned_message_plugin_part3_response'])
_ADMINMESSAGE.fields_by_name['get_canned_message_plugin_part3_response'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_canned_message_plugin_part4_request'])
_ADMINMESSAGE.fields_by_name['get_canned_message_plugin_part4_request'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_canned_message_plugin_part4_response'])
_ADMINMESSAGE.fields_by_name['get_canned_message_plugin_part4_response'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['set_canned_message_plugin_part1'])
_ADMINMESSAGE.fields_by_name['set_canned_message_plugin_part1'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['set_canned_message_plugin_part2'])
_ADMINMESSAGE.fields_by_name['set_canned_message_plugin_part2'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['set_canned_message_plugin_part3'])
_ADMINMESSAGE.fields_by_name['set_canned_message_plugin_part3'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['set_canned_message_plugin_part4'])
_ADMINMESSAGE.fields_by_name['set_canned_message_plugin_part4'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['shutdown_seconds'])
_ADMINMESSAGE.fields_by_name['shutdown_seconds'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
DESCRIPTOR.message_types_by_name['AdminMessage'] = _ADMINMESSAGE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
AdminMessage = _reflection.GeneratedProtocolMessageType('AdminMessage', (_message.Message,), {
'DESCRIPTOR' : _ADMINMESSAGE,
'__module__' : 'meshtastic.admin_pb2'
'__module__' : 'admin_pb2'
# @@protoc_insertion_point(class_scope:AdminMessage)
})
_sym_db.RegisterMessage(AdminMessage)
HamParameters = _reflection.GeneratedProtocolMessageType('HamParameters', (_message.Message,), {
'DESCRIPTOR' : _HAMPARAMETERS,
'__module__' : 'meshtastic.admin_pb2'
# @@protoc_insertion_point(class_scope:HamParameters)
})
_sym_db.RegisterMessage(HamParameters)
NodeRemoteHardwarePinsResponse = _reflection.GeneratedProtocolMessageType('NodeRemoteHardwarePinsResponse', (_message.Message,), {
'DESCRIPTOR' : _NODEREMOTEHARDWAREPINSRESPONSE,
'__module__' : 'meshtastic.admin_pb2'
# @@protoc_insertion_point(class_scope:NodeRemoteHardwarePinsResponse)
})
_sym_db.RegisterMessage(NodeRemoteHardwarePinsResponse)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\013AdminProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_ADMINMESSAGE._serialized_start=198
_ADMINMESSAGE._serialized_end=1954
_ADMINMESSAGE_CONFIGTYPE._serialized_start=1572
_ADMINMESSAGE_CONFIGTYPE._serialized_end=1721
_ADMINMESSAGE_MODULECONFIGTYPE._serialized_start=1724
_ADMINMESSAGE_MODULECONFIGTYPE._serialized_end=1935
_HAMPARAMETERS._serialized_start=1956
_HAMPARAMETERS._serialized_end=2047
_NODEREMOTEHARDWAREPINSRESPONSE._serialized_start=2049
_NODEREMOTEHARDWAREPINSRESPONSE._serialized_end=2140
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

View File

@@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/apponly.proto
"""Generated protocol buffer code."""
# source: apponly.proto
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
@@ -12,26 +11,62 @@ from google.protobuf import symbol_database as _symbol_database
_sym_db = _symbol_database.Default()
from meshtastic import channel_pb2 as meshtastic_dot_channel__pb2
from meshtastic import config_pb2 as meshtastic_dot_config__pb2
from . import channel_pb2 as channel__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18meshtastic/apponly.proto\x1a\x18meshtastic/channel.proto\x1a\x17meshtastic/config.proto\"Y\n\nChannelSet\x12\"\n\x08settings\x18\x01 \x03(\x0b\x32\x10.ChannelSettings\x12\'\n\x0blora_config\x18\x02 \x01(\x0b\x32\x12.Config.LoRaConfigBb\n\x13\x63om.geeksville.meshB\rAppOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
DESCRIPTOR = _descriptor.FileDescriptor(
name='apponly.proto',
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\rAppOnlyProtosH\003Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\rapponly.proto\x1a\rchannel.proto\"0\n\nChannelSet\x12\"\n\x08settings\x18\x01 \x03(\x0b\x32\x10.ChannelSettingsBI\n\x13\x63om.geeksville.meshB\rAppOnlyProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
,
dependencies=[channel__pb2.DESCRIPTOR,])
_CHANNELSET = DESCRIPTOR.message_types_by_name['ChannelSet']
_CHANNELSET = _descriptor.Descriptor(
name='ChannelSet',
full_name='ChannelSet',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='settings', full_name='ChannelSet.settings', index=0,
number=1, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=32,
serialized_end=80,
)
_CHANNELSET.fields_by_name['settings'].message_type = channel__pb2._CHANNELSETTINGS
DESCRIPTOR.message_types_by_name['ChannelSet'] = _CHANNELSET
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
ChannelSet = _reflection.GeneratedProtocolMessageType('ChannelSet', (_message.Message,), {
'DESCRIPTOR' : _CHANNELSET,
'__module__' : 'meshtastic.apponly_pb2'
'__module__' : 'apponly_pb2'
# @@protoc_insertion_point(class_scope:ChannelSet)
})
_sym_db.RegisterMessage(ChannelSet)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\rAppOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_CHANNELSET._serialized_start=79
_CHANNELSET._serialized_end=168
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

View File

@@ -6,11 +6,12 @@ import platform
from meshtastic.mesh_interface import MeshInterface
from meshtastic.util import our_exit
if platform.system() == "Linux":
if platform.system() == 'Linux':
# pylint: disable=E0401
import pygatt
# Our standard BLE characteristics
TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7"
FROMRADIO_UUID = "8ba2bcc2-ee02-4a55-a531-c525c5e454d5"
@@ -21,7 +22,7 @@ class BLEInterface(MeshInterface):
"""A not quite ready - FIXME - BLE interface to devices"""
def __init__(self, address, noProto=False, debugOut=None):
if platform.system() != "Linux":
if platform.system() != 'Linux':
our_exit("Linux is the only platform with experimental BLE support.", 1)
self.address = address
if not noProto:
@@ -38,7 +39,7 @@ class BLEInterface(MeshInterface):
self._readFromRadio() # read the initial responses
def handle_data(handle, data): # pylint: disable=W0613
def handle_data(handle, data): # pylint: disable=W0613
self._handleFromRadio(data)
if self.device:
@@ -46,7 +47,7 @@ class BLEInterface(MeshInterface):
def _sendToRadioImpl(self, toRadio):
"""Send a ToRadio protobuf to the device"""
# logging.debug(f"Sending: {stripnl(toRadio)}")
#logging.debug(f"Sending: {stripnl(toRadio)}")
b = toRadio.SerializeToString()
self.device.char_write(TORADIO_UUID, b)

View File

@@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/cannedmessages.proto
"""Generated protocol buffer code."""
# source: cannedmessages.proto
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
@@ -14,22 +13,78 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fmeshtastic/cannedmessages.proto\"-\n\x19\x43\x61nnedMessageModuleConfig\x12\x10\n\x08messages\x18\x01 \x01(\tBn\n\x13\x63om.geeksville.meshB\x19\x43\x61nnedMessageConfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
DESCRIPTOR = _descriptor.FileDescriptor(
name='cannedmessages.proto',
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\031CannedMessageConfigProtosH\003Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\x14\x63\x61nnedmessages.proto\"w\n\x19\x43\x61nnedMessagePluginConfig\x12\x15\n\rmessagesPart1\x18\x0b \x01(\t\x12\x15\n\rmessagesPart2\x18\x0c \x01(\t\x12\x15\n\rmessagesPart3\x18\r \x01(\t\x12\x15\n\rmessagesPart4\x18\x0e \x01(\tBU\n\x13\x63om.geeksville.meshB\x19\x43\x61nnedMessageConfigProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
)
_CANNEDMESSAGEMODULECONFIG = DESCRIPTOR.message_types_by_name['CannedMessageModuleConfig']
CannedMessageModuleConfig = _reflection.GeneratedProtocolMessageType('CannedMessageModuleConfig', (_message.Message,), {
'DESCRIPTOR' : _CANNEDMESSAGEMODULECONFIG,
'__module__' : 'meshtastic.cannedmessages_pb2'
# @@protoc_insertion_point(class_scope:CannedMessageModuleConfig)
_CANNEDMESSAGEPLUGINCONFIG = _descriptor.Descriptor(
name='CannedMessagePluginConfig',
full_name='CannedMessagePluginConfig',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='messagesPart1', full_name='CannedMessagePluginConfig.messagesPart1', index=0,
number=11, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='messagesPart2', full_name='CannedMessagePluginConfig.messagesPart2', index=1,
number=12, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='messagesPart3', full_name='CannedMessagePluginConfig.messagesPart3', index=2,
number=13, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='messagesPart4', full_name='CannedMessagePluginConfig.messagesPart4', index=3,
number=14, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=24,
serialized_end=143,
)
DESCRIPTOR.message_types_by_name['CannedMessagePluginConfig'] = _CANNEDMESSAGEPLUGINCONFIG
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
CannedMessagePluginConfig = _reflection.GeneratedProtocolMessageType('CannedMessagePluginConfig', (_message.Message,), {
'DESCRIPTOR' : _CANNEDMESSAGEPLUGINCONFIG,
'__module__' : 'cannedmessages_pb2'
# @@protoc_insertion_point(class_scope:CannedMessagePluginConfig)
})
_sym_db.RegisterMessage(CannedMessageModuleConfig)
_sym_db.RegisterMessage(CannedMessagePluginConfig)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\031CannedMessageConfigProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_CANNEDMESSAGEMODULECONFIG._serialized_start=35
_CANNEDMESSAGEMODULECONFIG._serialized_end=80
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

View File

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

View File

@@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/clientonly.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
from meshtastic import localonly_pb2 as meshtastic_dot_localonly__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bmeshtastic/clientonly.proto\x1a\x1ameshtastic/localonly.proto\"\xf7\x01\n\rDeviceProfile\x12\x16\n\tlong_name\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x17\n\nshort_name\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0b\x63hannel_url\x18\x03 \x01(\tH\x02\x88\x01\x01\x12!\n\x06\x63onfig\x18\x04 \x01(\x0b\x32\x0c.LocalConfigH\x03\x88\x01\x01\x12.\n\rmodule_config\x18\x05 \x01(\x0b\x32\x12.LocalModuleConfigH\x04\x88\x01\x01\x42\x0c\n\n_long_nameB\r\n\x0b_short_nameB\x0e\n\x0c_channel_urlB\t\n\x07_configB\x10\n\x0e_module_configBe\n\x13\x63om.geeksville.meshB\x10\x43lientOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_DEVICEPROFILE = DESCRIPTOR.message_types_by_name['DeviceProfile']
DeviceProfile = _reflection.GeneratedProtocolMessageType('DeviceProfile', (_message.Message,), {
'DESCRIPTOR' : _DEVICEPROFILE,
'__module__' : 'meshtastic.clientonly_pb2'
# @@protoc_insertion_point(class_scope:DeviceProfile)
})
_sym_db.RegisterMessage(DeviceProfile)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\020ClientOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_DEVICEPROFILE._serialized_start=60
_DEVICEPROFILE._serialized_end=307
# @@protoc_insertion_point(module_scope)

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,85 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/connection_status.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/connection_status.proto\"\x85\x02\n\x16\x44\x65viceConnectionStatus\x12(\n\x04wifi\x18\x01 \x01(\x0b\x32\x15.WifiConnectionStatusH\x00\x88\x01\x01\x12\x30\n\x08\x65thernet\x18\x02 \x01(\x0b\x32\x19.EthernetConnectionStatusH\x01\x88\x01\x01\x12\x32\n\tbluetooth\x18\x03 \x01(\x0b\x32\x1a.BluetoothConnectionStatusH\x02\x88\x01\x01\x12,\n\x06serial\x18\x04 \x01(\x0b\x32\x17.SerialConnectionStatusH\x03\x88\x01\x01\x42\x07\n\x05_wifiB\x0b\n\t_ethernetB\x0c\n\n_bluetoothB\t\n\x07_serial\"\\\n\x14WifiConnectionStatus\x12(\n\x06status\x18\x01 \x01(\x0b\x32\x18.NetworkConnectionStatus\x12\x0c\n\x04ssid\x18\x02 \x01(\t\x12\x0c\n\x04rssi\x18\x03 \x01(\x05\"D\n\x18\x45thernetConnectionStatus\x12(\n\x06status\x18\x01 \x01(\x0b\x32\x18.NetworkConnectionStatus\"{\n\x17NetworkConnectionStatus\x12\x12\n\nip_address\x18\x01 \x01(\x07\x12\x14\n\x0cis_connected\x18\x02 \x01(\x08\x12\x19\n\x11is_mqtt_connected\x18\x03 \x01(\x08\x12\x1b\n\x13is_syslog_connected\x18\x04 \x01(\x08\"L\n\x19\x42luetoothConnectionStatus\x12\x0b\n\x03pin\x18\x01 \x01(\r\x12\x0c\n\x04rssi\x18\x02 \x01(\x05\x12\x14\n\x0cis_connected\x18\x03 \x01(\x08\"<\n\x16SerialConnectionStatus\x12\x0c\n\x04\x62\x61ud\x18\x01 \x01(\r\x12\x14\n\x0cis_connected\x18\x02 \x01(\x08\x42\x65\n\x13\x63om.geeksville.meshB\x10\x43onnStatusProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_DEVICECONNECTIONSTATUS = DESCRIPTOR.message_types_by_name['DeviceConnectionStatus']
_WIFICONNECTIONSTATUS = DESCRIPTOR.message_types_by_name['WifiConnectionStatus']
_ETHERNETCONNECTIONSTATUS = DESCRIPTOR.message_types_by_name['EthernetConnectionStatus']
_NETWORKCONNECTIONSTATUS = DESCRIPTOR.message_types_by_name['NetworkConnectionStatus']
_BLUETOOTHCONNECTIONSTATUS = DESCRIPTOR.message_types_by_name['BluetoothConnectionStatus']
_SERIALCONNECTIONSTATUS = DESCRIPTOR.message_types_by_name['SerialConnectionStatus']
DeviceConnectionStatus = _reflection.GeneratedProtocolMessageType('DeviceConnectionStatus', (_message.Message,), {
'DESCRIPTOR' : _DEVICECONNECTIONSTATUS,
'__module__' : 'meshtastic.connection_status_pb2'
# @@protoc_insertion_point(class_scope:DeviceConnectionStatus)
})
_sym_db.RegisterMessage(DeviceConnectionStatus)
WifiConnectionStatus = _reflection.GeneratedProtocolMessageType('WifiConnectionStatus', (_message.Message,), {
'DESCRIPTOR' : _WIFICONNECTIONSTATUS,
'__module__' : 'meshtastic.connection_status_pb2'
# @@protoc_insertion_point(class_scope:WifiConnectionStatus)
})
_sym_db.RegisterMessage(WifiConnectionStatus)
EthernetConnectionStatus = _reflection.GeneratedProtocolMessageType('EthernetConnectionStatus', (_message.Message,), {
'DESCRIPTOR' : _ETHERNETCONNECTIONSTATUS,
'__module__' : 'meshtastic.connection_status_pb2'
# @@protoc_insertion_point(class_scope:EthernetConnectionStatus)
})
_sym_db.RegisterMessage(EthernetConnectionStatus)
NetworkConnectionStatus = _reflection.GeneratedProtocolMessageType('NetworkConnectionStatus', (_message.Message,), {
'DESCRIPTOR' : _NETWORKCONNECTIONSTATUS,
'__module__' : 'meshtastic.connection_status_pb2'
# @@protoc_insertion_point(class_scope:NetworkConnectionStatus)
})
_sym_db.RegisterMessage(NetworkConnectionStatus)
BluetoothConnectionStatus = _reflection.GeneratedProtocolMessageType('BluetoothConnectionStatus', (_message.Message,), {
'DESCRIPTOR' : _BLUETOOTHCONNECTIONSTATUS,
'__module__' : 'meshtastic.connection_status_pb2'
# @@protoc_insertion_point(class_scope:BluetoothConnectionStatus)
})
_sym_db.RegisterMessage(BluetoothConnectionStatus)
SerialConnectionStatus = _reflection.GeneratedProtocolMessageType('SerialConnectionStatus', (_message.Message,), {
'DESCRIPTOR' : _SERIALCONNECTIONSTATUS,
'__module__' : 'meshtastic.connection_status_pb2'
# @@protoc_insertion_point(class_scope:SerialConnectionStatus)
})
_sym_db.RegisterMessage(SerialConnectionStatus)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\020ConnStatusProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_DEVICECONNECTIONSTATUS._serialized_start=39
_DEVICECONNECTIONSTATUS._serialized_end=300
_WIFICONNECTIONSTATUS._serialized_start=302
_WIFICONNECTIONSTATUS._serialized_end=394
_ETHERNETCONNECTIONSTATUS._serialized_start=396
_ETHERNETCONNECTIONSTATUS._serialized_end=464
_NETWORKCONNECTIONSTATUS._serialized_start=466
_NETWORKCONNECTIONSTATUS._serialized_end=589
_BLUETOOTHCONNECTIONSTATUS._serialized_start=591
_BLUETOOTHCONNECTIONSTATUS._serialized_end=667
_SERIALCONNECTIONSTATUS._serialized_start=669
_SERIALCONNECTIONSTATUS._serialized_end=729
# @@protoc_insertion_point(module_scope)

View File

@@ -1,37 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/device_metadata.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
from meshtastic import config_pb2 as meshtastic_dot_config__pb2
from meshtastic import mesh_pb2 as meshtastic_dot_mesh__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n meshtastic/device_metadata.proto\x1a\x17meshtastic/config.proto\x1a\x15meshtastic/mesh.proto\"\xfc\x01\n\x0e\x44\x65viceMetadata\x12\x18\n\x10\x66irmware_version\x18\x01 \x01(\t\x12\x1c\n\x14\x64\x65vice_state_version\x18\x02 \x01(\r\x12\x13\n\x0b\x63\x61nShutdown\x18\x03 \x01(\x08\x12\x0f\n\x07hasWifi\x18\x04 \x01(\x08\x12\x14\n\x0chasBluetooth\x18\x05 \x01(\x08\x12\x13\n\x0bhasEthernet\x18\x06 \x01(\x08\x12\'\n\x04role\x18\x07 \x01(\x0e\x32\x19.Config.DeviceConfig.Role\x12\x16\n\x0eposition_flags\x18\x08 \x01(\r\x12 \n\x08hw_model\x18\t \x01(\x0e\x32\x0e.HardwareModelBi\n\x13\x63om.geeksville.meshB\x14\x44\x65viceMetadataProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_DEVICEMETADATA = DESCRIPTOR.message_types_by_name['DeviceMetadata']
DeviceMetadata = _reflection.GeneratedProtocolMessageType('DeviceMetadata', (_message.Message,), {
'DESCRIPTOR' : _DEVICEMETADATA,
'__module__' : 'meshtastic.device_metadata_pb2'
# @@protoc_insertion_point(class_scope:DeviceMetadata)
})
_sym_db.RegisterMessage(DeviceMetadata)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\024DeviceMetadataProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_DEVICEMETADATA._serialized_start=85
_DEVICEMETADATA._serialized_end=337
# @@protoc_insertion_point(module_scope)

View File

@@ -1,10 +1,8 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/deviceonly.proto
"""Generated protocol buffer code."""
from google.protobuf.internal import enum_type_wrapper
# source: deviceonly.proto
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
@@ -13,65 +11,245 @@ from google.protobuf import symbol_database as _symbol_database
_sym_db = _symbol_database.Default()
from meshtastic import channel_pb2 as meshtastic_dot_channel__pb2
from meshtastic import localonly_pb2 as meshtastic_dot_localonly__pb2
from meshtastic import mesh_pb2 as meshtastic_dot_mesh__pb2
from meshtastic import module_config_pb2 as meshtastic_dot_module__config__pb2
from . import channel_pb2 as channel__pb2
from . import mesh_pb2 as mesh__pb2
from . import radioconfig_pb2 as radioconfig__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bmeshtastic/deviceonly.proto\x1a\x18meshtastic/channel.proto\x1a\x1ameshtastic/localonly.proto\x1a\x15meshtastic/mesh.proto\x1a\x1emeshtastic/module_config.proto\"\xbd\x02\n\x0b\x44\x65viceState\x12\x1c\n\x07my_node\x18\x02 \x01(\x0b\x32\x0b.MyNodeInfo\x12\x14\n\x05owner\x18\x03 \x01(\x0b\x32\x05.User\x12\x1a\n\x07node_db\x18\x04 \x03(\x0b\x32\t.NodeInfo\x12\"\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x0b.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12$\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x0b.MeshPacket\x12\x0f\n\x07no_save\x18\t \x01(\x08\x12\x15\n\rdid_gps_reset\x18\x0b \x01(\x08\x12 \n\x0brx_waypoint\x18\x0c \x01(\x0b\x32\x0b.MeshPacket\x12\x39\n\x19node_remote_hardware_pins\x18\r \x03(\x0b\x32\x16.NodeRemoteHardwarePin\":\n\x0b\x43hannelFile\x12\x1a\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x08.Channel\x12\x0f\n\x07version\x18\x02 \x01(\r\"\xf6\x01\n\x08OEMStore\x12\x16\n\x0eoem_icon_width\x18\x01 \x01(\r\x12\x17\n\x0foem_icon_height\x18\x02 \x01(\r\x12\x15\n\roem_icon_bits\x18\x03 \x01(\x0c\x12\x1e\n\x08oem_font\x18\x04 \x01(\x0e\x32\x0c.ScreenFonts\x12\x10\n\x08oem_text\x18\x05 \x01(\t\x12\x13\n\x0boem_aes_key\x18\x06 \x01(\x0c\x12&\n\x10oem_local_config\x18\x07 \x01(\x0b\x32\x0c.LocalConfig\x12\x33\n\x17oem_local_module_config\x18\x08 \x01(\x0b\x32\x12.LocalModuleConfig\"J\n\x15NodeRemoteHardwarePin\x12\x10\n\x08node_num\x18\x01 \x01(\r\x12\x1f\n\x03pin\x18\x02 \x01(\x0b\x32\x12.RemoteHardwarePin*>\n\x0bScreenFonts\x12\x0e\n\nFONT_SMALL\x10\x00\x12\x0f\n\x0b\x46ONT_MEDIUM\x10\x01\x12\x0e\n\nFONT_LARGE\x10\x02\x42_\n\x13\x63om.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_SCREENFONTS = DESCRIPTOR.enum_types_by_name['ScreenFonts']
ScreenFonts = enum_type_wrapper.EnumTypeWrapper(_SCREENFONTS)
FONT_SMALL = 0
FONT_MEDIUM = 1
FONT_LARGE = 2
DESCRIPTOR = _descriptor.FileDescriptor(
name='deviceonly.proto',
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\nDeviceOnlyH\003Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\x10\x64\x65viceonly.proto\x1a\rchannel.proto\x1a\nmesh.proto\x1a\x11radioconfig.proto\"\x80\x01\n\x11LegacyRadioConfig\x12\x39\n\x0bpreferences\x18\x01 \x01(\x0b\x32$.LegacyRadioConfig.LegacyPreferences\x1a\x30\n\x11LegacyPreferences\x12\x1b\n\x06region\x18\x0f \x01(\x0e\x32\x0b.RegionCode\"\x8f\x02\n\x0b\x44\x65viceState\x12\'\n\x0blegacyRadio\x18\x01 \x01(\x0b\x32\x12.LegacyRadioConfig\x12\x1c\n\x07my_node\x18\x02 \x01(\x0b\x32\x0b.MyNodeInfo\x12\x14\n\x05owner\x18\x03 \x01(\x0b\x32\x05.User\x12\x1a\n\x07node_db\x18\x04 \x03(\x0b\x32\t.NodeInfo\x12\"\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x0b.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12$\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x0b.MeshPacket\x12\x0f\n\x07no_save\x18\t \x01(\x08\x12\x15\n\rdid_gps_reset\x18\x0b \x01(\x08J\x04\x08\x0c\x10\r\")\n\x0b\x43hannelFile\x12\x1a\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x08.ChannelBF\n\x13\x63om.geeksville.meshB\nDeviceOnlyH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
,
dependencies=[channel__pb2.DESCRIPTOR,mesh__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,])
_DEVICESTATE = DESCRIPTOR.message_types_by_name['DeviceState']
_CHANNELFILE = DESCRIPTOR.message_types_by_name['ChannelFile']
_OEMSTORE = DESCRIPTOR.message_types_by_name['OEMStore']
_NODEREMOTEHARDWAREPIN = DESCRIPTOR.message_types_by_name['NodeRemoteHardwarePin']
_LEGACYRADIOCONFIG_LEGACYPREFERENCES = _descriptor.Descriptor(
name='LegacyPreferences',
full_name='LegacyRadioConfig.LegacyPreferences',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='region', full_name='LegacyRadioConfig.LegacyPreferences.region', index=0,
number=15, type=14, cpp_type=8, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=147,
serialized_end=195,
)
_LEGACYRADIOCONFIG = _descriptor.Descriptor(
name='LegacyRadioConfig',
full_name='LegacyRadioConfig',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='preferences', full_name='LegacyRadioConfig.preferences', index=0,
number=1, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[_LEGACYRADIOCONFIG_LEGACYPREFERENCES, ],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=67,
serialized_end=195,
)
_DEVICESTATE = _descriptor.Descriptor(
name='DeviceState',
full_name='DeviceState',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='legacyRadio', full_name='DeviceState.legacyRadio', index=0,
number=1, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='my_node', full_name='DeviceState.my_node', index=1,
number=2, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='owner', full_name='DeviceState.owner', index=2,
number=3, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='node_db', full_name='DeviceState.node_db', index=3,
number=4, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='receive_queue', full_name='DeviceState.receive_queue', index=4,
number=5, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='version', full_name='DeviceState.version', index=5,
number=8, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='rx_text_message', full_name='DeviceState.rx_text_message', index=6,
number=7, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='no_save', full_name='DeviceState.no_save', index=7,
number=9, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='did_gps_reset', full_name='DeviceState.did_gps_reset', index=8,
number=11, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=198,
serialized_end=469,
)
_CHANNELFILE = _descriptor.Descriptor(
name='ChannelFile',
full_name='ChannelFile',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='channels', full_name='ChannelFile.channels', index=0,
number=1, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=471,
serialized_end=512,
)
_LEGACYRADIOCONFIG_LEGACYPREFERENCES.fields_by_name['region'].enum_type = radioconfig__pb2._REGIONCODE
_LEGACYRADIOCONFIG_LEGACYPREFERENCES.containing_type = _LEGACYRADIOCONFIG
_LEGACYRADIOCONFIG.fields_by_name['preferences'].message_type = _LEGACYRADIOCONFIG_LEGACYPREFERENCES
_DEVICESTATE.fields_by_name['legacyRadio'].message_type = _LEGACYRADIOCONFIG
_DEVICESTATE.fields_by_name['my_node'].message_type = mesh__pb2._MYNODEINFO
_DEVICESTATE.fields_by_name['owner'].message_type = mesh__pb2._USER
_DEVICESTATE.fields_by_name['node_db'].message_type = mesh__pb2._NODEINFO
_DEVICESTATE.fields_by_name['receive_queue'].message_type = mesh__pb2._MESHPACKET
_DEVICESTATE.fields_by_name['rx_text_message'].message_type = mesh__pb2._MESHPACKET
_CHANNELFILE.fields_by_name['channels'].message_type = channel__pb2._CHANNEL
DESCRIPTOR.message_types_by_name['LegacyRadioConfig'] = _LEGACYRADIOCONFIG
DESCRIPTOR.message_types_by_name['DeviceState'] = _DEVICESTATE
DESCRIPTOR.message_types_by_name['ChannelFile'] = _CHANNELFILE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
LegacyRadioConfig = _reflection.GeneratedProtocolMessageType('LegacyRadioConfig', (_message.Message,), {
'LegacyPreferences' : _reflection.GeneratedProtocolMessageType('LegacyPreferences', (_message.Message,), {
'DESCRIPTOR' : _LEGACYRADIOCONFIG_LEGACYPREFERENCES,
'__module__' : 'deviceonly_pb2'
# @@protoc_insertion_point(class_scope:LegacyRadioConfig.LegacyPreferences)
})
,
'DESCRIPTOR' : _LEGACYRADIOCONFIG,
'__module__' : 'deviceonly_pb2'
# @@protoc_insertion_point(class_scope:LegacyRadioConfig)
})
_sym_db.RegisterMessage(LegacyRadioConfig)
_sym_db.RegisterMessage(LegacyRadioConfig.LegacyPreferences)
DeviceState = _reflection.GeneratedProtocolMessageType('DeviceState', (_message.Message,), {
'DESCRIPTOR' : _DEVICESTATE,
'__module__' : 'meshtastic.deviceonly_pb2'
'__module__' : 'deviceonly_pb2'
# @@protoc_insertion_point(class_scope:DeviceState)
})
_sym_db.RegisterMessage(DeviceState)
ChannelFile = _reflection.GeneratedProtocolMessageType('ChannelFile', (_message.Message,), {
'DESCRIPTOR' : _CHANNELFILE,
'__module__' : 'meshtastic.deviceonly_pb2'
'__module__' : 'deviceonly_pb2'
# @@protoc_insertion_point(class_scope:ChannelFile)
})
_sym_db.RegisterMessage(ChannelFile)
OEMStore = _reflection.GeneratedProtocolMessageType('OEMStore', (_message.Message,), {
'DESCRIPTOR' : _OEMSTORE,
'__module__' : 'meshtastic.deviceonly_pb2'
# @@protoc_insertion_point(class_scope:OEMStore)
})
_sym_db.RegisterMessage(OEMStore)
NodeRemoteHardwarePin = _reflection.GeneratedProtocolMessageType('NodeRemoteHardwarePin', (_message.Message,), {
'DESCRIPTOR' : _NODEREMOTEHARDWAREPIN,
'__module__' : 'meshtastic.deviceonly_pb2'
# @@protoc_insertion_point(class_scope:NodeRemoteHardwarePin)
})
_sym_db.RegisterMessage(NodeRemoteHardwarePin)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_SCREENFONTS._serialized_start=845
_SCREENFONTS._serialized_end=907
_DEVICESTATE._serialized_start=141
_DEVICESTATE._serialized_end=458
_CHANNELFILE._serialized_start=460
_CHANNELFILE._serialized_end=518
_OEMSTORE._serialized_start=521
_OEMSTORE._serialized_end=767
_NODEREMOTEHARDWAREPIN._serialized_start=769
_NODEREMOTEHARDWAREPIN._serialized_end=843
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

View File

@@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: environmental_measurement.proto
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='environmental_measurement.proto',
package='',
syntax='proto3',
serialized_options=b'Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\x1f\x65nvironmental_measurement.proto\"\xa1\x01\n\x18\x45nvironmentalMeasurement\x12\x13\n\x0btemperature\x18\x01 \x01(\x02\x12\x19\n\x11relative_humidity\x18\x02 \x01(\x02\x12\x1b\n\x13\x62\x61rometric_pressure\x18\x03 \x01(\x02\x12\x16\n\x0egas_resistance\x18\x04 \x01(\x02\x12\x0f\n\x07voltage\x18\x05 \x01(\x02\x12\x0f\n\x07\x63urrent\x18\x06 \x01(\x02\x42#Z!github.com/meshtastic/gomeshprotob\x06proto3'
)
_ENVIRONMENTALMEASUREMENT = _descriptor.Descriptor(
name='EnvironmentalMeasurement',
full_name='EnvironmentalMeasurement',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='temperature', full_name='EnvironmentalMeasurement.temperature', index=0,
number=1, type=2, cpp_type=6, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='relative_humidity', full_name='EnvironmentalMeasurement.relative_humidity', index=1,
number=2, type=2, cpp_type=6, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='barometric_pressure', full_name='EnvironmentalMeasurement.barometric_pressure', index=2,
number=3, type=2, cpp_type=6, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='gas_resistance', full_name='EnvironmentalMeasurement.gas_resistance', index=3,
number=4, type=2, cpp_type=6, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='voltage', full_name='EnvironmentalMeasurement.voltage', index=4,
number=5, type=2, cpp_type=6, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='current', full_name='EnvironmentalMeasurement.current', index=5,
number=6, type=2, cpp_type=6, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=36,
serialized_end=197,
)
DESCRIPTOR.message_types_by_name['EnvironmentalMeasurement'] = _ENVIRONMENTALMEASUREMENT
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
EnvironmentalMeasurement = _reflection.GeneratedProtocolMessageType('EnvironmentalMeasurement', (_message.Message,), {
'DESCRIPTOR' : _ENVIRONMENTALMEASUREMENT,
'__module__' : 'environmental_measurement_pb2'
# @@protoc_insertion_point(class_scope:EnvironmentalMeasurement)
})
_sym_db.RegisterMessage(EnvironmentalMeasurement)
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

View File

@@ -8,10 +8,8 @@
"""
class Globals:
"""Globals class is a Singleton."""
__instance = None
@staticmethod

View File

@@ -1,47 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/localonly.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
from meshtastic import config_pb2 as meshtastic_dot_config__pb2
from meshtastic import module_config_pb2 as meshtastic_dot_module__config__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ameshtastic/localonly.proto\x1a\x17meshtastic/config.proto\x1a\x1emeshtastic/module_config.proto\"\xb0\x02\n\x0bLocalConfig\x12$\n\x06\x64\x65vice\x18\x01 \x01(\x0b\x32\x14.Config.DeviceConfig\x12(\n\x08position\x18\x02 \x01(\x0b\x32\x16.Config.PositionConfig\x12\"\n\x05power\x18\x03 \x01(\x0b\x32\x13.Config.PowerConfig\x12&\n\x07network\x18\x04 \x01(\x0b\x32\x15.Config.NetworkConfig\x12&\n\x07\x64isplay\x18\x05 \x01(\x0b\x32\x15.Config.DisplayConfig\x12 \n\x04lora\x18\x06 \x01(\x0b\x32\x12.Config.LoRaConfig\x12*\n\tbluetooth\x18\x07 \x01(\x0b\x32\x17.Config.BluetoothConfig\x12\x0f\n\x07version\x18\x08 \x01(\r\"\x81\x04\n\x11LocalModuleConfig\x12&\n\x04mqtt\x18\x01 \x01(\x0b\x32\x18.ModuleConfig.MQTTConfig\x12*\n\x06serial\x18\x02 \x01(\x0b\x32\x1a.ModuleConfig.SerialConfig\x12G\n\x15\x65xternal_notification\x18\x03 \x01(\x0b\x32(.ModuleConfig.ExternalNotificationConfig\x12\x37\n\rstore_forward\x18\x04 \x01(\x0b\x32 .ModuleConfig.StoreForwardConfig\x12\x31\n\nrange_test\x18\x05 \x01(\x0b\x32\x1d.ModuleConfig.RangeTestConfig\x12\x30\n\ttelemetry\x18\x06 \x01(\x0b\x32\x1d.ModuleConfig.TelemetryConfig\x12\x39\n\x0e\x63\x61nned_message\x18\x07 \x01(\x0b\x32!.ModuleConfig.CannedMessageConfig\x12(\n\x05\x61udio\x18\t \x01(\x0b\x32\x19.ModuleConfig.AudioConfig\x12;\n\x0fremote_hardware\x18\n \x01(\x0b\x32\".ModuleConfig.RemoteHardwareConfig\x12\x0f\n\x07version\x18\x08 \x01(\rBd\n\x13\x63om.geeksville.meshB\x0fLocalOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_LOCALCONFIG = DESCRIPTOR.message_types_by_name['LocalConfig']
_LOCALMODULECONFIG = DESCRIPTOR.message_types_by_name['LocalModuleConfig']
LocalConfig = _reflection.GeneratedProtocolMessageType('LocalConfig', (_message.Message,), {
'DESCRIPTOR' : _LOCALCONFIG,
'__module__' : 'meshtastic.localonly_pb2'
# @@protoc_insertion_point(class_scope:LocalConfig)
})
_sym_db.RegisterMessage(LocalConfig)
LocalModuleConfig = _reflection.GeneratedProtocolMessageType('LocalModuleConfig', (_message.Message,), {
'DESCRIPTOR' : _LOCALMODULECONFIG,
'__module__' : 'meshtastic.localonly_pb2'
# @@protoc_insertion_point(class_scope:LocalModuleConfig)
})
_sym_db.RegisterMessage(LocalModuleConfig)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\017LocalOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_LOCALCONFIG._serialized_start=88
_LOCALCONFIG._serialized_end=392
_LOCALMODULECONFIG._serialized_start=395
_LOCALMODULECONFIG._serialized_end=908
# @@protoc_insertion_point(module_scope)

View File

@@ -1,42 +1,25 @@
"""Mesh Interface class
"""
import collections
import logging
import random
import sys
import threading
import random
import time
import json
from datetime import datetime
import logging
from typing import AnyStr
import google.protobuf.json_format
import threading
from datetime import datetime
import timeago
from google.protobuf.json_format import MessageToJson
from pubsub import pub
from tabulate import tabulate
import meshtastic.node
from meshtastic import mesh_pb2, portnums_pb2
from meshtastic.__init__ import (
BROADCAST_ADDR,
BROADCAST_NUM,
LOCAL_ADDR,
OUR_APP_VERSION,
ResponseHandler,
protocols,
publishingThread,
)
from meshtastic.util import (
Acknowledgment,
Timeout,
convert_mac_addr,
our_exit,
remove_keys_from_dict,
stripnl,
)
import google.protobuf.json_format
from pubsub import pub
from google.protobuf.json_format import MessageToJson
import meshtastic.node
from meshtastic import portnums_pb2, mesh_pb2
from meshtastic.util import stripnl, Timeout, our_exit, remove_keys_from_dict, convert_mac_addr
from meshtastic.__init__ import LOCAL_ADDR, BROADCAST_NUM, BROADCAST_ADDR, ResponseHandler, publishingThread, OUR_APP_VERSION, protocols
class MeshInterface:
"""Interface class for meshtastic devices
@@ -62,20 +45,16 @@ class MeshInterface:
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
)
self.failure = None # If we've encountered a fatal exception it will be kept here
self._timeout = Timeout()
self._acknowledgment = Acknowledgment()
self.heartbeatTimer = None
random.seed() # FIXME, we should not clobber the random seedval here, instead tell user they must call it
self.currentPacketId = random.randint(0, 0xFFFFFFFF)
self.currentPacketId = random.randint(0, 0xffffffff)
self.nodesByNum = None
self.configId = None
self.gotResponse = False # used in gpio read
self.mask = None # used in gpio read and gpio watch
self.queueStatus = None
self.queue = collections.OrderedDict()
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"""
@@ -89,154 +68,114 @@ class MeshInterface:
def __exit__(self, exc_type, exc_value, traceback):
if exc_type is not None and exc_value is not None:
logging.error(
f"An exception of type {exc_type} with value {exc_value} has occurred"
)
logging.error(f'An exception of type {exc_type} with value {exc_value} has occurred')
if traceback is not None:
logging.error(f"Traceback: {traceback}")
logging.error(f'Traceback: {traceback}')
self.close()
def showInfo(self, file=sys.stdout): # pylint: disable=W0613
def showInfo(self, file=sys.stdout): # pylint: disable=W0613
"""Show human readable summary about this object"""
owner = f"Owner: {self.getLongName()} ({self.getShortName()})"
myinfo = ""
myinfo = ''
if self.myInfo:
myinfo = f"\nMy info: {stripnl(MessageToJson(self.myInfo))}"
mesh = "\n\nNodes in mesh: "
nodes = {}
mesh = "\nNodes in mesh:"
nodes = ""
if self.nodes:
for n in self.nodes.values():
# when the TBeam is first booted, it sometimes shows the raw data
# so, we will just remove any raw keys
keys_to_remove = ("raw", "decoded", "payload")
keys_to_remove = ('raw', 'decoded', 'payload')
n2 = remove_keys_from_dict(keys_to_remove, n)
# if we have 'macaddr', re-format it
if "macaddr" in n2["user"]:
val = n2["user"]["macaddr"]
if 'macaddr' in n2['user']:
val = n2['user']['macaddr']
# decode the base64 value
addr = convert_mac_addr(val)
n2["user"]["macaddr"] = addr
n2['user']['macaddr'] = addr
# use id as dictionary key for correct json format in list of nodes
nodeid = n2['user']['id']
n2['user'].pop('id')
nodes[nodeid] = n2
infos = owner + myinfo + mesh + json.dumps(nodes)
nodes = nodes + f" {stripnl(n2)}"
infos = owner + myinfo + mesh + nodes
print(infos)
return infos
def showNodes(self, includeSelf=True, file=sys.stdout): # pylint: disable=W0613
def showNodes(self, includeSelf=True, file=sys.stdout): # pylint: disable=W0613
"""Show table summary of nodes in mesh"""
def formatFloat(value, precision=2, unit=""):
def formatFloat(value, precision=2, unit=''):
"""Format a float value with precsion."""
return f"{value:.{precision}f}{unit}" if value else None
return f'{value:.{precision}f}{unit}' if value else None
def getLH(ts):
"""Format last heard"""
return (
datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S") if ts else None
)
return datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S') if ts else None
def getTimeAgo(ts):
"""Format how long ago have we heard from this node (aka timeago)."""
return (
timeago.format(datetime.fromtimestamp(ts), datetime.now())
if ts
else None
)
return timeago.format(datetime.fromtimestamp(ts), datetime.now()) if ts else None
rows = []
if self.nodes:
logging.debug(f"self.nodes:{self.nodes}")
logging.debug(f'self.nodes:{self.nodes}')
for node in self.nodes.values():
if not includeSelf and node["num"] == self.localNode.nodeNum:
if not includeSelf and node['num'] == self.localNode.nodeNum:
continue
row = {"N": 0}
user = node.get("user")
user = node.get('user')
if user:
row.update(
{
"User": user.get("longName", "N/A"),
"AKA": user.get("shortName", "N/A"),
"ID": user["id"],
}
)
row.update({
"User": user['longName'],
"AKA": user['shortName'],
"ID": user['id'],
})
pos = node.get("position")
pos = node.get('position')
if pos:
row.update(
{
"Latitude": formatFloat(pos.get("latitude"), 4, "°"),
"Longitude": formatFloat(pos.get("longitude"), 4, "°"),
"Altitude": formatFloat(pos.get("altitude"), 0, " m"),
}
)
row.update({
"Latitude": formatFloat(pos.get("latitude"), 4, "°"),
"Longitude": formatFloat(pos.get("longitude"), 4, "°"),
"Altitude": formatFloat(pos.get("altitude"), 0, " m"),
"Battery": formatFloat(pos.get("batteryLevel"), 2, "%"),
})
metrics = node.get("deviceMetrics")
if metrics:
batteryLevel = metrics.get("batteryLevel")
if batteryLevel is not None:
if batteryLevel == 0:
batteryString = "Powered"
else:
batteryString = str(batteryLevel) + "%"
row.update({"Battery": batteryString})
row.update(
{
"Channel util.": formatFloat(
metrics.get("channelUtilization"), 2, "%"
),
"Tx air util.": formatFloat(
metrics.get("airUtilTx"), 2, "%"
),
}
)
row.update(
{
"SNR": formatFloat(node.get("snr"), 2, " dB"),
"LastHeard": getLH(node.get("lastHeard")),
"Since": getTimeAgo(node.get("lastHeard")),
}
)
row.update({
"SNR": formatFloat(node.get("snr"), 2, " dB"),
"LastHeard": getLH(node.get("lastHeard")),
"Since": getTimeAgo(node.get("lastHeard")),
})
rows.append(row)
rows.sort(key=lambda r: r.get("LastHeard") or "0000", reverse=True)
rows.sort(key=lambda r: r.get('LastHeard') or '0000', reverse=True)
for i, row in enumerate(rows):
row["N"] = i + 1
row['N'] = i+1
table = tabulate(rows, headers="keys", missingval="N/A", tablefmt="fancy_grid")
table = tabulate(rows, headers='keys', missingval='N/A', tablefmt='fancy_grid')
print(table)
return table
def getNode(self, nodeId, requestChannels=True):
def getNode(self, nodeId):
"""Return a node object which contains device settings and channel info"""
if nodeId in (LOCAL_ADDR, BROADCAST_ADDR):
return self.localNode
else:
n = meshtastic.node.Node(self, nodeId)
# Only request device settings and channel info when necessary
if requestChannels:
logging.debug("About to requestChannels")
n.requestChannels()
if not n.waitForConfig():
our_exit("Error: Timed out waiting for channels")
logging.debug("About to requestConfig")
n.requestConfig()
if not n.waitForConfig():
our_exit("Error: Timed out waiting for node config")
return n
def sendText(
self,
text: AnyStr,
destinationId=BROADCAST_ADDR,
wantAck=False,
wantResponse=False,
onResponse=None,
channelIndex=0,
):
def sendText(self, text: AnyStr,
destinationId=BROADCAST_ADDR,
wantAck=False,
wantResponse=False,
hopLimit=None,
onResponse=None,
channelIndex=0):
"""Send a utf8 string to some other node, if the node has a display it
will also be shown on the device.
@@ -256,27 +195,23 @@ 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,
wantResponse=wantResponse,
onResponse=onResponse,
channelIndex=channelIndex,
)
return self.sendData(text.encode("utf-8"), destinationId,
portNum=portnums_pb2.PortNum.TEXT_MESSAGE_APP,
wantAck=wantAck,
wantResponse=wantResponse,
hopLimit=hopLimit,
onResponse=onResponse,
channelIndex=channelIndex)
def sendData(
self,
data,
destinationId=BROADCAST_ADDR,
portNum=portnums_pb2.PortNum.PRIVATE_APP,
wantAck=False,
wantResponse=False,
onResponse=None,
channelIndex=0,
):
def sendData(self, data, destinationId=BROADCAST_ADDR,
portNum=portnums_pb2.PortNum.PRIVATE_APP, wantAck=False,
wantResponse=False,
hopLimit=None,
onResponse=None,
channelIndex=0):
"""Send a data packet to some other node
Keyword Arguments:
@@ -299,21 +234,19 @@ 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
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}"
)
logging.debug(f"mesh_pb2.Constants.DATA_PAYLOAD_LEN: {mesh_pb2.Constants.DATA_PAYLOAD_LEN}")
if len(data) > mesh_pb2.Constants.DATA_PAYLOAD_LEN:
raise Exception("Data payload too big")
if (
portNum == portnums_pb2.PortNum.UNKNOWN_APP
): # we are now more strict wrt port numbers
if portNum == portnums_pb2.PortNum.UNKNOWN_APP: # we are now more strict wrt port numbers
our_exit("Warning: A non-zero port number must be specified")
meshPacket = mesh_pb2.MeshPacket()
@@ -325,19 +258,12 @@ class MeshInterface:
if onResponse is not None:
self._addResponseHandler(meshPacket.id, onResponse)
p = self._sendPacket(meshPacket, destinationId, wantAck=wantAck)
p = self._sendPacket(meshPacket, destinationId,
wantAck=wantAck, hopLimit=hopLimit)
return p
def sendPosition(
self,
latitude=0.0,
longitude=0.0,
altitude=0,
timeSec=0,
destinationId=BROADCAST_ADDR,
wantAck=False,
wantResponse=False,
):
def sendPosition(self, latitude=0.0, longitude=0.0, altitude=0, timeSec=0,
destinationId=BROADCAST_ADDR, wantAck=False, wantResponse=False):
"""
Send a position packet to some other node (normally a broadcast)
@@ -352,72 +278,43 @@ 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}")
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}")
logging.debug(f'p.longitude_i:{p.longitude_i}')
if altitude != 0:
p.altitude = int(altitude)
logging.debug(f"p.altitude:{p.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}")
logging.debug(f'p.time:{p.time}')
return self.sendData(
p,
destinationId,
portNum=portnums_pb2.PortNum.POSITION_APP,
wantAck=wantAck,
wantResponse=wantResponse,
)
def sendTraceRoute(self, dest, hopLimit):
"""Send the trace route"""
r = mesh_pb2.RouteDiscovery()
self.sendData(
r,
destinationId=dest,
portNum=portnums_pb2.PortNum.TRACEROUTE_APP,
wantResponse=True,
onResponse=self.onResponseTraceRoute,
)
# extend timeout based on number of nodes, limit by configured hopLimit
waitFactor = min(len(self.nodes) - 1, hopLimit)
self.waitForTraceRoute(waitFactor)
def onResponseTraceRoute(self, p):
"""on response for trace route"""
routeDiscovery = mesh_pb2.RouteDiscovery()
routeDiscovery.ParseFromString(p["decoded"]["payload"])
asDict = google.protobuf.json_format.MessageToDict(routeDiscovery)
print("Route traced:")
routeStr = self._nodeNumToId(p["to"])
if "route" in asDict:
for nodeNum in asDict["route"]:
routeStr += " --> " + self._nodeNumToId(nodeNum)
routeStr += " --> " + self._nodeNumToId(p["from"])
print(routeStr)
self._acknowledgment.receivedTraceRoute = True
return self.sendData(p, destinationId,
portNum=portnums_pb2.PortNum.POSITION_APP,
wantAck=wantAck,
wantResponse=wantResponse)
def _addResponseHandler(self, requestId, callback):
self.responseHandlers[requestId] = ResponseHandler(callback)
def _sendPacket(self, meshPacket, destinationId=BROADCAST_ADDR, wantAck=False):
def _sendPacket(self, meshPacket,
destinationId=BROADCAST_ADDR,
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:
if(self.myInfo is not None and destinationId != self.myInfo.my_node_num):
self._waitConnected()
toRadio = mesh_pb2.ToRadio()
@@ -442,14 +339,12 @@ class MeshInterface:
node = self.nodes.get(destinationId)
if not node:
our_exit(f"Warning: NodeId {destinationId} not found in DB")
nodeNum = node["num"]
nodeNum = node['num']
else:
logging.warning("Warning: There were no self.nodes.")
meshPacket.to = nodeNum
meshPacket.want_ack = wantAck
loraConfig = getattr(self.localNode.localConfig, "lora")
hopLimit = getattr(loraConfig, "hop_limit")
meshPacket.hop_limit = hopLimit
# if the user hasn't set an ID for this packet (likely and recommended),
@@ -459,9 +354,7 @@ class MeshInterface:
toRadio.packet.CopyFrom(meshPacket)
if self.noProto:
logging.warning(
f"Not sending packet because protocol use is disabled by 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)
@@ -469,51 +362,36 @@ class MeshInterface:
def waitForConfig(self):
"""Block until radio config is received. Returns True if config has been received."""
success = (
self._timeout.waitForSet(self, attrs=("myInfo", "nodes"))
and self.localNode.waitForConfig()
)
success = self._timeout.waitForSet(self, attrs=('myInfo', 'nodes')) and self.localNode.waitForConfig()
if not success:
raise Exception("Timed out waiting for interface config")
def waitForAckNak(self):
"""Wait for the ack/nak"""
success = self._timeout.waitForAckNak(self._acknowledgment)
if not success:
raise Exception("Timed out waiting for an acknowledgment")
def waitForTraceRoute(self, waitFactor):
"""Wait for trace route"""
success = self._timeout.waitForTraceRoute(waitFactor, self._acknowledgment)
if not success:
raise Exception("Timed out waiting for traceroute")
def getMyNodeInfo(self):
"""Get info about my node."""
if self.myInfo is None:
return None
logging.debug(f"self.nodesByNum:{self.nodesByNum}")
logging.debug(f'self.nodesByNum:{self.nodesByNum}')
return self.nodesByNum.get(self.myInfo.my_node_num)
def getMyUser(self):
"""Get user"""
nodeInfo = self.getMyNodeInfo()
if nodeInfo is not None:
return nodeInfo.get("user")
return nodeInfo.get('user')
return None
def getLongName(self):
"""Get long name"""
user = self.getMyUser()
if user is not None:
return user.get("longName", None)
return user.get('longName', None)
return None
def getShortName(self):
"""Get short name"""
user = self.getMyUser()
if user is not None:
return user.get("shortName", None)
return user.get('shortName', None)
return None
def _waitConnected(self, timeout=15.0):
@@ -532,23 +410,20 @@ class MeshInterface:
if self.currentPacketId is None:
raise Exception("Not connected yet, can not generate packet")
else:
self.currentPacketId = (self.currentPacketId + 1) & 0xFFFFFFFF
self.currentPacketId = (self.currentPacketId + 1) & 0xffffffff
return self.currentPacketId
def _disconnected(self):
"""Called by subclasses to tell clients this interface has disconnected"""
self.isConnected.clear()
publishingThread.queueWork(
lambda: pub.sendMessage("meshtastic.connection.lost", interface=self)
)
publishingThread.queueWork(lambda: pub.sendMessage("meshtastic.connection.lost", interface=self))
def _startHeartbeat(self):
"""We need to send a heartbeat message to the device every X seconds"""
def callback():
self.heartbeatTimer = None
prefs = self.localNode.localConfig
i = prefs.power.ls_secs / 2
prefs = self.localNode.radioConfig.preferences
i = prefs.phone_timeout_secs / 2
logging.debug(f"Sending heartbeat, interval {i}")
if i != 0:
self.heartbeatTimer = threading.Timer(i, callback)
@@ -559,18 +434,15 @@ class MeshInterface:
callback() # run our periodic callback now, it will make another timer if necessary
def _connected(self):
"""Called by this class to tell clients we are now fully connected to a node"""
"""Called by this class to tell clients we are now fully connected to a node
"""
# (because I'm lazy) _connected might be called when remote Node
# objects complete their config reads, don't generate redundant isConnected
# for the local interface
if not self.isConnected.is_set():
self.isConnected.set()
self._startHeartbeat()
publishingThread.queueWork(
lambda: pub.sendMessage(
"meshtastic.connection.established", interface=self
)
)
publishingThread.queueWork(lambda: pub.sendMessage("meshtastic.connection.established", interface=self))
def _startConfig(self):
"""Start device packets flowing"""
@@ -579,7 +451,7 @@ class MeshInterface:
self.nodesByNum = {} # nodes keyed by nodenum
startConfig = mesh_pb2.ToRadio()
self.configId = random.randint(0, 0xFFFFFFFF)
self.configId = random.randint(0, 0xffffffff)
startConfig.want_config_id = self.configId
self._sendToRadio(startConfig)
@@ -589,65 +461,13 @@ class MeshInterface:
m.disconnect = True
self._sendToRadio(m)
def _queueHasFreeSpace(self):
# We never got queueStatus, maybe the firmware is old
if self.queueStatus is None:
return True
return self.queueStatus.free > 0
def _queueClaim(self):
if self.queueStatus is None:
return
self.queueStatus.free -= 1
def _sendToRadio(self, toRadio):
"""Send a ToRadio protobuf to the device"""
if self.noProto:
logging.warning(
f"Not sending packet because protocol use is disabled by noProto"
)
logging.warning(f"Not sending packet because protocol use is disabled by noProto")
else:
# logging.debug(f"Sending toRadio: {stripnl(toRadio)}")
if not toRadio.HasField("packet"):
# not a meshpacket -- send immediately, give queue a chance,
# this makes heartbeat trigger queue
self._sendToRadioImpl(toRadio)
else:
# meshpacket -- queue
self.queue[toRadio.packet.id] = toRadio
resentQueue = collections.OrderedDict()
while self.queue:
# logging.warn("queue: " + " ".join(f'{k:08x}' for k in self.queue))
while not self._queueHasFreeSpace():
logging.debug("Waiting for free space in TX Queue")
time.sleep(0.5)
try:
toResend = self.queue.popitem(last=False)
except KeyError:
break
packetId, packet = toResend
# logging.warn(f"packet: {packetId:08x} {packet}")
resentQueue[packetId] = packet
if packet is False:
continue
self._queueClaim()
if packet != toRadio:
logging.debug(f"Resending packet ID {packetId:08x} {packet}")
self._sendToRadioImpl(packet)
# logging.warn("resentQueue: " + " ".join(f'{k:08x}' for k in resentQueue))
for packetId, packet in resentQueue.items():
if (
self.queue.pop(packetId, False) is False
): # Packet got acked under us
logging.debug(f"packet {packetId:08x} got acked under us")
continue
if packet:
self.queue[packetId] = packet
# logging.warn("queue + resentQueue: " + " ".join(f'{k:08x}' for k in self.queue))
#logging.debug(f"Sending toRadio: {stripnl(toRadio)}")
self._sendToRadioImpl(toRadio)
def _sendToRadioImpl(self, toRadio):
"""Send a ToRadio protobuf to the device"""
@@ -658,26 +478,7 @@ class MeshInterface:
Done with initial config messages, now send regular MeshPackets
to ask for settings and channels
"""
self.localNode.requestChannels()
def _handleQueueStatusFromRadio(self, queueStatus):
self.queueStatus = queueStatus
logging.debug(
f"TX QUEUE free {queueStatus.free} of {queueStatus.maxlen}, res = {queueStatus.res}, id = {queueStatus.mesh_packet_id:08x} "
)
if queueStatus.res:
return
# logging.warn("queue: " + " ".join(f'{k:08x}' for k in self.queue))
justQueued = self.queue.pop(queueStatus.mesh_packet_id, None)
if justQueued is None and queueStatus.mesh_packet_id != 0:
self.queue[queueStatus.mesh_packet_id] = False
logging.debug(
f"Reply for unexpected packet ID {queueStatus.mesh_packet_id:08x}"
)
# logging.warn("queue: " + " ".join(f'{k:08x}' for k in self.queue))
self.localNode.requestConfig()
def _handleFromRadio(self, fromRadioBytes):
"""
@@ -686,9 +487,7 @@ class MeshInterface:
Called by subclasses."""
fromRadio = mesh_pb2.FromRadio()
fromRadio.ParseFromString(fromRadioBytes)
logging.debug(
f"in mesh_interface.py _handleFromRadio() fromRadioBytes: {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}")
if fromRadio.HasField("my_info"):
@@ -699,17 +498,13 @@ class MeshInterface:
failmsg = None
# Check for app too old
if self.myInfo.min_app_version > OUR_APP_VERSION:
failmsg = (
"This device needs a newer python client, run 'pip install --upgrade meshtastic'."
"For more information see https://tinyurl.com/5bjsxu32"
)
failmsg = "This device needs a newer python client, run 'pip install --upgrade meshtastic'."\
"For more information see https://tinyurl.com/5bjsxu32"
# check for firmware too old
if self.myInfo.max_channels == 0:
failmsg = (
"This version of meshtastic-python requires device firmware version 1.2 or later. "
"For more information see https://tinyurl.com/5bjsxu32"
)
failmsg = "This version of meshtastic-python requires device firmware version 1.2 or later. "\
"For more information see https://tinyurl.com/5bjsxu32"
if failmsg:
self.failure = Exception(failmsg)
@@ -730,77 +525,21 @@ class MeshInterface:
if "user" in node: # Some nodes might not have user/ids assigned yet
if "id" in node["user"]:
self.nodes[node["user"]["id"]] = node
publishingThread.queueWork(
lambda: pub.sendMessage(
"meshtastic.node.updated", node=node, interface=self
)
)
publishingThread.queueWork(lambda: pub.sendMessage("meshtastic.node.updated",
node=node, interface=self))
elif fromRadio.config_complete_id == self.configId:
# we ignore the config_complete_id, it is unneeded for our
# stream API fromRadio.config_complete_id
logging.debug(f"Config complete ID {self.configId}")
self._handleConfigComplete()
elif fromRadio.HasField("packet"):
self._handlePacketFromRadio(fromRadio.packet)
elif fromRadio.HasField("queueStatus"):
self._handleQueueStatusFromRadio(fromRadio.queueStatus)
elif fromRadio.rebooted:
# Tell clients the device went away. Careful not to call the overridden
# subclass version that closes the serial port
MeshInterface._disconnected(self)
self._startConfig() # redownload the node db etc...
elif fromRadio.config or fromRadio.moduleConfig:
if fromRadio.config.HasField("device"):
self.localNode.localConfig.device.CopyFrom(fromRadio.config.device)
elif fromRadio.config.HasField("position"):
self.localNode.localConfig.position.CopyFrom(fromRadio.config.position)
elif fromRadio.config.HasField("power"):
self.localNode.localConfig.power.CopyFrom(fromRadio.config.power)
elif fromRadio.config.HasField("network"):
self.localNode.localConfig.network.CopyFrom(fromRadio.config.network)
elif fromRadio.config.HasField("display"):
self.localNode.localConfig.display.CopyFrom(fromRadio.config.display)
elif fromRadio.config.HasField("lora"):
self.localNode.localConfig.lora.CopyFrom(fromRadio.config.lora)
elif fromRadio.config.HasField("bluetooth"):
self.localNode.localConfig.bluetooth.CopyFrom(
fromRadio.config.bluetooth
)
elif fromRadio.moduleConfig.HasField("mqtt"):
self.localNode.moduleConfig.mqtt.CopyFrom(fromRadio.moduleConfig.mqtt)
elif fromRadio.moduleConfig.HasField("serial"):
self.localNode.moduleConfig.serial.CopyFrom(
fromRadio.moduleConfig.serial
)
elif fromRadio.moduleConfig.HasField("external_notification"):
self.localNode.moduleConfig.external_notification.CopyFrom(
fromRadio.moduleConfig.external_notification
)
elif fromRadio.moduleConfig.HasField("range_test"):
self.localNode.moduleConfig.range_test.CopyFrom(
fromRadio.moduleConfig.range_test
)
elif fromRadio.moduleConfig.HasField("telemetry"):
self.localNode.moduleConfig.telemetry.CopyFrom(
fromRadio.moduleConfig.telemetry
)
elif fromRadio.moduleConfig.HasField("canned_message"):
self.localNode.moduleConfig.canned_message.CopyFrom(
fromRadio.moduleConfig.canned_message
)
elif fromRadio.moduleConfig.HasField("audio"):
self.localNode.moduleConfig.audio.CopyFrom(fromRadio.moduleConfig.audio)
elif fromRadio.moduleConfig.HasField("remote_hardware"):
self.localNode.moduleConfig.remote_hardware.CopyFrom(
fromRadio.moduleConfig.remote_hardware
)
else:
logging.debug("Unexpected FromRadio payload")
@@ -873,12 +612,8 @@ class MeshInterface:
# from might be missing if the nodenum was zero.
if not hack and "from" not in asDict:
asDict["from"] = 0
logging.error(
f"Device returned a packet we sent, ignoring: {stripnl(asDict)}"
)
print(
f"Error: 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
@@ -898,62 +633,60 @@ class MeshInterface:
topic = "meshtastic.receive" # Generic unknown packet type
decoded = None
portnum = portnums_pb2.PortNum.Name(portnums_pb2.PortNum.UNKNOWN_APP)
if "decoded" in asDict:
if 'decoded' in asDict:
decoded = asDict["decoded"]
# The default MessageToDict converts byte arrays into base64 strings.
# We don't want that - it messes up data payload. So slam in the correct
# byte array.
decoded["payload"] = meshPacket.decoded.payload
# UNKNOWN_APP is the default protobuf portnum value, and therefore if not
# set it will not be populated at all to make API usage easier, set
# it to prevent confusion
if "portnum" not in decoded:
decoded["portnum"] = portnum
logging.warning(f"portnum was not in decoded. Setting to:{portnum}")
else:
portnum = decoded["portnum"]
# 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 "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}")
topic = f"meshtastic.receive.data.{portnum}"
portnum = decoded["portnum"]
# decode position protobufs and update nodedb, provide decoded version
# as "position" in the published msg move the following into a 'decoders'
# API that clients could register?
portNumInt = meshPacket.decoded.portnum # we want portnum as an int
handler = protocols.get(portNumInt)
# The decoded protobuf as a dictionary (if we understand this message)
p = None
if handler is not None:
topic = f"meshtastic.receive.{handler.name}"
topic = f"meshtastic.receive.data.{portnum}"
# Convert to protobuf if possible
if handler.protobufFactory is not None:
pb = handler.protobufFactory()
pb.ParseFromString(meshPacket.decoded.payload)
p = google.protobuf.json_format.MessageToDict(pb)
asDict["decoded"][handler.name] = p
# Also provide the protobuf raw
asDict["decoded"][handler.name]["raw"] = pb
# decode position protobufs and update nodedb, provide decoded version
# as "position" in the published msg move the following into a 'decoders'
# API that clients could register?
portNumInt = meshPacket.decoded.portnum # we want portnum as an int
handler = protocols.get(portNumInt)
# The decoded protobuf as a dictionary (if we understand this message)
p = None
if handler is not None:
topic = f"meshtastic.receive.{handler.name}"
# Call specialized onReceive if necessary
if handler.onReceive is not None:
handler.onReceive(self, asDict)
# Convert to protobuf if possible
if handler.protobufFactory is not None:
pb = handler.protobufFactory()
pb.ParseFromString(meshPacket.decoded.payload)
p = google.protobuf.json_format.MessageToDict(pb)
asDict["decoded"][handler.name] = p
# Also provide the protobuf raw
asDict["decoded"][handler.name]["raw"] = pb
# Is this message in response to a request, if so, look for a handler
requestId = decoded.get("requestId")
if requestId is not None:
# We ignore ACK packets, but send NAKs and data responses to the handlers
routing = decoded.get("routing")
isAck = routing is not None and ("errorReason" not in routing)
if not isAck:
# we keep the responseHandler in dict until we get a non ack
handler = self.responseHandlers.pop(requestId, None)
if handler is not None:
if not isAck or (isAck and handler.__name__ == "onAckNak"):
handler.callback(asDict)
# Call specialized onReceive if necessary
if handler.onReceive is not None:
handler.onReceive(self, asDict)
# Is this message in response to a request, if so, look for a handler
requestId = decoded.get("requestId")
if requestId is not None:
# We ignore ACK packets, but send NAKs and data responses to the handlers
routing = decoded.get("routing")
isAck = routing is not None and ("errorReason" not in routing)
if not isAck:
# we keep the responseHandler in dict until we get a non ack
handler = self.responseHandlers.pop(requestId, None)
if handler is not None:
handler.callback(asDict)
logging.debug(f"Publishing {topic}: packet={stripnl(asDict)} ")
publishingThread.queueWork(
lambda: pub.sendMessage(topic, packet=asDict, interface=self)
)
publishingThread.queueWork(lambda: pub.sendMessage(
topic, packet=asDict, interface=self))

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -1,43 +1,36 @@
"""Node class
"""
import base64
import logging
import base64
import time
from google.protobuf.json_format import MessageToJson
from meshtastic import admin_pb2, apponly_pb2, channel_pb2, localonly_pb2, portnums_pb2
from meshtastic.util import (
Timeout,
camel_to_snake,
fromPSK,
our_exit,
pskToString,
stripnl,
)
from meshtastic import portnums_pb2, apponly_pb2, admin_pb2, channel_pb2
from meshtastic.util import pskToString, stripnl, Timeout, our_exit, fromPSK
class Node:
"""A model of a (local or remote) node in the mesh
Includes methods for localConfig, moduleConfig and channels
Includes methods for radioConfig and channels
"""
def __init__(self, iface, nodeNum, noProto=False):
"""Constructor"""
self.iface = iface
self.nodeNum = nodeNum
self.localConfig = localonly_pb2.LocalConfig()
self.moduleConfig = localonly_pb2.LocalModuleConfig()
self.radioConfig = None
self.channels = None
self._timeout = Timeout(maxSecs=300)
self.partialChannels = None
self.noProto = noProto
self.cannedPluginMessage = None
self.cannedPluginMessageMessages = None
self.ringtone = None
self.ringtonePart = None
self.cannedPluginMessagePart1 = None
self.cannedPluginMessagePart2 = None
self.cannedPluginMessagePart3 = None
self.cannedPluginMessagePart4 = None
self.gotResponse = None
@@ -45,15 +38,13 @@ class Node:
"""Show human readable description of our channels."""
print("Channels:")
if self.channels:
logging.debug(f"self.channels:{self.channels}")
logging.debug(f'self.channels:{self.channels}')
for c in self.channels:
# print('c.settings.psk:', c.settings.psk)
#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:
print(
f" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}"
)
print(f" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}")
publicURL = self.getURL(includeAll=False)
adminURL = self.getURL(includeAll=True)
print(f"\nPrimary channel URL: {publicURL}")
@@ -63,75 +54,27 @@ class Node:
def showInfo(self):
"""Show human readable description of our node"""
prefs = ""
if self.localConfig:
prefs = stripnl(MessageToJson(self.localConfig))
if self.radioConfig and self.radioConfig.preferences:
prefs = stripnl(MessageToJson(self.radioConfig.preferences))
print(f"Preferences: {prefs}\n")
prefs = ""
if self.moduleConfig:
prefs = stripnl(MessageToJson(self.moduleConfig))
print(f"Module preferences: {prefs}\n")
self.showChannels()
def requestChannels(self):
"""Send regular MeshPackets to ask channels."""
logging.debug(f"requestChannels for nodeNum:{self.nodeNum}")
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
self._requestChannel(0)
# Note: We do not get the canned plugin message, unless get_canned_message() is called
self.cannedPluginMessage = None
def onResponseRequestSettings(self, p):
"""Handle the response packets for requesting settings _requestSettings()"""
logging.debug(f"onResponseRequestSetting() p:{p}")
if "routing" in p["decoded"]:
if p["decoded"]["routing"]["errorReason"] != "NONE":
print(f'Error on response: {p["decoded"]["routing"]["errorReason"]}')
self.iface._acknowledgment.receivedNak = True
else:
self.iface._acknowledgment.receivedAck = True
print("")
adminMessage = p["decoded"]["admin"]
if "getConfigResponse" in adminMessage:
resp = adminMessage["getConfigResponse"]
field = list(resp.keys())[0]
config_type = self.localConfig.DESCRIPTOR.fields_by_name.get(
camel_to_snake(field)
)
config_values = getattr(self.localConfig, config_type.name)
elif "getModuleConfigResponse" in adminMessage:
resp = adminMessage["getModuleConfigResponse"]
field = list(resp.keys())[0]
config_type = self.moduleConfig.DESCRIPTOR.fields_by_name.get(
camel_to_snake(field)
)
config_values = getattr(self.moduleConfig, config_type.name)
else:
print(
"Did not receive a valid response. Make sure to have a shared channel named 'admin'."
)
return
for key, value in resp[field].items():
setattr(config_values, camel_to_snake(key), value)
print(f"{str(camel_to_snake(field))}:\n{str(config_values)}")
self.cannedPluginMessagePart1 = None
self.cannedPluginMessagePart2 = None
self.cannedPluginMessagePart3 = None
self.cannedPluginMessagePart4 = None
def requestConfig(self, configType):
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onResponseRequestSettings
print("Requesting current config from remote node (this can take a while).")
msgIndex = configType.index
if configType.containing_type.full_name == "LocalConfig":
p = admin_pb2.AdminMessage()
p.get_config_request = msgIndex
self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
else:
p = admin_pb2.AdminMessage()
p.get_module_config_request = msgIndex
self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
if onResponse:
self.iface.waitForAckNak()
self._requestSettings()
def turnOffEncryptionOnPrimaryChannel(self):
"""Turn off encryption on primary channel."""
@@ -139,77 +82,34 @@ class Node:
print("Writing modified channels to device")
self.writeChannel(0)
def waitForConfig(self, attribute="channels"):
def waitForConfig(self, attribute='channels'):
"""Block until radio config is received. Returns True if config has been received."""
return self._timeout.waitForSet(self, attrs=("localConfig", attribute))
return self._timeout.waitForSet(self, attrs=('radioConfig', attribute))
def writeConfig(self, config_name):
"""Write the current (edited) localConfig to the device"""
if self.localConfig is None:
our_exit("Error: No localConfig has been read")
def writeConfig(self):
"""Write the current (edited) radioConfig to the device"""
if self.radioConfig is None:
our_exit("Error: No RadioConfig has been read")
p = admin_pb2.AdminMessage()
p.set_radio.CopyFrom(self.radioConfig)
if config_name == "device":
p.set_config.device.CopyFrom(self.localConfig.device)
elif config_name == "position":
p.set_config.position.CopyFrom(self.localConfig.position)
elif config_name == "power":
p.set_config.power.CopyFrom(self.localConfig.power)
elif config_name == "network":
p.set_config.network.CopyFrom(self.localConfig.network)
elif config_name == "display":
p.set_config.display.CopyFrom(self.localConfig.display)
elif config_name == "lora":
p.set_config.lora.CopyFrom(self.localConfig.lora)
elif config_name == "bluetooth":
p.set_config.bluetooth.CopyFrom(self.localConfig.bluetooth)
elif config_name == "mqtt":
p.set_module_config.mqtt.CopyFrom(self.moduleConfig.mqtt)
elif config_name == "serial":
p.set_module_config.serial.CopyFrom(self.moduleConfig.serial)
elif config_name == "external_notification":
p.set_module_config.external_notification.CopyFrom(
self.moduleConfig.external_notification
)
elif config_name == "store_forward":
p.set_module_config.store_forward.CopyFrom(self.moduleConfig.store_forward)
elif config_name == "range_test":
p.set_module_config.range_test.CopyFrom(self.moduleConfig.range_test)
elif config_name == "telemetry":
p.set_module_config.telemetry.CopyFrom(self.moduleConfig.telemetry)
elif config_name == "canned_message":
p.set_module_config.canned_message.CopyFrom(
self.moduleConfig.canned_message
)
elif config_name == "audio":
p.set_module_config.audio.CopyFrom(self.moduleConfig.audio)
elif config_name == "remote_hardware":
p.set_module_config.remote_hardware.CopyFrom(
self.moduleConfig.remote_hardware
)
else:
our_exit(f"Error: No valid config with name {config_name}")
logging.debug(f"Wrote: {config_name}")
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
self._sendAdmin(p, onResponse=onResponse)
self._sendAdmin(p)
logging.debug("Wrote config")
def writeChannel(self, channelIndex, adminIndex=0):
"""Write the current (edited) channel to the device"""
p = admin_pb2.AdminMessage()
p.set_channel.CopyFrom(self.channels[channelIndex])
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
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):
@@ -219,10 +119,7 @@ class Node:
def deleteChannel(self, channelIndex):
"""Delete the specifed channelIndex and shift other channels up"""
ch = self.channels[channelIndex]
if ch.role not in (
channel_pb2.Channel.Role.SECONDARY,
channel_pb2.Channel.Role.DISABLED,
):
if ch.role not in (channel_pb2.Channel.Role.SECONDARY, channel_pb2.Channel.Role.DISABLED):
our_exit("Warning: Only SECONDARY channels can be deleted")
# we are careful here because if we move the "admin" channel the channelIndex we need to use
@@ -246,7 +143,7 @@ class Node:
def getChannelByName(self, name):
"""Try to find the named channel or return None"""
for c in self.channels or []:
for c in (self.channels or []):
if c.settings and c.settings.name == name:
return c
return None
@@ -266,33 +163,44 @@ class Node:
else:
return 0
def setOwner(self, long_name=None, short_name=None, is_licensed=False):
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}")
p = admin_pb2.AdminMessage()
nChars = 4
nChars = 3
minChars = 2
if long_name is not None:
long_name = long_name.strip()
if short_name is None:
words = long_name.split()
if len(long_name) <= nChars:
short_name = long_name
elif len(words) >= minChars:
short_name = ''.join(map(lambda word: word[0], words))
else:
trans = str.maketrans(dict.fromkeys('aeiouAEIOU'))
short_name = long_name[0] + long_name[1:].translate(trans)
if len(short_name) < nChars:
short_name = long_name[:nChars]
p = admin_pb2.AdminMessage()
if long_name is not None:
p.set_owner.long_name = long_name
p.set_owner.is_licensed = is_licensed
if short_name is not None:
short_name = short_name.strip()
if len(short_name) > nChars:
short_name = short_name[:nChars]
print(f"Maximum is 4 characters, truncated to {short_name}")
p.set_owner.short_name = short_name
p.set_owner.is_licensed = is_licensed
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}")
# If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
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):
"""The sharable URL that describes the current channel"""
@@ -300,23 +208,18 @@ class Node:
channelSet = apponly_pb2.ChannelSet()
if self.channels:
for c in self.channels:
if c.role == channel_pb2.Channel.Role.PRIMARY or (
includeAll and c.role == channel_pb2.Channel.Role.SECONDARY
):
if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY):
channelSet.settings.append(c.settings)
channelSet.lora_config.CopyFrom(self.localConfig.lora)
some_bytes = channelSet.SerializeToString()
s = base64.urlsafe_b64encode(some_bytes).decode("ascii")
s = s.replace("=", "").replace("+", "-").replace("/", "_")
return f"https://meshtastic.org/e/#{s}"
s = base64.urlsafe_b64encode(some_bytes).decode('ascii')
return f"https://www.meshtastic.org/d/#{s}".replace("=", "")
def setURL(self, url):
"""Set mesh network URL"""
if self.localConfig is None:
our_exit("Warning: No Config has been read")
if self.radioConfig is None:
our_exit("Warning: No RadioConfig has been read")
# URLs are of the form https://meshtastic.org/d/#{base64_channel_set}
# URLs are of the form https://www.meshtastic.org/d/#{base64_channel_set}
# Split on '/#' to find the base64 encoded channel settings
splitURL = url.split("/#")
b64 = splitURL[-1]
@@ -326,37 +229,66 @@ class Node:
# per https://stackoverflow.com/a/9807138
missing_padding = len(b64) % 4
if missing_padding:
b64 += "=" * (4 - missing_padding)
b64 += '=' * (4 - missing_padding)
decodedURL = base64.urlsafe_b64decode(b64)
channelSet = apponly_pb2.ChannelSet()
channelSet.ParseFromString(decodedURL)
if len(channelSet.settings) == 0:
our_exit("Warning: There were no settings.")
i = 0
for chs in channelSet.settings:
ch = channel_pb2.Channel()
ch.role = (
channel_pb2.Channel.Role.PRIMARY
if i == 0
else channel_pb2.Channel.Role.SECONDARY
)
ch.role = channel_pb2.Channel.Role.PRIMARY if i == 0 else channel_pb2.Channel.Role.SECONDARY
ch.index = i
ch.settings.CopyFrom(chs)
self.channels[ch.index] = ch
logging.debug(f"Channel i:{i} ch:{ch}")
logging.debug(f'Channel i:{i} ch:{ch}')
self.writeChannel(ch.index)
i = i + 1
p = admin_pb2.AdminMessage()
p.set_config.lora.CopyFrom(channelSet.lora_config)
self._sendAdmin(p)
def onResponseRequestRingtone(self, p):
"""Handle the response packet for requesting ringtone part 1"""
logging.debug(f"onResponseRequestRingtone() p:{p}")
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
# TODO: should we check that localNode has an 'admin' channel?
# Show progress message for super slow operations
if self != self.iface.localNode:
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=self.onResponseRequestSettings)
def onResponseRequestCannedMessagePluginMessagePart1(self, p):
"""Handle the response packet for requesting canned message plugin message part 1"""
logging.debug(f'onResponseRequestCannedMessagePluginMessagePart1() p:{p}')
errorFound = False
if "routing" in p["decoded"]:
if p["decoded"]["routing"]["errorReason"] != "NONE":
@@ -366,67 +298,13 @@ class Node:
if "decoded" in p:
if "admin" in p["decoded"]:
if "raw" in p["decoded"]["admin"]:
self.ringtonePart = p["decoded"]["admin"][
"raw"
].get_ringtone_response
logging.debug(f"self.ringtonePart:{self.ringtonePart}")
self.cannedPluginMessagePart1 = p["decoded"]["admin"]["raw"].get_canned_message_plugin_part1_response
logging.debug(f'self.cannedPluginMessagePart1:{self.cannedPluginMessagePart1}')
self.gotResponse = True
def get_ringtone(self):
"""Get the ringtone. Concatenate all pieces together and return a single string."""
logging.debug(f"in get_ringtone()")
if not self.ringtone:
p1 = admin_pb2.AdminMessage()
p1.get_ringtone_request = True
self.gotResponse = False
self._sendAdmin(
p1, wantResponse=True, onResponse=self.onResponseRequestRingtone
)
while self.gotResponse is False:
time.sleep(0.1)
logging.debug(f"self.ringtone:{self.ringtone}")
self.ringtone = ""
if self.ringtonePart:
self.ringtone += self.ringtonePart
print(f"ringtone:{self.ringtone}")
logging.debug(f"ringtone:{self.ringtone}")
return self.ringtone
def set_ringtone(self, ringtone):
"""Set the ringtone. The ringtone length must be less than 230 character."""
if len(ringtone) > 230:
our_exit("Warning: The ringtone must be less than 230 characters.")
# split into chunks
chunks = []
chunks_size = 230
for i in range(0, len(ringtone), chunks_size):
chunks.append(ringtone[i : i + chunks_size])
# for each chunk, send a message to set the values
# for i in range(0, len(chunks)):
for i, chunk in enumerate(chunks):
p = admin_pb2.AdminMessage()
# TODO: should be a way to improve this
if i == 0:
p.set_ringtone_message = chunk
logging.debug(f"Setting ringtone '{chunk}' part {i+1}")
# If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def onResponseRequestCannedMessagePluginMessageMessages(self, p):
"""Handle the response packet for requesting canned message plugin message part 1"""
logging.debug(f"onResponseRequestCannedMessagePluginMessageMessages() p:{p}")
def onResponseRequestCannedMessagePluginMessagePart2(self, p):
"""Handle the response packet for requesting canned message plugin message part 2"""
logging.debug(f'onResponseRequestCannedMessagePluginMessagePart2() p:{p}')
errorFound = False
if "routing" in p["decoded"]:
if p["decoded"]["routing"]["errorReason"] != "NONE":
@@ -436,76 +314,134 @@ class Node:
if "decoded" in p:
if "admin" in p["decoded"]:
if "raw" in p["decoded"]["admin"]:
self.cannedPluginMessageMessages = p["decoded"]["admin"][
"raw"
].get_canned_message_module_messages_response
logging.debug(
f"self.cannedPluginMessageMessages:{self.cannedPluginMessageMessages}"
)
self.cannedPluginMessagePart2 = p["decoded"]["admin"]["raw"].get_canned_message_plugin_part2_response
logging.debug(f'self.cannedPluginMessagePart2:{self.cannedPluginMessagePart2}')
self.gotResponse = True
def onResponseRequestCannedMessagePluginMessagePart3(self, p):
"""Handle the response packet for requesting canned message plugin message part 3"""
logging.debug(f'onResponseRequestCannedMessagePluginMessagePart3() 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:
if "decoded" in p:
if "admin" in p["decoded"]:
if "raw" in p["decoded"]["admin"]:
self.cannedPluginMessagePart3 = p["decoded"]["admin"]["raw"].get_canned_message_plugin_part3_response
logging.debug(f'self.cannedPluginMessagePart3:{self.cannedPluginMessagePart3}')
self.gotResponse = True
def onResponseRequestCannedMessagePluginMessagePart4(self, p):
"""Handle the response packet for requesting canned message plugin message part 4"""
logging.debug(f'onResponseRequestCannedMessagePluginMessagePart4() 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:
if "decoded" in p:
if "admin" in p["decoded"]:
if "raw" in p["decoded"]["admin"]:
self.cannedPluginMessagePart4 = p["decoded"]["admin"]["raw"].get_canned_message_plugin_part4_response
logging.debug(f'self.cannedPluginMessagePart4:{self.cannedPluginMessagePart4}')
self.gotResponse = True
def get_canned_message(self):
"""Get the canned message string. Concatenate all pieces together and return a single string."""
logging.debug(f"in get_canned_message()")
logging.debug(f'in get_canned_message()')
if not self.cannedPluginMessage:
p1 = admin_pb2.AdminMessage()
p1.get_canned_message_module_messages_request = True
p1.get_canned_message_plugin_part1_request = True
self.gotResponse = False
self._sendAdmin(
p1,
wantResponse=True,
onResponse=self.onResponseRequestCannedMessagePluginMessageMessages,
)
self._sendAdmin(p1, wantResponse=True, onResponse=self.onResponseRequestCannedMessagePluginMessagePart1)
while self.gotResponse is False:
time.sleep(0.1)
logging.debug(
f"self.cannedPluginMessageMessages:{self.cannedPluginMessageMessages}"
)
p2 = admin_pb2.AdminMessage()
p2.get_canned_message_plugin_part2_request = True
self.gotResponse = False
self._sendAdmin(p2, wantResponse=True, onResponse=self.onResponseRequestCannedMessagePluginMessagePart2)
while self.gotResponse is False:
time.sleep(0.1)
p3 = admin_pb2.AdminMessage()
p3.get_canned_message_plugin_part3_request = True
self.gotResponse = False
self._sendAdmin(p3, wantResponse=True, onResponse=self.onResponseRequestCannedMessagePluginMessagePart3)
while self.gotResponse is False:
time.sleep(0.1)
p4 = admin_pb2.AdminMessage()
p4.get_canned_message_plugin_part4_request = True
self.gotResponse = False
self._sendAdmin(p4, wantResponse=True, onResponse=self.onResponseRequestCannedMessagePluginMessagePart4)
while self.gotResponse is False:
time.sleep(0.1)
# TODO: This feels wrong to have a sleep here. Is there a way to ensure that
# all requests are complete? Perhaps change to a while loop any parts are None... maybe?
time.sleep(3)
logging.debug(f'self.cannedPluginMessagePart1:{self.cannedPluginMessagePart1}')
logging.debug(f'self.cannedPluginMessagePart2:{self.cannedPluginMessagePart2}')
logging.debug(f'self.cannedPluginMessagePart3:{self.cannedPluginMessagePart3}')
logging.debug(f'self.cannedPluginMessagePart4:{self.cannedPluginMessagePart4}')
self.cannedPluginMessage = ""
if self.cannedPluginMessageMessages:
self.cannedPluginMessage += self.cannedPluginMessageMessages
if self.cannedPluginMessagePart1:
self.cannedPluginMessage += self.cannedPluginMessagePart1
if self.cannedPluginMessagePart2:
self.cannedPluginMessage += self.cannedPluginMessagePart2
if self.cannedPluginMessagePart3:
self.cannedPluginMessage += self.cannedPluginMessagePart3
if self.cannedPluginMessagePart4:
self.cannedPluginMessage += self.cannedPluginMessagePart4
print(f"canned_plugin_message:{self.cannedPluginMessage}")
logging.debug(f"canned_plugin_message:{self.cannedPluginMessage}")
print(f'canned_plugin_message:{self.cannedPluginMessage}')
logging.debug(f'canned_plugin_message:{self.cannedPluginMessage}')
return self.cannedPluginMessage
def set_canned_message(self, message):
"""Set the canned message. The canned messages length must be less than 200 character."""
"""Set the canned message. Split into parts of 200 chars each."""
if len(message) > 200:
our_exit("Warning: The canned message must be less than 200 characters.")
if len(message) > 800:
our_exit("Warning: The canned message must be less than 800 characters.")
# split into chunks
chunks = []
chunks_size = 200
for i in range(0, len(message), chunks_size):
chunks.append(message[i : i + chunks_size])
chunks.append(message[i: i + chunks_size])
# for each chunk, send a message to set the values
# for i in range(0, len(chunks)):
#for i in range(0, len(chunks)):
for i, chunk in enumerate(chunks):
p = admin_pb2.AdminMessage()
# TODO: should be a way to improve this
if i == 0:
p.set_canned_message_module_messages = chunk
p.set_canned_message_plugin_part1 = chunk
elif i == 1:
p.set_canned_message_plugin_part2 = chunk
elif i == 2:
p.set_canned_message_plugin_part3 = chunk
elif i == 3:
p.set_canned_message_plugin_part4 = chunk
logging.debug(f"Setting canned message '{chunk}' part {i+1}")
# If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
self._sendAdmin(p)
def exitSimulator(self):
"""Tell a simulator node to exit (this message
is ignored for other nodes)"""
is ignored for other nodes)"""
p = admin_pb2.AdminMessage()
p.exit_simulator = True
logging.debug("in exitSimulator()")
logging.debug('in exitSimulator()')
return self._sendAdmin(p)
@@ -515,51 +451,7 @@ class Node:
p.reboot_seconds = secs
logging.info(f"Telling node to reboot in {secs} seconds")
# If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def beginSettingsTransaction(self):
"""Tell the node to open a transaction to edit settings."""
p = admin_pb2.AdminMessage()
p.begin_edit_settings = True
logging.info(f"Telling open a transaction to edit settings")
# If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def commitSettingsTransaction(self):
"""Tell the node to commit the open transaction for editing settings."""
p = admin_pb2.AdminMessage()
p.commit_edit_settings = True
logging.info(f"Telling node to commit open transaction for editing settings")
# If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def rebootOTA(self, secs: int = 10):
"""Tell the node to reboot into factory firmware."""
p = admin_pb2.AdminMessage()
p.reboot_ota_seconds = secs
logging.info(f"Telling node to reboot to OTA in {secs} seconds")
# If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
return self._sendAdmin(p)
def shutdown(self, secs: int = 10):
"""Tell the node to shutdown."""
@@ -567,48 +459,7 @@ class Node:
p.shutdown_seconds = secs
logging.info(f"Telling node to shutdown in {secs} seconds")
# If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def getMetadata(self):
"""Get the node's metadata."""
p = admin_pb2.AdminMessage()
p.get_device_metadata_request = True
logging.info(f"Requesting device metadata")
return self._sendAdmin(
p, wantResponse=True, onResponse=self.onRequestGetMetadata
)
def factoryReset(self):
"""Tell the node to factory reset."""
p = admin_pb2.AdminMessage()
p.factory_reset = True
logging.info(f"Telling node to factory reset")
# If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def resetNodeDb(self):
"""Tell the node to reset its list of nodes."""
p = admin_pb2.AdminMessage()
p.nodedb_reset = True
logging.info(f"Telling node to reset the NodeDB")
# If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
return self._sendAdmin(p)
def _fixupChannels(self):
"""Fixup indexes and add disabled channels as needed"""
@@ -632,49 +483,10 @@ class Node:
self.channels.append(ch)
index += 1
def onRequestGetMetadata(self, p):
"""Handle the response packet for requesting device metadata getMetadata()"""
logging.debug(f"onRequestGetMetadata() p:{p}")
if p["decoded"]["portnum"] == portnums_pb2.PortNum.Name(
portnums_pb2.PortNum.ROUTING_APP
):
if p["decoded"]["routing"]["errorReason"] != "NONE":
logging.warning(
f'Metadata request failed, error reason: {p["decoded"]["routing"]["errorReason"]}'
)
self._timeout.expireTime = time.time() # Do not wait any longer
return # Don't try to parse this routing message
logging.debug(f"Retrying metadata request.")
self.getMetadata()
return
c = p["decoded"]["admin"]["raw"].get_device_metadata_response
self._timeout.reset() # We made foreward progress
logging.debug(f"Received metadata {stripnl(c)}")
print(f"\nfirmware_version: {c.firmware_version}")
print(f"device_state_version: {c.device_state_version}")
def onResponseRequestChannel(self, p):
"""Handle the response packet for requesting a channel _requestChannel()"""
logging.debug(f"onResponseRequestChannel() p:{p}")
if p["decoded"]["portnum"] == portnums_pb2.PortNum.Name(
portnums_pb2.PortNum.ROUTING_APP
):
if p["decoded"]["routing"]["errorReason"] != "NONE":
logging.warning(
f'Channel request failed, error reason: {p["decoded"]["routing"]["errorReason"]}'
)
self._timeout.expireTime = time.time() # Do not wait any longer
return # Don't try to parse this routing message
lastTried = 0
if len(self.partialChannels) > 0:
lastTried = self.partialChannels[-1].index
logging.debug(f"Retrying previous channel request.")
self._requestChannel(lastTried)
return
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
@@ -686,9 +498,7 @@ class Node:
# 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
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")
@@ -701,70 +511,37 @@ class Node:
else:
self._requestChannel(index + 1)
def onAckNak(self, p):
if p["decoded"]["routing"]["errorReason"] != "NONE":
print(
f'Received a NAK, error reason: {p["decoded"]["routing"]["errorReason"]}'
)
self.iface._acknowledgment.receivedNak = True
else:
if int(p["from"]) == self.iface.localNode.nodeNum:
print(
f"Received an implicit ACK. Packet will likely arrive, but cannot be guaranteed."
)
self.iface._acknowledgment.receivedImplAck = True
else:
print(f"Received an ACK.")
self.iface._acknowledgment.receivedAck = True
def _requestChannel(self, channelNum: int):
"""Done with initial config messages, now send regular
MeshPackets to ask for settings"""
MeshPackets to ask for settings"""
p = admin_pb2.AdminMessage()
p.get_channel_request = channelNum + 1
# Show progress message for super slow operations
if self != self.iface.localNode:
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)"
)
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}")
return self._sendAdmin(
p, wantResponse=True, onResponse=self.onResponseRequestChannel
)
return self._sendAdmin(p, wantResponse=True, onResponse=self.onResponseRequestChannel)
# pylint: disable=R1710
def _sendAdmin(
self,
p: admin_pb2.AdminMessage,
wantResponse=True,
onResponse=None,
adminIndex=0,
):
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 self.noProto:
logging.warning(
f"Not sending packet because protocol use is disabled by 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
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}")
logging.debug(f'adminIndex:{adminIndex}')
return self.iface.sendData(
p,
self.nodeNum,
portNum=portnums_pb2.PortNum.ADMIN_APP,
wantAck=False,
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

@@ -1,10 +1,9 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/portnums.proto
"""Generated protocol buffer code."""
# source: portnums.proto
from google.protobuf.internal import enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
@@ -15,9 +14,96 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19meshtastic/portnums.proto*\xba\x03\n\x07PortNum\x12\x0f\n\x0bUNKNOWN_APP\x10\x00\x12\x14\n\x10TEXT_MESSAGE_APP\x10\x01\x12\x17\n\x13REMOTE_HARDWARE_APP\x10\x02\x12\x10\n\x0cPOSITION_APP\x10\x03\x12\x10\n\x0cNODEINFO_APP\x10\x04\x12\x0f\n\x0bROUTING_APP\x10\x05\x12\r\n\tADMIN_APP\x10\x06\x12\x1f\n\x1bTEXT_MESSAGE_COMPRESSED_APP\x10\x07\x12\x10\n\x0cWAYPOINT_APP\x10\x08\x12\r\n\tAUDIO_APP\x10\t\x12\r\n\tREPLY_APP\x10 \x12\x11\n\rIP_TUNNEL_APP\x10!\x12\x0e\n\nSERIAL_APP\x10@\x12\x15\n\x11STORE_FORWARD_APP\x10\x41\x12\x12\n\x0eRANGE_TEST_APP\x10\x42\x12\x11\n\rTELEMETRY_APP\x10\x43\x12\x0b\n\x07ZPS_APP\x10\x44\x12\x11\n\rSIMULATOR_APP\x10\x45\x12\x12\n\x0eTRACEROUTE_APP\x10\x46\x12\x14\n\x10NEIGHBORINFO_APP\x10G\x12\x10\n\x0bPRIVATE_APP\x10\x80\x02\x12\x13\n\x0e\x41TAK_FORWARDER\x10\x81\x02\x12\x08\n\x03MAX\x10\xff\x03\x42]\n\x13\x63om.geeksville.meshB\x08PortnumsZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
DESCRIPTOR = _descriptor.FileDescriptor(
name='portnums.proto',
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\010PortnumsH\003Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\x0eportnums.proto*\xcb\x02\n\x07PortNum\x12\x0f\n\x0bUNKNOWN_APP\x10\x00\x12\x14\n\x10TEXT_MESSAGE_APP\x10\x01\x12\x17\n\x13REMOTE_HARDWARE_APP\x10\x02\x12\x10\n\x0cPOSITION_APP\x10\x03\x12\x10\n\x0cNODEINFO_APP\x10\x04\x12\x0f\n\x0bROUTING_APP\x10\x05\x12\r\n\tADMIN_APP\x10\x06\x12\r\n\tREPLY_APP\x10 \x12\x11\n\rIP_TUNNEL_APP\x10!\x12\x0e\n\nSERIAL_APP\x10@\x12\x15\n\x11STORE_FORWARD_APP\x10\x41\x12\x12\n\x0eRANGE_TEST_APP\x10\x42\x12!\n\x1d\x45NVIRONMENTAL_MEASUREMENT_APP\x10\x43\x12\x0b\n\x07ZPS_APP\x10\x44\x12\x10\n\x0bPRIVATE_APP\x10\x80\x02\x12\x13\n\x0e\x41TAK_FORWARDER\x10\x81\x02\x12\x08\n\x03MAX\x10\xff\x03\x42\x44\n\x13\x63om.geeksville.meshB\x08PortnumsH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
)
_PORTNUM = _descriptor.EnumDescriptor(
name='PortNum',
full_name='PortNum',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='UNKNOWN_APP', index=0, number=0,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='TEXT_MESSAGE_APP', index=1, number=1,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='REMOTE_HARDWARE_APP', index=2, number=2,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='POSITION_APP', index=3, number=3,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='NODEINFO_APP', index=4, number=4,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ROUTING_APP', index=5, number=5,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ADMIN_APP', index=6, number=6,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='REPLY_APP', index=7, number=32,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='IP_TUNNEL_APP', index=8, number=33,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='SERIAL_APP', index=9, number=64,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='STORE_FORWARD_APP', index=10, number=65,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='RANGE_TEST_APP', index=11, number=66,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ENVIRONMENTAL_MEASUREMENT_APP', index=12, number=67,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ZPS_APP', index=13, number=68,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='PRIVATE_APP', index=14, number=256,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ATAK_FORWARDER', index=15, number=257,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='MAX', index=16, number=511,
serialized_options=None,
type=None),
],
containing_type=None,
serialized_options=None,
serialized_start=19,
serialized_end=350,
)
_sym_db.RegisterEnumDescriptor(_PORTNUM)
_PORTNUM = DESCRIPTOR.enum_types_by_name['PortNum']
PortNum = enum_type_wrapper.EnumTypeWrapper(_PORTNUM)
UNKNOWN_APP = 0
TEXT_MESSAGE_APP = 1
@@ -26,28 +112,21 @@ POSITION_APP = 3
NODEINFO_APP = 4
ROUTING_APP = 5
ADMIN_APP = 6
TEXT_MESSAGE_COMPRESSED_APP = 7
WAYPOINT_APP = 8
AUDIO_APP = 9
REPLY_APP = 32
IP_TUNNEL_APP = 33
SERIAL_APP = 64
STORE_FORWARD_APP = 65
RANGE_TEST_APP = 66
TELEMETRY_APP = 67
ENVIRONMENTAL_MEASUREMENT_APP = 67
ZPS_APP = 68
SIMULATOR_APP = 69
TRACEROUTE_APP = 70
NEIGHBORINFO_APP = 71
PRIVATE_APP = 256
ATAK_FORWARDER = 257
MAX = 511
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR.enum_types_by_name['PortNum'] = _PORTNUM
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\010PortnumsZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_PORTNUM._serialized_start=30
_PORTNUM._serialized_end=472
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,15 +1,14 @@
"""Remote hardware
"""
import logging
from pubsub import pub
from meshtastic import portnums_pb2, remote_hardware_pb2
from meshtastic.util import our_exit
def onGPIOreceive(packet, interface):
"""Callback for received GPIO responses"""
"""Callback for received GPIO responses
"""
logging.debug(f"packet:{packet} interface:{interface}")
gpioValue = 0
hw = packet["decoded"]["remotehw"]
@@ -22,11 +21,9 @@ def onGPIOreceive(packet, interface):
# so, we set it here
gpioValue = 0
# print(f'mask:{interface.mask}')
#print(f'mask:{interface.mask}')
value = int(gpioValue) & int(interface.mask)
print(
f'Received RemoteHardware type={hw["type"]}, gpio_value={gpioValue} value={value}'
)
print(f'Received RemoteHardware typ={hw["typ"]}, gpio_value={gpioValue} value={value}')
interface.gotResponse = True
@@ -47,55 +44,46 @@ class RemoteHardwareClient:
ch = iface.localNode.getChannelByName("gpio")
if not ch:
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."
)
"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")
def _sendHardware(self, nodeid, r, wantResponse=False, onResponse=None):
if not nodeid:
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,
)
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)
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}")
logging.debug(f'writeGPIOs nodeid:{nodeid} mask:{mask} vals:{vals}')
r = remote_hardware_pb2.HardwareMessage()
r.type = remote_hardware_pb2.HardwareMessage.Type.WRITE_GPIOS
r.typ = remote_hardware_pb2.HardwareMessage.Type.WRITE_GPIOS
r.gpio_mask = mask
r.gpio_value = vals
return self._sendHardware(nodeid, r)
def readGPIOs(self, nodeid, mask, onResponse=None):
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}")
logging.debug(f'readGPIOs nodeid:{nodeid} mask:{mask}')
r = remote_hardware_pb2.HardwareMessage()
r.type = remote_hardware_pb2.HardwareMessage.Type.READ_GPIOS
r.typ = remote_hardware_pb2.HardwareMessage.Type.READ_GPIOS
r.gpio_mask = mask
return self._sendHardware(nodeid, r, wantResponse=True, onResponse=onResponse)
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}")
logging.debug(f'watchGPIOs nodeid:{nodeid} mask:{mask}')
r = remote_hardware_pb2.HardwareMessage()
r.type = remote_hardware_pb2.HardwareMessage.Type.WATCH_GPIOS
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,9 +1,8 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/remote_hardware.proto
"""Generated protocol buffer code."""
# source: remote_hardware.proto
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
@@ -14,25 +13,112 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n meshtastic/remote_hardware.proto\"\xcb\x01\n\x0fHardwareMessage\x12#\n\x04type\x18\x01 \x01(\x0e\x32\x15.HardwareMessage.Type\x12\x11\n\tgpio_mask\x18\x02 \x01(\x04\x12\x12\n\ngpio_value\x18\x03 \x01(\x04\"l\n\x04Type\x12\t\n\x05UNSET\x10\x00\x12\x0f\n\x0bWRITE_GPIOS\x10\x01\x12\x0f\n\x0bWATCH_GPIOS\x10\x02\x12\x11\n\rGPIOS_CHANGED\x10\x03\x12\x0e\n\nREAD_GPIOS\x10\x04\x12\x14\n\x10READ_GPIOS_REPLY\x10\x05\x42\x63\n\x13\x63om.geeksville.meshB\x0eRemoteHardwareZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
DESCRIPTOR = _descriptor.FileDescriptor(
name='remote_hardware.proto',
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\016RemoteHardwareH\003Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\x15remote_hardware.proto\"\xca\x01\n\x0fHardwareMessage\x12\"\n\x03typ\x18\x01 \x01(\x0e\x32\x15.HardwareMessage.Type\x12\x11\n\tgpio_mask\x18\x02 \x01(\x04\x12\x12\n\ngpio_value\x18\x03 \x01(\x04\"l\n\x04Type\x12\t\n\x05UNSET\x10\x00\x12\x0f\n\x0bWRITE_GPIOS\x10\x01\x12\x0f\n\x0bWATCH_GPIOS\x10\x02\x12\x11\n\rGPIOS_CHANGED\x10\x03\x12\x0e\n\nREAD_GPIOS\x10\x04\x12\x14\n\x10READ_GPIOS_REPLY\x10\x05\x42J\n\x13\x63om.geeksville.meshB\x0eRemoteHardwareH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
)
_HARDWAREMESSAGE = DESCRIPTOR.message_types_by_name['HardwareMessage']
_HARDWAREMESSAGE_TYPE = _HARDWAREMESSAGE.enum_types_by_name['Type']
_HARDWAREMESSAGE_TYPE = _descriptor.EnumDescriptor(
name='Type',
full_name='HardwareMessage.Type',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='UNSET', index=0, number=0,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='WRITE_GPIOS', index=1, number=1,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='WATCH_GPIOS', index=2, number=2,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='GPIOS_CHANGED', index=3, number=3,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='READ_GPIOS', index=4, number=4,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='READ_GPIOS_REPLY', index=5, number=5,
serialized_options=None,
type=None),
],
containing_type=None,
serialized_options=None,
serialized_start=120,
serialized_end=228,
)
_sym_db.RegisterEnumDescriptor(_HARDWAREMESSAGE_TYPE)
_HARDWAREMESSAGE = _descriptor.Descriptor(
name='HardwareMessage',
full_name='HardwareMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='typ', full_name='HardwareMessage.typ', index=0,
number=1, type=14, cpp_type=8, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='gpio_mask', full_name='HardwareMessage.gpio_mask', index=1,
number=2, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='gpio_value', full_name='HardwareMessage.gpio_value', index=2,
number=3, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
_HARDWAREMESSAGE_TYPE,
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=26,
serialized_end=228,
)
_HARDWAREMESSAGE.fields_by_name['typ'].enum_type = _HARDWAREMESSAGE_TYPE
_HARDWAREMESSAGE_TYPE.containing_type = _HARDWAREMESSAGE
DESCRIPTOR.message_types_by_name['HardwareMessage'] = _HARDWAREMESSAGE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
HardwareMessage = _reflection.GeneratedProtocolMessageType('HardwareMessage', (_message.Message,), {
'DESCRIPTOR' : _HARDWAREMESSAGE,
'__module__' : 'meshtastic.remote_hardware_pb2'
'__module__' : 'remote_hardware_pb2'
# @@protoc_insertion_point(class_scope:HardwareMessage)
})
_sym_db.RegisterMessage(HardwareMessage)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\016RemoteHardwareZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_HARDWAREMESSAGE._serialized_start=37
_HARDWAREMESSAGE._serialized_end=240
_HARDWAREMESSAGE_TYPE._serialized_start=132
_HARDWAREMESSAGE_TYPE._serialized_end=240
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

View File

@@ -1,35 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/rtttl.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16meshtastic/rtttl.proto\"\x1f\n\x0bRTTTLConfig\x12\x10\n\x08ringtone\x18\x01 \x01(\tBf\n\x13\x63om.geeksville.meshB\x11RTTTLConfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_RTTTLCONFIG = DESCRIPTOR.message_types_by_name['RTTTLConfig']
RTTTLConfig = _reflection.GeneratedProtocolMessageType('RTTTLConfig', (_message.Message,), {
'DESCRIPTOR' : _RTTTLCONFIG,
'__module__' : 'meshtastic.rtttl_pb2'
# @@protoc_insertion_point(class_scope:RTTTLConfig)
})
_sym_db.RegisterMessage(RTTTLConfig)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\021RTTTLConfigProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_RTTTLCONFIG._serialized_start=26
_RTTTLCONFIG._serialized_end=57
# @@protoc_insertion_point(module_scope)

View File

@@ -1,18 +1,16 @@
""" Serial interface class
"""
import logging
import platform
import time
import platform
import serial
import meshtastic.util
from meshtastic.stream_interface import StreamInterface
if platform.system() != "Windows":
if platform.system() != 'Windows':
import termios
class SerialInterface(StreamInterface):
"""Interface class for meshtastic devices over a serial link"""
@@ -44,23 +42,19 @@ class SerialInterface(StreamInterface):
# first we need to set the HUPCL so the device will not reboot based on RTS and/or DTR
# see https://github.com/pyserial/pyserial/issues/124
if platform.system() != "Windows":
with open(self.devPath, encoding="utf8") as f:
if platform.system() != 'Windows':
with open(self.devPath, encoding='utf8') as f:
attrs = termios.tcgetattr(f)
attrs[2] = attrs[2] & ~termios.HUPCL
termios.tcsetattr(f, termios.TCSAFLUSH, attrs)
f.close()
time.sleep(0.1)
self.stream = serial.Serial(
self.devPath, 115200, exclusive=True, timeout=0.5, write_timeout=0
)
self.stream = serial.Serial(self.devPath, 921600, exclusive=True, timeout=0.5, write_timeout=0)
self.stream.flush()
time.sleep(0.1)
StreamInterface.__init__(
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow
)
StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto, connectNow=connectNow)
def close(self):
"""Close a connection to the device"""

View File

@@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/storeforward.proto
"""Generated protocol buffer code."""
# source: storeforward.proto
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
@@ -14,39 +13,337 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1dmeshtastic/storeforward.proto\"\xbe\x06\n\x0fStoreAndForward\x12,\n\x02rr\x18\x01 \x01(\x0e\x32 .StoreAndForward.RequestResponse\x12,\n\x05stats\x18\x02 \x01(\x0b\x32\x1b.StoreAndForward.StatisticsH\x00\x12+\n\x07history\x18\x03 \x01(\x0b\x32\x18.StoreAndForward.HistoryH\x00\x12/\n\theartbeat\x18\x04 \x01(\x0b\x32\x1a.StoreAndForward.HeartbeatH\x00\x12\x0f\n\x05\x65mpty\x18\x05 \x01(\x08H\x00\x1a\xcd\x01\n\nStatistics\x12\x16\n\x0emessages_total\x18\x01 \x01(\r\x12\x16\n\x0emessages_saved\x18\x02 \x01(\r\x12\x14\n\x0cmessages_max\x18\x03 \x01(\r\x12\x0f\n\x07up_time\x18\x04 \x01(\r\x12\x10\n\x08requests\x18\x05 \x01(\r\x12\x18\n\x10requests_history\x18\x06 \x01(\r\x12\x11\n\theartbeat\x18\x07 \x01(\x08\x12\x12\n\nreturn_max\x18\x08 \x01(\r\x12\x15\n\rreturn_window\x18\t \x01(\r\x1aI\n\x07History\x12\x18\n\x10history_messages\x18\x01 \x01(\r\x12\x0e\n\x06window\x18\x02 \x01(\r\x12\x14\n\x0clast_request\x18\x03 \x01(\r\x1a.\n\tHeartbeat\x12\x0e\n\x06period\x18\x01 \x01(\r\x12\x11\n\tsecondary\x18\x02 \x01(\r\"\x89\x02\n\x0fRequestResponse\x12\t\n\x05UNSET\x10\x00\x12\x10\n\x0cROUTER_ERROR\x10\x01\x12\x14\n\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n\x0bROUTER_PING\x10\x03\x12\x0f\n\x0bROUTER_PONG\x10\x04\x12\x0f\n\x0bROUTER_BUSY\x10\x05\x12\x12\n\x0eROUTER_HISTORY\x10\x06\x12\x10\n\x0cROUTER_STATS\x10\x07\x12\x10\n\x0c\x43LIENT_ERROR\x10@\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x41\x12\x10\n\x0c\x43LIENT_STATS\x10\x42\x12\x0f\n\x0b\x43LIENT_PING\x10\x43\x12\x0f\n\x0b\x43LIENT_PONG\x10\x44\x12\x10\n\x0c\x43LIENT_ABORT\x10jB\t\n\x07variantBj\n\x13\x63om.geeksville.meshB\x15StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
DESCRIPTOR = _descriptor.FileDescriptor(
name='storeforward.proto',
package='',
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\025StoreAndForwardProtosH\003Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\x12storeforward.proto\"\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'
)
_STOREANDFORWARD = DESCRIPTOR.message_types_by_name['StoreAndForward']
_STOREANDFORWARD_STATISTICS = _STOREANDFORWARD.nested_types_by_name['Statistics']
_STOREANDFORWARD_HISTORY = _STOREANDFORWARD.nested_types_by_name['History']
_STOREANDFORWARD_HEARTBEAT = _STOREANDFORWARD.nested_types_by_name['Heartbeat']
_STOREANDFORWARD_REQUESTRESPONSE = _STOREANDFORWARD.enum_types_by_name['RequestResponse']
_STOREANDFORWARD_REQUESTRESPONSE = _descriptor.EnumDescriptor(
name='RequestResponse',
full_name='StoreAndForward.RequestResponse',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='UNSET', index=0, number=0,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ROUTER_ERROR', index=1, number=1,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ROUTER_HEARTBEAT', index=2, number=2,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ROUTER_PING', index=3, number=3,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ROUTER_PONG', index=4, number=4,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ROUTER_BUSY', index=5, number=5,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='ROUTER_HISTORY', index=6, number=6,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='CLIENT_ERROR', index=7, number=101,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='CLIENT_HISTORY', index=8, number=102,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='CLIENT_STATS', index=9, number=103,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
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=554,
serialized_end=801,
)
_sym_db.RegisterEnumDescriptor(_STOREANDFORWARD_REQUESTRESPONSE)
_STOREANDFORWARD_STATISTICS = _descriptor.Descriptor(
name='Statistics',
full_name='StoreAndForward.Statistics',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='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='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='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='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,
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='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,
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='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='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,
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=223,
serialized_end=428,
)
_STOREANDFORWARD_HISTORY = _descriptor.Descriptor(
name='History',
full_name='StoreAndForward.History',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
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,
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,
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=505,
serialized_end=551,
)
_STOREANDFORWARD = _descriptor.Descriptor(
name='StoreAndForward',
full_name='StoreAndForward',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='rr', full_name='StoreAndForward.rr', index=0,
number=1, type=14, cpp_type=8, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='stats', full_name='StoreAndForward.stats', index=1,
number=2, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='history', full_name='StoreAndForward.history', index=2,
number=3, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_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, _STOREANDFORWARD_HEARTBEAT, ],
enum_types=[
_STOREANDFORWARD_REQUESTRESPONSE,
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=23,
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)
StoreAndForward = _reflection.GeneratedProtocolMessageType('StoreAndForward', (_message.Message,), {
'Statistics' : _reflection.GeneratedProtocolMessageType('Statistics', (_message.Message,), {
'DESCRIPTOR' : _STOREANDFORWARD_STATISTICS,
'__module__' : 'meshtastic.storeforward_pb2'
'__module__' : 'storeforward_pb2'
# @@protoc_insertion_point(class_scope:StoreAndForward.Statistics)
})
,
'History' : _reflection.GeneratedProtocolMessageType('History', (_message.Message,), {
'DESCRIPTOR' : _STOREANDFORWARD_HISTORY,
'__module__' : 'meshtastic.storeforward_pb2'
'__module__' : 'storeforward_pb2'
# @@protoc_insertion_point(class_scope:StoreAndForward.History)
})
,
'Heartbeat' : _reflection.GeneratedProtocolMessageType('Heartbeat', (_message.Message,), {
'DESCRIPTOR' : _STOREANDFORWARD_HEARTBEAT,
'__module__' : 'meshtastic.storeforward_pb2'
'__module__' : 'storeforward_pb2'
# @@protoc_insertion_point(class_scope:StoreAndForward.Heartbeat)
})
,
'DESCRIPTOR' : _STOREANDFORWARD,
'__module__' : 'meshtastic.storeforward_pb2'
'__module__' : 'storeforward_pb2'
# @@protoc_insertion_point(class_scope:StoreAndForward)
})
_sym_db.RegisterMessage(StoreAndForward)
@@ -54,18 +351,6 @@ _sym_db.RegisterMessage(StoreAndForward.Statistics)
_sym_db.RegisterMessage(StoreAndForward.History)
_sym_db.RegisterMessage(StoreAndForward.Heartbeat)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\025StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_STOREANDFORWARD._serialized_start=34
_STOREANDFORWARD._serialized_end=864
_STOREANDFORWARD_STATISTICS._serialized_start=257
_STOREANDFORWARD_STATISTICS._serialized_end=462
_STOREANDFORWARD_HISTORY._serialized_start=464
_STOREANDFORWARD_HISTORY._serialized_end=537
_STOREANDFORWARD_HEARTBEAT._serialized_start=539
_STOREANDFORWARD_HEARTBEAT._serialized_end=585
_STOREANDFORWARD_REQUESTRESPONSE._serialized_start=588
_STOREANDFORWARD_REQUESTRESPONSE._serialized_end=853
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

View File

@@ -4,14 +4,15 @@ import logging
import threading
import time
import traceback
import serial
from meshtastic.mesh_interface import MeshInterface
from meshtastic.util import is_windows11, stripnl
from meshtastic.util import stripnl, is_windows11
START1 = 0x94
START2 = 0xC3
START2 = 0xc3
HEADER_LEN = 4
MAX_TO_FROM_RADIO_SIZE = 512
@@ -31,10 +32,9 @@ class StreamInterface(MeshInterface):
Exception: [description]
"""
if not hasattr(self, "stream") and not noProto:
if not hasattr(self, 'stream') and not noProto:
raise Exception(
"StreamInterface is now abstract (to update existing code create SerialInterface instead)"
)
"StreamInterface is now abstract (to update existing code create SerialInterface instead)")
self._rxBuf = bytes() # empty
self._wantExit = False
@@ -110,8 +110,8 @@ class StreamInterface(MeshInterface):
b = toRadio.SerializeToString()
bufLen = len(b)
# We convert into a string, because the TCP code doesn't work with byte arrays
header = bytes([START1, START2, (bufLen >> 8) & 0xFF, bufLen & 0xFF])
logging.debug(f"sending header:{header} b:{b}")
header = bytes([START1, START2, (bufLen >> 8) & 0xff, bufLen & 0xff])
logging.debug(f'sending header:{header} b:{b}')
self._writeBytes(header + b)
def close(self):
@@ -126,18 +126,18 @@ class StreamInterface(MeshInterface):
def __reader(self):
"""The reader thread that reads bytes from our stream"""
logging.debug("in __reader()")
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}')
#logging.debug(f'c:{c}')
ptr = len(self._rxBuf)
# Assume we want to append this byte, fixme use bytearray instead
@@ -150,54 +150,38 @@ class StreamInterface(MeshInterface):
try:
self.debugOut.write(b.decode("utf-8"))
except:
self.debugOut.write("?")
self.debugOut.write('?')
elif ptr == 1: # looking for START2
if c != START2:
self._rxBuf = empty # failed to find start2
elif ptr >= HEADER_LEN - 1: # we've at least got a header
# logging.debug('at least we received a 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 ptr == HEADER_LEN - 1: # we _just_ finished reading the header, validate length
if packetlen > MAX_TO_FROM_RADIO_SIZE:
self._rxBuf = (
empty # length was 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:
self._handleFromRadio(self._rxBuf[HEADER_LEN:])
except Exception as ex:
logging.error(
f"Error while handling message from radio {ex}"
)
logging.error(f"Error while handling message from radio {ex}")
traceback.print_exc()
self._rxBuf = empty
else:
# logging.debug(f"timeout")
pass
except serial.SerialException as ex:
if (
not self._wantExit
): # We might intentionally get an exception during shutdown
logging.warning(
f"Meshtastic serial port disconnected, disconnecting... {ex}"
)
if not self._wantExit: # We might intentionally get an exception during shutdown
logging.warning(f"Meshtastic serial port disconnected, disconnecting... {ex}")
except OSError as ex:
if (
not self._wantExit
): # We might intentionally get an exception during shutdown
logging.error(
f"Unexpected OSError, terminating meshtastic reader... {ex}"
)
if not self._wantExit: # We might intentionally get an exception during shutdown
logging.error(f"Unexpected OSError, terminating meshtastic reader... {ex}")
except Exception as ex:
logging.error(
f"Unexpected exception, terminating meshtastic reader... {ex}"
)
logging.error(f"Unexpected exception, terminating meshtastic reader... {ex}")
finally:
logging.debug("reader is exiting")
self._disconnected()

View File

@@ -5,224 +5,85 @@
# Goal is to detect which device and port to use from the supported devices
# without installing any libraries that are not currently in the python meshtastic library
class SupportedDevice:
class SupportedDevice():
"""Devices supported on Meshtastic"""
def __init__(
self,
name,
version=None,
for_firmware=None,
device_class="esp32",
baseport_on_linux=None,
baseport_on_mac=None,
baseport_on_windows="COM",
usb_vendor_id_in_hex=None,
usb_product_id_in_hex=None,
):
"""constructor"""
def __init__(self, name, version=None, for_firmware=None, device_class="esp32",
baseport_on_linux=None, baseport_on_mac=None, baseport_on_windows="COM",
usb_vendor_id_in_hex=None, usb_product_id_in_hex=None):
""" constructor """
self.name = name
self.version = version
self.for_firmware = for_firmware
self.device_class = device_class # could be "nrf52"
self.device_class = device_class # could be "nrf52"
# when you run "lsusb -d xxxx:" in linux
self.usb_vendor_id_in_hex = usb_vendor_id_in_hex # store in lower case
self.usb_product_id_in_hex = usb_product_id_in_hex # store in lower case
self.usb_vendor_id_in_hex = usb_vendor_id_in_hex # store in lower case
self.usb_product_id_in_hex = usb_product_id_in_hex # store in lower case
self.baseport_on_linux = baseport_on_linux # ex: ttyUSB or ttyACM
self.baseport_on_linux = baseport_on_linux # ex: ttyUSB or ttyACM
self.baseport_on_mac = baseport_on_mac
self.baseport_on_windows = baseport_on_windows
# supported devices
tbeam_v0_7 = SupportedDevice(
name="T-Beam",
version="0.7",
for_firmware="tbeam0.7",
baseport_on_linux="ttyACM",
baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86",
usb_product_id_in_hex="55d4",
)
tbeam_v1_1 = SupportedDevice(
name="T-Beam",
version="1.1",
for_firmware="tbeam",
baseport_on_linux="ttyACM",
baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86",
usb_product_id_in_hex="55d4",
)
tbeam_M8N = SupportedDevice(
name="T-Beam",
version="M8N",
for_firmware="tbeam",
baseport_on_linux="ttyACM",
baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86",
usb_product_id_in_hex="55d4",
)
tbeam_M8N_SX1262 = SupportedDevice(
name="T-Beam",
version="M8N_SX1262",
for_firmware="tbeam",
baseport_on_linux="ttyACM",
baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86",
usb_product_id_in_hex="55d4",
)
tlora_v1 = SupportedDevice(
name="T-Lora",
version="1",
for_firmware="tlora-v1",
baseport_on_linux="ttyUSB",
baseport_on_mac="cu.usbserial",
usb_vendor_id_in_hex="1a86",
usb_product_id_in_hex="55d4",
)
tlora_v1_3 = SupportedDevice(
name="T-Lora",
version="1.3",
for_firmware="tlora-v1-3",
baseport_on_linux="ttyUSB",
baseport_on_mac="cu.usbserial",
usb_vendor_id_in_hex="10c4",
usb_product_id_in_hex="ea60",
)
tlora_v2 = SupportedDevice(
name="T-Lora",
version="2",
for_firmware="tlora-v2",
baseport_on_linux="ttyACM",
baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86",
usb_product_id_in_hex="55d4",
)
tlora_v2_1_1_6 = SupportedDevice(
name="T-Lora",
version="2.1-1.6",
for_firmware="tlora-v2-1-1.6",
baseport_on_linux="ttyACM",
baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86",
usb_product_id_in_hex="55d4",
)
heltec_v1 = SupportedDevice(
name="Heltec",
version="1",
for_firmware="heltec-v1",
baseport_on_linux="ttyUSB",
baseport_on_mac="cu.usbserial-",
usb_vendor_id_in_hex="10c4",
usb_product_id_in_hex="ea60",
)
heltec_v2_0 = SupportedDevice(
name="Heltec",
version="2.0",
for_firmware="heltec-v2.0",
baseport_on_linux="ttyUSB",
baseport_on_mac="cu.usbserial-",
usb_vendor_id_in_hex="10c4",
usb_product_id_in_hex="ea60",
)
heltec_v2_1 = SupportedDevice(
name="Heltec",
version="2.1",
for_firmware="heltec-v2.1",
baseport_on_linux="ttyUSB",
baseport_on_mac="cu.usbserial-",
usb_vendor_id_in_hex="10c4",
usb_product_id_in_hex="ea60",
)
rak11200 = SupportedDevice(
name="RAK 11200",
version="",
for_firmware="rak11200",
baseport_on_linux="ttyUSB",
baseport_on_mac="cu.usbserial-",
usb_vendor_id_in_hex="1a86",
usb_product_id_in_hex="7523",
)
meshtastic_diy_v1 = SupportedDevice(
name="Meshtastic DIY",
version="1",
for_firmware="meshtastic-diy-v1",
baseport_on_linux="ttyUSB",
baseport_on_mac="cu.usbserial-",
usb_vendor_id_in_hex="10c4",
usb_product_id_in_hex="ea60",
)
tbeam_v0_7 = SupportedDevice(name="T-Beam", version="0.7", for_firmware="tbeam0.7",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4")
tbeam_v1_1 = SupportedDevice(name="T-Beam", version="1.1", for_firmware="tbeam",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4")
tbeam_M8N = SupportedDevice(name="T-Beam", version="M8N", for_firmware="tbeam",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4")
tbeam_M8N_SX1262 = SupportedDevice(name="T-Beam", version="M8N_SX1262", for_firmware="tbeam",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4")
tlora_v1 = SupportedDevice(name="T-Lora", version="1", for_firmware="tlora-v1",
baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4")
tlora_v1_3 = SupportedDevice(name="T-Lora", version="1.3", for_firmware="tlora-v1-3",
baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial",
usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60")
tlora_v2 = SupportedDevice(name="T-Lora", version="2", for_firmware="tlora-v2",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4")
tlora_v2_1 = SupportedDevice(name="T-Lora", version="2.1", for_firmware="tlora-v2-1",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4")
tlora_v2_1_1_6 = SupportedDevice(name="T-Lora", version="2.1-1.6", for_firmware="tlora-v2-1-1.6",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4")
heltec_v1 = SupportedDevice(name="Heltec", version="1", for_firmware="heltec-v1",
baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial-",
usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60")
heltec_v2_0 = SupportedDevice(name="Heltec", version="2.0", for_firmware="heltec-v2.0",
baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial-",
usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60")
heltec_v2_1 = SupportedDevice(name="Heltec", version="2.1", for_firmware="heltec-v2.1",
baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial-",
usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60")
rak11200 = SupportedDevice(name="RAK 11200", version="", for_firmware="rak11200",
baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial-",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="7523")
meshtastic_diy_v1 = SupportedDevice(name="Meshtastic DIY", version="1", for_firmware="meshtastic-diy-v1",
baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial-",
usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60")
# Note: The T-Echo reports product id in boot mode
techo_1 = SupportedDevice(
name="T-Echo",
version="1",
for_firmware="t-echo-1",
device_class="nrf52",
baseport_on_linux="ttyACM",
baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="239a",
usb_product_id_in_hex="0029",
)
rak4631_5005 = SupportedDevice(
name="RAK 4631 5005",
version="",
for_firmware="rak4631_5005",
device_class="nrf52",
baseport_on_linux="ttyACM",
baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="239a",
usb_product_id_in_hex="0029",
)
rak4631_5005_epaper = SupportedDevice(
name="RAK 4631 5005 14000 epaper",
version="",
for_firmware="rak4631_5005_epaper",
device_class="nrf52",
baseport_on_linux="ttyACM",
baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="239a",
usb_product_id_in_hex="0029",
)
techo_1 = SupportedDevice(name="T-Echo", version="1", for_firmware="t-echo-1", device_class="nrf52",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="239a", usb_product_id_in_hex="0029")
rak4631_5005 = SupportedDevice(name="RAK 4631 5005", version="", for_firmware="rak4631_5005",
device_class="nrf52",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="239a", usb_product_id_in_hex="0029")
# Note: The 19003 reports same product id as 5005 in boot mode
rak4631_19003 = SupportedDevice(
name="RAK 4631 19003",
version="",
for_firmware="rak4631_19003",
device_class="nrf52",
baseport_on_linux="ttyACM",
baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="239a",
usb_product_id_in_hex="8029",
)
nano_g1 = SupportedDevice(
name="Nano G1",
version="",
for_firmware="nano-g1",
baseport_on_linux="ttyACM",
baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86",
usb_product_id_in_hex="55d4",
)
rak4631_19003 = SupportedDevice(name="RAK 4631 19003", version="", for_firmware="rak4631_19003",
device_class="nrf52",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="239a", usb_product_id_in_hex="8029")
supported_devices = [
tbeam_v0_7,
tbeam_v1_1,
tbeam_M8N,
tbeam_M8N_SX1262,
tlora_v1,
tlora_v1_3,
tlora_v2,
tlora_v2_1_1_6,
heltec_v1,
heltec_v2_0,
heltec_v2_1,
meshtastic_diy_v1,
techo_1,
rak4631_5005,
rak4631_5005_epaper,
rak4631_19003,
rak11200,
nano_g1,
]
supported_devices = [tbeam_v0_7, tbeam_v1_1, tbeam_M8N, tbeam_M8N_SX1262,
tlora_v1, tlora_v1_3, tlora_v2, tlora_v2_1, tlora_v2_1_1_6,
heltec_v1, heltec_v2_0, heltec_v2_1,
meshtastic_diy_v1, techo_1, rak4631_5005, rak4631_19003,
rak11200]

View File

@@ -6,18 +6,11 @@ from typing import AnyStr
from meshtastic.stream_interface import StreamInterface
class TCPInterface(StreamInterface):
"""Interface class for meshtastic devices over a TCP link"""
def __init__(
self,
hostname: AnyStr,
debugOut=None,
noProto=False,
connectNow=True,
portNumber=4403,
):
def __init__(self, hostname: AnyStr, debugOut=None, noProto=False,
connectNow=True, portNumber=4403):
"""Constructor, opens a connection to a specified IP address/hostname
Keyword Arguments:
@@ -37,13 +30,12 @@ class TCPInterface(StreamInterface):
else:
self.socket = None
StreamInterface.__init__(
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow
)
StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto,
connectNow=connectNow)
def _socket_shutdown(self):
"""Shutdown the socket.
Note: Broke out this line so the exception could be unit tested.
Note: Broke out this line so the exception could be unit tested.
"""
self.socket.shutdown(socket.SHUT_RDWR)

View File

@@ -1,84 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/telemetry.proto
"""Generated protocol buffer code."""
from google.protobuf.internal import enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ameshtastic/telemetry.proto\"i\n\rDeviceMetrics\x12\x15\n\rbattery_level\x18\x01 \x01(\r\x12\x0f\n\x07voltage\x18\x02 \x01(\x02\x12\x1b\n\x13\x63hannel_utilization\x18\x03 \x01(\x02\x12\x13\n\x0b\x61ir_util_tx\x18\x04 \x01(\x02\"\x9b\x01\n\x12\x45nvironmentMetrics\x12\x13\n\x0btemperature\x18\x01 \x01(\x02\x12\x19\n\x11relative_humidity\x18\x02 \x01(\x02\x12\x1b\n\x13\x62\x61rometric_pressure\x18\x03 \x01(\x02\x12\x16\n\x0egas_resistance\x18\x04 \x01(\x02\x12\x0f\n\x07voltage\x18\x05 \x01(\x02\x12\x0f\n\x07\x63urrent\x18\x06 \x01(\x02\"\xbf\x02\n\x11\x41irQualityMetrics\x12\x15\n\rpm10_standard\x18\x01 \x01(\r\x12\x15\n\rpm25_standard\x18\x02 \x01(\r\x12\x16\n\x0epm100_standard\x18\x03 \x01(\r\x12\x1a\n\x12pm10_environmental\x18\x04 \x01(\r\x12\x1a\n\x12pm25_environmental\x18\x05 \x01(\r\x12\x1b\n\x13pm100_environmental\x18\x06 \x01(\r\x12\x16\n\x0eparticles_03um\x18\x07 \x01(\r\x12\x16\n\x0eparticles_05um\x18\x08 \x01(\r\x12\x16\n\x0eparticles_10um\x18\t \x01(\r\x12\x16\n\x0eparticles_25um\x18\n \x01(\r\x12\x16\n\x0eparticles_50um\x18\x0b \x01(\r\x12\x17\n\x0fparticles_100um\x18\x0c \x01(\r\"\xb5\x01\n\tTelemetry\x12\x0c\n\x04time\x18\x01 \x01(\x07\x12(\n\x0e\x64\x65vice_metrics\x18\x02 \x01(\x0b\x32\x0e.DeviceMetricsH\x00\x12\x32\n\x13\x65nvironment_metrics\x18\x03 \x01(\x0b\x32\x13.EnvironmentMetricsH\x00\x12\x31\n\x13\x61ir_quality_metrics\x18\x04 \x01(\x0b\x32\x12.AirQualityMetricsH\x00\x42\t\n\x07variant*\xc7\x01\n\x13TelemetrySensorType\x12\x10\n\x0cSENSOR_UNSET\x10\x00\x12\n\n\x06\x42ME280\x10\x01\x12\n\n\x06\x42ME680\x10\x02\x12\x0b\n\x07MCP9808\x10\x03\x12\n\n\x06INA260\x10\x04\x12\n\n\x06INA219\x10\x05\x12\n\n\x06\x42MP280\x10\x06\x12\t\n\x05SHTC3\x10\x07\x12\t\n\x05LPS22\x10\x08\x12\x0b\n\x07QMC6310\x10\t\x12\x0b\n\x07QMI8658\x10\n\x12\x0c\n\x08QMC5883L\x10\x0b\x12\t\n\x05SHT31\x10\x0c\x12\x0c\n\x08PMSA003I\x10\rBd\n\x13\x63om.geeksville.meshB\x0fTelemetryProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_TELEMETRYSENSORTYPE = DESCRIPTOR.enum_types_by_name['TelemetrySensorType']
TelemetrySensorType = enum_type_wrapper.EnumTypeWrapper(_TELEMETRYSENSORTYPE)
SENSOR_UNSET = 0
BME280 = 1
BME680 = 2
MCP9808 = 3
INA260 = 4
INA219 = 5
BMP280 = 6
SHTC3 = 7
LPS22 = 8
QMC6310 = 9
QMI8658 = 10
QMC5883L = 11
SHT31 = 12
PMSA003I = 13
_DEVICEMETRICS = DESCRIPTOR.message_types_by_name['DeviceMetrics']
_ENVIRONMENTMETRICS = DESCRIPTOR.message_types_by_name['EnvironmentMetrics']
_AIRQUALITYMETRICS = DESCRIPTOR.message_types_by_name['AirQualityMetrics']
_TELEMETRY = DESCRIPTOR.message_types_by_name['Telemetry']
DeviceMetrics = _reflection.GeneratedProtocolMessageType('DeviceMetrics', (_message.Message,), {
'DESCRIPTOR' : _DEVICEMETRICS,
'__module__' : 'meshtastic.telemetry_pb2'
# @@protoc_insertion_point(class_scope:DeviceMetrics)
})
_sym_db.RegisterMessage(DeviceMetrics)
EnvironmentMetrics = _reflection.GeneratedProtocolMessageType('EnvironmentMetrics', (_message.Message,), {
'DESCRIPTOR' : _ENVIRONMENTMETRICS,
'__module__' : 'meshtastic.telemetry_pb2'
# @@protoc_insertion_point(class_scope:EnvironmentMetrics)
})
_sym_db.RegisterMessage(EnvironmentMetrics)
AirQualityMetrics = _reflection.GeneratedProtocolMessageType('AirQualityMetrics', (_message.Message,), {
'DESCRIPTOR' : _AIRQUALITYMETRICS,
'__module__' : 'meshtastic.telemetry_pb2'
# @@protoc_insertion_point(class_scope:AirQualityMetrics)
})
_sym_db.RegisterMessage(AirQualityMetrics)
Telemetry = _reflection.GeneratedProtocolMessageType('Telemetry', (_message.Message,), {
'DESCRIPTOR' : _TELEMETRY,
'__module__' : 'meshtastic.telemetry_pb2'
# @@protoc_insertion_point(class_scope:Telemetry)
})
_sym_db.RegisterMessage(Telemetry)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\017TelemetryProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_TELEMETRYSENSORTYPE._serialized_start=802
_TELEMETRYSENSORTYPE._serialized_end=1001
_DEVICEMETRICS._serialized_start=30
_DEVICEMETRICS._serialized_end=135
_ENVIRONMENTMETRICS._serialized_start=138
_ENVIRONMENTMETRICS._serialized_end=293
_AIRQUALITYMETRICS._serialized_start=296
_AIRQUALITYMETRICS._serialized_end=615
_TELEMETRY._serialized_start=618
_TELEMETRY._serialized_end=799
# @@protoc_insertion_point(module_scope)

View File

@@ -2,18 +2,17 @@
messages and report back if successful.
"""
import logging
import sys
import time
import sys
import traceback
from dotmap import DotMap
from pubsub import pub
import meshtastic.util
from meshtastic.__init__ import BROADCAST_NUM
from meshtastic.serial_interface import SerialInterface
from meshtastic.tcp_interface import TCPInterface
"""The interfaces we are using for our tests"""
interfaces = None
@@ -53,9 +52,7 @@ def subscribe():
pub.subscribe(onNode, "meshtastic.node")
def testSend(
fromInterface, toInterface, isBroadcast=False, asBinary=False, wantAck=False
):
def testSend(fromInterface, toInterface, isBroadcast=False, asBinary=False, wantAck=False):
"""
Sends one test packet between two nodes and then returns success or failure
@@ -76,19 +73,19 @@ def testSend(
else:
toNode = toInterface.myInfo.my_node_num
logging.debug(f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}")
logging.debug(
f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}")
# pylint: disable=W0603
global sendingInterface
sendingInterface = fromInterface
if not asBinary:
fromInterface.sendText(f"Test {testNumber}", toNode, wantAck=wantAck)
else:
fromInterface.sendData(
(f"Binary {testNumber}").encode("utf-8"), toNode, wantAck=wantAck
)
fromInterface.sendData((f"Binary {testNumber}").encode(
"utf-8"), toNode, wantAck=wantAck)
for _ in range(60): # max of 60 secs before we timeout
time.sleep(1)
if len(receivedPackets) >= 1:
if len(receivedPackets) >= 1:
return True
return False # Failed to send
@@ -105,18 +102,15 @@ def runTests(numTests=50, wantAck=False, maxFailures=0):
isBroadcast = True
# asBinary=(i % 2 == 0)
success = testSend(
interfaces[0], interfaces[1], isBroadcast, asBinary=False, wantAck=wantAck
)
interfaces[0], interfaces[1], isBroadcast, asBinary=False, wantAck=wantAck)
if not success:
numFail = numFail + 1
logging.error(
f"Test {testNumber} failed, expected packet not received ({numFail} failures so far)"
)
f"Test {testNumber} failed, expected packet not received ({numFail} failures so far)")
else:
numSuccess = numSuccess + 1
logging.info(
f"Test {testNumber} succeeded {numSuccess} successes {numFail} failures so far"
)
f"Test {testNumber} succeeded {numSuccess} successes {numFail} failures so far")
time.sleep(1)
@@ -146,7 +140,7 @@ def openDebugLog(portName):
"""Open the debug log file"""
debugname = "log" + portName.replace("/", "_")
logging.info(f"Writing serial debugging to {debugname}")
return open(debugname, "w+", buffering=1, encoding="utf8")
return open(debugname, 'w+', buffering=1, encoding='utf8')
def testAll(numTests=5):
@@ -157,22 +151,14 @@ def testAll(numTests=5):
"""
ports = meshtastic.util.findPorts(True)
if len(ports) < 2:
meshtastic.util.our_exit(
"Warning: Must have at least two devices connected to USB."
)
meshtastic.util.our_exit("Warning: Must have at least two devices connected to USB.")
pub.subscribe(onConnection, "meshtastic.connection")
pub.subscribe(onReceive, "meshtastic.receive")
# pylint: disable=W0603
global interfaces
interfaces = list(
map(
lambda port: SerialInterface(
port, debugOut=openDebugLog(port), connectNow=True
),
ports,
)
)
interfaces = list(map(lambda port: SerialInterface(
port, debugOut=openDebugLog(port), connectNow=True), ports))
logging.info("Ports opened, starting test")
result = testThread(numTests)

View File

@@ -1,12 +1,11 @@
"""Common pytest code (place for fixtures)."""
import argparse
from unittest.mock import MagicMock
from unittest.mock import MagicMock
import pytest
from meshtastic.__main__ import Globals
from ..mesh_interface import MeshInterface
@@ -23,34 +22,36 @@ def reset_globals():
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,
}
}
'!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,
}
}
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

View File

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

View File

@@ -6,24 +6,19 @@ 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"
)
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"
)
return_value, _ = subprocess.getstatusoutput('source venv/bin/activate; python3 examples/hello_world_serial.py hello')
assert return_value == 1
_, err = capsys.readouterr()
assert err == ""
assert err == ''
# TODO: Why does this not work?
# assert out == 'Warning: No Meshtastic devices detected.'

View File

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

View File

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

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,16 @@
"""Meshtastic unit tests for mesh_interface.py"""
import logging
import re
from unittest.mock import MagicMock, patch
import logging
from unittest.mock import patch, MagicMock
import pytest
from .. import mesh_pb2
from ..__init__ import BROADCAST_ADDR, LOCAL_ADDR
from ..mesh_interface import MeshInterface
from ..node import Node
# TODO
# from ..config import Config
from .. import mesh_pb2
from ..__init__ import LOCAL_ADDR, BROADCAST_ADDR
from ..radioconfig_pb2 import RadioConfig
from ..util import Timeout
@@ -21,24 +19,24 @@ from ..util import Timeout
def test_MeshInterface(capsys):
"""Test that we can instantiate a MeshInterface"""
iface = MeshInterface(noProto=True)
anode = Node("foo", "bar")
anode = Node('foo', 'bar')
nodes = {
"!9388f81c": {
"num": 2475227164,
"user": {
"id": "!9388f81c",
"longName": "Unknown f81c",
"shortName": "?1C",
"macaddr": "RBeTiPgc",
"hwModel": "TBEAM",
'!9388f81c': {
'num': 2475227164,
'user': {
'id': '!9388f81c',
'longName': 'Unknown f81c',
'shortName': '?1C',
'macaddr': 'RBeTiPgc',
'hwModel': 'TBEAM'
},
"position": {},
"lastHeard": 1640204888,
'position': {},
'lastHeard': 1640204888
}
}
iface.nodesByNum = {1: anode}
iface.nodesByNum = {1: anode }
iface.nodes = nodes
myInfo = MagicMock()
@@ -47,15 +45,15 @@ def test_MeshInterface(capsys):
iface.showInfo()
iface.localNode.showInfo()
iface.showNodes()
iface.sendText("hello")
iface.sendText('hello')
iface.close()
out, err = capsys.readouterr()
assert re.search(r"Owner: None \(None\)", out, re.MULTILINE)
assert re.search(r"Nodes", out, re.MULTILINE)
assert re.search(r"Preferences", out, re.MULTILINE)
assert re.search(r"Channels", out, re.MULTILINE)
assert re.search(r"Primary channel URL", out, re.MULTILINE)
assert err == ""
assert re.search(r'Owner: None \(None\)', out, re.MULTILINE)
assert re.search(r'Nodes', out, re.MULTILINE)
assert re.search(r'Preferences', out, re.MULTILINE)
assert re.search(r'Channels', out, re.MULTILINE)
assert re.search(r'Primary channel URL', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
@@ -66,7 +64,7 @@ def test_getMyUser(iface_with_nodes):
iface.myInfo.my_node_num = 2475227164
myuser = iface.getMyUser()
assert myuser is not None
assert myuser["id"] == "!9388f81c"
assert myuser["id"] == '!9388f81c'
@pytest.mark.unit
@@ -76,7 +74,7 @@ def test_getLongName(iface_with_nodes):
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
mylongname = iface.getLongName()
assert mylongname == "Unknown f81c"
assert mylongname == 'Unknown f81c'
@pytest.mark.unit
@@ -86,7 +84,7 @@ def test_getShortName(iface_with_nodes):
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
myshortname = iface.getShortName()
assert myshortname == "?1C"
assert myshortname == '?1C'
@pytest.mark.unit
@@ -97,24 +95,24 @@ def test_handlePacketFromRadio_no_from(capsys):
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 == ""
assert re.search(r'Device returned a packet we sent, ignoring', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
@pytest.mark.usefixtures("reset_globals")
def test_handlePacketFromRadio_with_a_portnum(caplog):
"""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.
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.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)
assert re.search(r'Not populating fromId', caplog.text, re.MULTILINE)
@pytest.mark.unit
@@ -123,10 +121,10 @@ def test_handlePacketFromRadio_no_portnum(caplog):
"""Test _handlePacketFromRadio without a portnum"""
iface = MeshInterface(noProto=True)
meshPacket = mesh_pb2.MeshPacket()
meshPacket.decoded.payload = b""
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)
assert re.search(r'Not populating fromId', caplog.text, re.MULTILINE)
@pytest.mark.unit
@@ -145,10 +143,10 @@ def test_getNode_not_local(caplog):
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")
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)
assert re.search(r'About to requestConfig', caplog.text, re.MULTILINE)
@pytest.mark.unit
@@ -158,14 +156,14 @@ def test_getNode_not_local_timeout(capsys):
iface = MeshInterface(noProto=True)
anode = MagicMock(autospec=Node)
anode.waitForConfig.return_value = False
with patch("meshtastic.node.Node", return_value=anode):
with patch('meshtastic.node.Node', return_value=anode):
with pytest.raises(SystemExit) as pytest_wrapped_e:
iface.getNode("bar2")
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 == ""
assert re.match(r'Error: Timed out waiting for node config', out)
assert err == ''
@pytest.mark.unit
@@ -176,37 +174,35 @@ def test_sendPosition(caplog):
with caplog.at_level(logging.DEBUG):
iface.sendPosition()
iface.close()
assert re.search(r"p.time:", caplog.text, re.MULTILINE)
assert re.search(r'p.time:', caplog.text, re.MULTILINE)
# TODO
# @pytest.mark.unit
# @pytest.mark.usefixtures("reset_globals")
# def test_close_with_heartbeatTimer(caplog):
# """Test close() with heartbeatTimer"""
# iface = MeshInterface(noProto=True)
# anode = Node('foo', 'bar')
# aconfig = Config()
# aonfig.preferences.phone_timeout_secs = 10
# anode.config = aconfig
# iface.localNode = anode
# assert iface.heartbeatTimer is None
# with caplog.at_level(logging.DEBUG):
# iface._startHeartbeat()
# assert iface.heartbeatTimer is not None
# iface.close()
@pytest.mark.unit
@pytest.mark.usefixtures("reset_globals")
def test_close_with_heartbeatTimer(caplog):
"""Test close() with heartbeatTimer"""
iface = MeshInterface(noProto=True)
anode = Node('foo', 'bar')
radioConfig = RadioConfig()
radioConfig.preferences.phone_timeout_secs = 10
anode.radioConfig = radioConfig
iface.localNode = anode
assert iface.heartbeatTimer is None
with caplog.at_level(logging.DEBUG):
iface._startHeartbeat()
assert iface.heartbeatTimer is not None
iface.close()
# TODO
# @pytest.mark.unit
# @pytest.mark.usefixtures("reset_globals")
# def test_handleFromRadio_empty_payload(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
@pytest.mark.usefixtures("reset_globals")
def test_handleFromRadio_empty_payload(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
@@ -217,21 +213,22 @@ def test_handleFromRadio_with_my_info(caplog):
# 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
# has_wifi: true
# }
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"
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"max_channels: 8", caplog.text, re.MULTILINE)
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
@@ -258,16 +255,16 @@ def test_handleFromRadio_with_node_info(caplog, capsys):
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)
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 == ""
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()
@@ -282,16 +279,16 @@ def test_handleFromRadio_with_node_info_tbeam1(caplog, capsys):
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)
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 == ""
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()
@@ -313,8 +310,8 @@ def test_MeshInterface_sendToRadioImpl(caplog):
"""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._sendToRadioImpl('foo')
assert re.search(r'Subclass must provide toradio', caplog.text, re.MULTILINE)
iface.close()
@@ -324,8 +321,8 @@ def test_MeshInterface_sendToRadio_no_proto(caplog):
"""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._sendToRadioImpl('foo')
assert re.search(r'Subclass must provide toradio', caplog.text, re.MULTILINE)
iface.close()
@@ -334,22 +331,22 @@ def test_MeshInterface_sendToRadio_no_proto(caplog):
def test_sendData_too_long(caplog):
"""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."
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 re.search('Data payload too big', caplog.text, re.MULTILINE)
assert pytest_wrapped_e.type == Exception
iface.close()
@@ -360,10 +357,10 @@ def test_sendData_unknown_app(capsys):
"""Test sendData when unknown app"""
iface = MeshInterface(noProto=True)
with pytest.raises(SystemExit) as pytest_wrapped_e:
iface.sendData(b"hello", portNum=0)
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 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
@@ -375,9 +372,9 @@ def test_sendPosition_with_a_position(caplog):
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)
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
@@ -386,10 +383,10 @@ def test_sendPacket_with_no_destination(capsys):
"""Test _sendPacket()"""
iface = MeshInterface(noProto=True)
with pytest.raises(SystemExit) as pytest_wrapped_e:
iface._sendPacket(b"", destinationId=None)
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 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
@@ -402,7 +399,7 @@ def test_sendPacket_with_destination_as_int(caplog):
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)
assert re.search(r'Not sending packet', caplog.text, re.MULTILINE)
@pytest.mark.unit
@@ -412,8 +409,8 @@ def test_sendPacket_with_destination_starting_with_a_bang(caplog):
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)
iface._sendPacket(meshPacket, destinationId='!1234')
assert re.search(r'Not sending packet', caplog.text, re.MULTILINE)
@pytest.mark.unit
@@ -424,7 +421,7 @@ def test_sendPacket_with_destination_as_BROADCAST_ADDR(caplog):
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)
assert re.search(r'Not sending packet', caplog.text, re.MULTILINE)
@pytest.mark.unit
@@ -436,8 +433,8 @@ def test_sendPacket_with_destination_as_LOCAL_ADDR_no_myInfo(capsys):
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 re.search(r'Warning: No myInfo', out, re.MULTILINE)
assert err == ''
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
@@ -453,7 +450,7 @@ def test_sendPacket_with_destination_as_LOCAL_ADDR_with_myInfo(caplog):
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)
assert re.search(r'Not sending packet', caplog.text, re.MULTILINE)
@pytest.mark.unit
@@ -463,12 +460,12 @@ def test_sendPacket_with_destination_is_blank_with_nodes(capsys, iface_with_node
iface = iface_with_nodes
meshPacket = mesh_pb2.MeshPacket()
with pytest.raises(SystemExit) as pytest_wrapped_e:
iface._sendPacket(meshPacket, destinationId="")
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 == ""
assert re.match(r'Warning: NodeId not found in DB', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
@@ -479,8 +476,8 @@ def test_sendPacket_with_destination_is_blank_without_nodes(caplog, iface_with_n
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)
iface._sendPacket(meshPacket, destinationId='')
assert re.search(r'Warning: There were no self.nodes.', caplog.text, re.MULTILINE)
@pytest.mark.unit
@@ -489,7 +486,7 @@ def test_getMyNodeInfo():
"""Test getMyNodeInfo()"""
iface = MeshInterface(noProto=True)
anode = iface.getNode(LOCAL_ADDR)
iface.nodesByNum = {1: anode}
iface.nodesByNum = {1: anode }
assert iface.nodesByNum.get(1) == anode
myInfo = MagicMock()
iface.myInfo = myInfo
@@ -509,10 +506,8 @@ def test_generatePacketId(capsys):
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 re.search(r'Not connected yet, can not generate packet', out, re.MULTILINE)
assert err == ''
assert pytest_wrapped_e.type == Exception
@@ -543,12 +538,10 @@ def test_fixupPosition():
iface = MeshInterface(noProto=True)
pos = {"latitudeI": 1010000000, "longitudeI": 1020000000}
newpos = iface._fixupPosition(pos)
assert newpos == {
"latitude": 101.0,
"latitudeI": 1010000000,
"longitude": 102.0,
"longitudeI": 1020000000,
}
assert newpos == {"latitude": 101.0,
"latitudeI": 1010000000,
"longitude": 102.0,
"longitudeI": 1020000000}
@pytest.mark.unit
@@ -558,7 +551,7 @@ def test_nodeNumToId(iface_with_nodes):
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
someid = iface._nodeNumToId(2475227164)
assert someid == "!9388f81c"
assert someid == '!9388f81c'
@pytest.mark.unit
@@ -577,8 +570,8 @@ def test_nodeNumToId_to_all(iface_with_nodes):
"""Test _nodeNumToId()"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
someid = iface._nodeNumToId(0xFFFFFFFF)
assert someid == "^all"
someid = iface._nodeNumToId(0xffffffff)
assert someid == '^all'
@pytest.mark.unit
@@ -588,7 +581,7 @@ def test_getOrCreateByNum_minimal(iface_with_nodes):
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
tmp = iface._getOrCreateByNum(123)
assert tmp == {"num": 123}
assert tmp == {'num': 123}
@pytest.mark.unit
@@ -598,7 +591,7 @@ def test_getOrCreateByNum_not_found(iface_with_nodes):
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
with pytest.raises(Exception) as pytest_wrapped_e:
iface._getOrCreateByNum(0xFFFFFFFF)
iface._getOrCreateByNum(0xffffffff)
assert pytest_wrapped_e.type == Exception
@@ -609,15 +602,14 @@ def test_getOrCreateByNum(iface_with_nodes):
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
tmp = iface._getOrCreateByNum(2475227164)
assert tmp["num"] == 2475227164
assert tmp['num'] == 2475227164
# TODO
# @pytest.mark.unit
# def test_enter():
# """Test __enter__()"""
# iface = MeshInterface(noProto=True)
# assert iface == iface.__enter__()
@pytest.mark.unit
def test_enter():
"""Test __enter__()"""
iface = MeshInterface(noProto=True)
assert iface == iface.__enter__()
@pytest.mark.unit
@@ -625,13 +617,9 @@ def test_exit_with_exception(caplog):
"""Test __exit__()"""
iface = MeshInterface(noProto=True)
with caplog.at_level(logging.ERROR):
iface.__exit__("foo", "bar", "baz")
assert re.search(
r"An exception of type foo with value bar has occurred",
caplog.text,
re.MULTILINE,
)
assert re.search(r"Traceback: baz", caplog.text, re.MULTILINE)
iface.__exit__('foo', 'bar', 'baz')
assert re.search(r'An exception of type foo with value bar has occurred', caplog.text, re.MULTILINE)
assert re.search(r'Traceback: baz', caplog.text, re.MULTILINE)
@pytest.mark.unit
@@ -655,10 +643,8 @@ def test_waitForConfig(capsys):
iface.waitForConfig()
assert pytest_wrapped_e.type == Exception
out, err = capsys.readouterr()
assert re.search(
r"Exception: Timed out waiting for interface config", err, re.MULTILINE
)
assert out == ""
assert re.search(r'Exception: Timed out waiting for interface config', err, re.MULTILINE)
assert out == ''
@pytest.mark.unit
@@ -670,8 +656,8 @@ def test_waitConnected_raises_an_exception(capsys):
iface._waitConnected(0.01)
assert pytest_wrapped_e.type == Exception
out, err = capsys.readouterr()
assert re.search(r"warn about something", err, re.MULTILINE)
assert out == ""
assert re.search(r'warn about something', err, re.MULTILINE)
assert out == ''
@pytest.mark.unit
@@ -682,5 +668,5 @@ def test_waitConnected_isConnected_timeout(capsys):
iface._waitConnected(0.01)
assert pytest_wrapped_e.type == Exception
out, err = capsys.readouterr()
assert re.search(r"warn about something", err, re.MULTILINE)
assert out == ""
assert re.search(r'warn about something', err, re.MULTILINE)
assert out == ''

View File

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,8 @@
import logging
import re
from unittest.mock import MagicMock, patch
from unittest.mock import patch, MagicMock
import pytest
from ..remote_hardware import RemoteHardwareClient, onGPIOreceive
@@ -23,25 +23,25 @@ def test_RemoteHardwareClient():
def test_onGPIOreceive(capsys):
"""Test onGPIOreceive"""
iface = MagicMock(autospec=SerialInterface)
packet = {"decoded": {"remotehw": {"type": "foo", "gpioValue": "4096"}}}
packet = {'decoded': {'remotehw': {'typ': 'foo', 'gpioValue': '4096' }}}
onGPIOreceive(packet, iface)
out, err = capsys.readouterr()
assert re.search(r"Received RemoteHardware", out)
assert err == ""
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:
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 re.search(r'Warning: No channel named', out)
assert err == ""
@@ -51,8 +51,8 @@ def test_readGPIOs(caplog):
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)
rhw.readGPIOs('0x10', 123)
assert re.search(r'readGPIOs', caplog.text, re.MULTILINE)
iface.close()
@@ -62,8 +62,8 @@ def test_writeGPIOs(caplog):
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)
rhw.writeGPIOs('0x10', 123, 1)
assert re.search(r'writeGPIOs', caplog.text, re.MULTILINE)
iface.close()
@@ -73,8 +73,8 @@ def test_watchGPIOs(caplog):
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)
rhw.watchGPIOs('0x10', 123)
assert re.search(r'watchGPIOs', caplog.text, re.MULTILINE)
iface.close()
@@ -82,11 +82,11 @@ def test_watchGPIOs(caplog):
def test_sendHardware_no_nodeid(capsys):
"""Test sending no nodeid to _sendHardware()"""
iface = MagicMock(autospec=SerialInterface)
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
with pytest.raises(SystemExit) as pytest_wrapped_e:
rhw = RemoteHardwareClient(mo)
rhw._sendHardware(None, None)
assert pytest_wrapped_e.type == SystemExit
out, err = capsys.readouterr()
assert re.search(r"Warning: Must use a destination node ID", out)
assert err == ""
assert re.search(r'Warning: Must use a destination node ID', out)
assert err == ''

View File

@@ -1,23 +1,21 @@
"""Meshtastic unit tests for serial_interface.py"""
import re
from unittest.mock import mock_open, patch
from unittest.mock import patch, mock_open
import pytest
from ..serial_interface import SerialInterface
@pytest.mark.unit
@patch("time.sleep")
@patch("termios.tcsetattr")
@patch("termios.tcgetattr")
@patch("builtins.open", new_callable=mock_open, read_data="data")
@patch("serial.Serial")
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
def test_SerialInterface_single_port(
mocked_findPorts, mocked_serial, mocked_open, mock_get, mock_set, mock_sleep, capsys
):
@patch('serial.Serial')
@patch('meshtastic.util.findPorts', return_value=['/dev/ttyUSBfake'])
def test_SerialInterface_single_port(mocked_findPorts, mocked_serial, mocked_open, mock_get, mock_set, mock_sleep, capsys):
"""Test that we can instantiate a SerialInterface with a single port"""
iface = SerialInterface(noProto=True)
iface.showInfo()
@@ -30,15 +28,15 @@ def test_SerialInterface_single_port(
mock_set.assert_called()
mock_sleep.assert_called()
out, err = capsys.readouterr()
assert re.search(r"Nodes in mesh", out, re.MULTILINE)
assert re.search(r"Preferences", out, re.MULTILINE)
assert re.search(r"Channels", out, re.MULTILINE)
assert re.search(r"Primary channel", out, re.MULTILINE)
assert err == ""
assert re.search(r'Nodes in mesh', out, re.MULTILINE)
assert re.search(r'Preferences', out, re.MULTILINE)
assert re.search(r'Channels', out, re.MULTILINE)
assert re.search(r'Primary channel', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
@patch("meshtastic.util.findPorts", return_value=[])
@patch('meshtastic.util.findPorts', return_value=[])
def test_SerialInterface_no_ports(mocked_findPorts, capsys):
"""Test that we can instantiate a SerialInterface with no ports"""
with pytest.raises(SystemExit) as pytest_wrapped_e:
@@ -47,14 +45,12 @@ def test_SerialInterface_no_ports(mocked_findPorts, capsys):
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r"Warning: No Meshtastic devices detected", out, re.MULTILINE)
assert err == ""
assert re.search(r'Warning: No Meshtastic devices detected', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
@patch(
"meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake1", "/dev/ttyUSBfake2"]
)
@patch('meshtastic.util.findPorts', return_value=['/dev/ttyUSBfake1', '/dev/ttyUSBfake2'])
def test_SerialInterface_multiple_ports(mocked_findPorts, capsys):
"""Test that we can instantiate a SerialInterface with two ports"""
with pytest.raises(SystemExit) as pytest_wrapped_e:
@@ -63,5 +59,5 @@ def test_SerialInterface_multiple_ports(mocked_findPorts, capsys):
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r"Warning: Multiple serial ports were detected", out, re.MULTILINE)
assert err == ""
assert re.search(r'Warning: Multiple serial ports were detected', out, re.MULTILINE)
assert err == ''

View File

@@ -1,9 +1,9 @@
"""Meshtastic smoke tests with a single device via USB"""
import os
import platform
import re
import subprocess
import time
import platform
import os
# Do not like using hard coded sleeps, but it probably makes
# sense to pause for the radio at apprpriate times
@@ -19,7 +19,7 @@ PAUSE_AFTER_REBOOT = 7
@pytest.mark.smoke1
def test_smoke1_reboot():
"""Test reboot"""
return_value, _ = subprocess.getstatusoutput("meshtastic --reboot")
return_value, _ = subprocess.getstatusoutput('meshtastic --reboot')
assert return_value == 0
# pause for the radio to reset (10 seconds for the pause, and a few more seconds to be back up)
time.sleep(18)
@@ -28,100 +28,94 @@ def test_smoke1_reboot():
@pytest.mark.smoke1
def test_smoke1_info():
"""Test --info"""
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r"Connected to radio", out)
assert re.search(r"^Owner", out, re.MULTILINE)
assert re.search(r"^My info", out, re.MULTILINE)
assert re.search(r"^Nodes in mesh", out, re.MULTILINE)
assert re.search(r"^Preferences", out, re.MULTILINE)
assert re.search(r"^Channels", out, re.MULTILINE)
assert re.search(r"^ PRIMARY", out, re.MULTILINE)
assert re.search(r"^Primary channel URL", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Owner', out, re.MULTILINE)
assert re.search(r'^My info', out, re.MULTILINE)
assert re.search(r'^Nodes in mesh', out, re.MULTILINE)
assert re.search(r'^Preferences', out, re.MULTILINE)
assert re.search(r'^Channels', out, re.MULTILINE)
assert re.search(r'^ PRIMARY', out, re.MULTILINE)
assert re.search(r'^Primary channel URL', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_sendping():
"""Test --sendping"""
return_value, out = subprocess.getstatusoutput("meshtastic --sendping")
assert re.match(r"Connected to radio", out)
assert re.search(r"^Sending ping message", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --sendping')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Sending ping message', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_get_with_invalid_setting():
"""Test '--get a_bad_setting'."""
return_value, out = subprocess.getstatusoutput("meshtastic --get a_bad_setting")
assert re.search(r"Choices in sorted order", out)
return_value, out = subprocess.getstatusoutput('meshtastic --get a_bad_setting')
assert re.search(r'Choices in sorted order', out)
assert return_value == 0
@pytest.mark.smoke1
def test_set_with_invalid_setting():
"""Test '--set a_bad_setting'."""
return_value, out = subprocess.getstatusoutput("meshtastic --set a_bad_setting foo")
assert re.search(r"Choices in sorted order", out)
return_value, out = subprocess.getstatusoutput('meshtastic --set a_bad_setting foo')
assert re.search(r'Choices in sorted order', out)
assert return_value == 0
@pytest.mark.smoke1
def test_ch_set_with_invalid_settingpatch_find_ports():
"""Test '--ch-set with a_bad_setting'."""
return_value, out = subprocess.getstatusoutput(
"meshtastic --ch-set invalid_setting foo --ch-index 0"
)
assert re.search(r"Choices in sorted order", out)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set invalid_setting foo --ch-index 0')
assert re.search(r'Choices in sorted order', out)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_pos_fields():
"""Test --pos-fields (with some values POS_ALTITUDE POS_ALT_MSL POS_BATTERY)"""
return_value, out = subprocess.getstatusoutput(
"meshtastic --pos-fields POS_ALTITUDE POS_ALT_MSL POS_BATTERY"
)
assert re.match(r"Connected to radio", out)
assert re.search(r"^Setting position fields to 35", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --pos-fields POS_ALTITUDE POS_ALT_MSL POS_BATTERY')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Setting position fields to 35', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --pos-fields")
assert re.match(r"Connected to radio", out)
assert re.search(r"POS_ALTITUDE", out, re.MULTILINE)
assert re.search(r"POS_ALT_MSL", out, re.MULTILINE)
assert re.search(r"POS_BATTERY", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --pos-fields')
assert re.match(r'Connected to radio', out)
assert re.search(r'POS_ALTITUDE', out, re.MULTILINE)
assert re.search(r'POS_ALT_MSL', out, re.MULTILINE)
assert re.search(r'POS_BATTERY', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_test_with_arg_but_no_hardware():
"""Test --test
Note: Since only one device is connected, it will not do much.
Note: Since only one device is connected, it will not do much.
"""
return_value, out = subprocess.getstatusoutput("meshtastic --test")
assert re.search(r"^Warning: Must have at least two devices", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --test')
assert re.search(r'^Warning: Must have at least two devices', out, re.MULTILINE)
assert return_value == 1
@pytest.mark.smoke1
def test_smoke1_debug():
"""Test --debug"""
return_value, out = subprocess.getstatusoutput("meshtastic --info --debug")
assert re.search(r"^Owner", out, re.MULTILINE)
assert re.search(r"^DEBUG file", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info --debug')
assert re.search(r'^Owner', out, re.MULTILINE)
assert re.search(r'^DEBUG file', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_seriallog_to_file():
"""Test --seriallog to a file creates a file"""
filename = "tmpoutput.txt"
filename = 'tmpoutput.txt'
if os.path.exists(f"{filename}"):
os.remove(f"{filename}")
return_value, _ = subprocess.getstatusoutput(
f"meshtastic --info --seriallog {filename}"
)
return_value, _ = subprocess.getstatusoutput(f'meshtastic --info --seriallog {filename}')
assert os.path.exists(f"{filename}")
assert return_value == 0
os.remove(f"{filename}")
@@ -130,10 +124,10 @@ def test_smoke1_seriallog_to_file():
@pytest.mark.smoke1
def test_smoke1_qr():
"""Test --qr"""
filename = "tmpqr"
filename = 'tmpqr'
if os.path.exists(f"{filename}"):
os.remove(f"{filename}")
return_value, _ = subprocess.getstatusoutput(f"meshtastic --qr > {filename}")
return_value, _ = subprocess.getstatusoutput(f'meshtastic --qr > {filename}')
assert os.path.exists(f"{filename}")
# not really testing that a valid qr code is created, just that the file size
# is reasonably big enough for a qr code
@@ -145,20 +139,20 @@ def test_smoke1_qr():
@pytest.mark.smoke1
def test_smoke1_nodes():
"""Test --nodes"""
return_value, out = subprocess.getstatusoutput("meshtastic --nodes")
assert re.match(r"Connected to radio", out)
if platform.system() != "Windows":
assert re.search(r" User ", out, re.MULTILINE)
assert re.search(r" 1 ", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --nodes')
assert re.match(r'Connected to radio', out)
if platform.system() != 'Windows':
assert re.search(r' User ', out, re.MULTILINE)
assert re.search(r' 1 ', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_send_hello():
"""Test --sendtext hello"""
return_value, out = subprocess.getstatusoutput("meshtastic --sendtext hello")
assert re.match(r"Connected to radio", out)
assert re.search(r"^Sending text message hello to \^all", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --sendtext hello')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Sending text message hello to \^all', out, re.MULTILINE)
assert return_value == 0
@@ -170,29 +164,55 @@ def test_smoke1_port():
# hopefully there is just one
assert len(ports) == 1
port = ports[0]
return_value, out = subprocess.getstatusoutput(f"meshtastic --port {port} --info")
assert re.match(r"Connected to radio", out)
assert re.search(r"^Owner", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput(f'meshtastic --port {port} --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Owner', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_set_is_router_true():
"""Test --set is_router true"""
return_value, out = subprocess.getstatusoutput('meshtastic --set is_router true')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set is_router to true', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --get is_router')
assert re.search(r'^is_router: True', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_set_location_info():
"""Test --setlat, --setlon and --setalt"""
return_value, out = subprocess.getstatusoutput(
"meshtastic --setlat 32.7767 --setlon -96.7970 --setalt 1337"
)
assert re.match(r"Connected to radio", out)
assert re.search(r"^Fixing altitude", out, re.MULTILINE)
assert re.search(r"^Fixing latitude", out, re.MULTILINE)
assert re.search(r"^Fixing longitude", out, re.MULTILINE)
"""Test --setlat, --setlon and --setalt """
return_value, out = subprocess.getstatusoutput('meshtastic --setlat 32.7767 --setlon -96.7970 --setalt 1337')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Fixing altitude', out, re.MULTILINE)
assert re.search(r'^Fixing latitude', out, re.MULTILINE)
assert re.search(r'^Fixing longitude', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out2 = subprocess.getstatusoutput("meshtastic --info")
assert re.search(r"1337", out2, re.MULTILINE)
assert re.search(r"32.7767", out2, re.MULTILINE)
assert re.search(r"-96.797", out2, re.MULTILINE)
return_value, out2 = subprocess.getstatusoutput('meshtastic --info')
assert re.search(r'1337', out2, re.MULTILINE)
assert re.search(r'32.7767', out2, re.MULTILINE)
assert re.search(r'-96.797', out2, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_set_is_router_false():
"""Test --set is_router false"""
return_value, out = subprocess.getstatusoutput('meshtastic --set is_router false')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set is_router to false', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --get is_router')
assert re.search(r'^is_router: False', out, re.MULTILINE)
assert return_value == 0
@@ -200,80 +220,72 @@ def test_smoke1_set_location_info():
def test_smoke1_set_owner():
"""Test --set-owner name"""
# make sure the owner is not Joe
return_value, out = subprocess.getstatusoutput("meshtastic --set-owner Bob")
assert re.match(r"Connected to radio", out)
assert re.search(r"^Setting device owner to Bob", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --set-owner Bob')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Setting device owner to Bob', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert not re.search(r"Owner: Joe", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert not re.search(r'Owner: Joe', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --set-owner Joe")
assert re.match(r"Connected to radio", out)
assert re.search(r"^Setting device owner to Joe", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --set-owner Joe')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Setting device owner to Joe', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.search(r"Owner: Joe", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.search(r'Owner: Joe', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_ch_set_modem_config():
"""Test --ch-set modem_config"""
return_value, out = subprocess.getstatusoutput(
"meshtastic --ch-set modem_config MedFast"
)
assert re.search(r"Warning: Need to specify", out, re.MULTILINE)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert not re.search(r"MedFast", out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(
"meshtastic --ch-set modem_config MedFast --ch-index 0"
)
assert re.match(r"Connected to radio", out)
assert re.search(r"^Set modem_config to MedFast", out, re.MULTILINE)
def test_smoke1_set_team():
"""Test --set-team """
# unset the team
return_value, out = subprocess.getstatusoutput('meshtastic --set-team CLEAR')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Setting team to CLEAR', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.search(r"MedFast", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --set-team CYAN')
assert re.search(r'Setting team to CYAN', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.search(r'CYAN', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_ch_values():
"""Test --ch-vlongslow --ch-longslow, --ch-longfast, --ch-mediumslow, --ch-mediumsfast,
--ch-shortslow, and --ch-shortfast arguments
"""Test --ch-longslow, --ch-longfast, --ch-mediumslow, --ch-mediumsfast,
--ch-shortslow, and --ch-shortfast arguments
"""
exp = {
"--ch-vlongslow": '{ "psk": "AQ==" }',
"--ch-longslow": "LongSlow",
"--ch-longfast": "LongFast",
"--ch-medslow": "MedSlow",
"--ch-medfast": "MedFast",
"--ch-shortslow": "ShortSlow",
"--ch-shortfast": "ShortFast",
}
'--ch-longslow': 'Bw125Cr48Sf4096',
'--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'
}
for key, val in exp.items():
print(key, val)
return_value, out = subprocess.getstatusoutput(f"meshtastic {key}")
assert re.match(r"Connected to radio", out)
assert re.search(r"Writing modified channels to device", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput(f'meshtastic {key}')
assert re.match(r'Connected to radio', out)
assert re.search(r'Writing modified channels to device', out, re.MULTILINE)
assert return_value == 0
# pause for the radio (might reboot)
time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.search(val, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
@@ -283,144 +295,132 @@ def test_smoke1_ch_values():
@pytest.mark.smoke1
def test_smoke1_ch_set_name():
"""Test --ch-set name"""
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert not re.search(r"MyChannel", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert not re.search(r'MyChannel', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --ch-set name MyChannel")
assert re.match(r"Connected to radio", out)
assert re.search(r"Warning: Need to specify", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name MyChannel')
assert re.match(r'Connected to radio', out)
assert re.search(r'Warning: Need to specify', out, re.MULTILINE)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(
"meshtastic --ch-set name MyChannel --ch-index 0"
)
assert re.match(r"Connected to radio", out)
assert re.search(r"^Set name to MyChannel", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name MyChannel --ch-index 0')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set name to MyChannel', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.search(r"MyChannel", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.search(r'MyChannel', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_ch_set_downlink_and_uplink():
"""Test -ch-set downlink_enabled X and --ch-set uplink_enabled X"""
return_value, out = subprocess.getstatusoutput(
"meshtastic --ch-set downlink_enabled false --ch-set uplink_enabled false"
)
assert re.match(r"Connected to radio", out)
assert re.search(r"Warning: Need to specify", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled false --ch-set uplink_enabled false')
assert re.match(r'Connected to radio', out)
assert re.search(r'Warning: Need to specify', out, re.MULTILINE)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(
"meshtastic --ch-set downlink_enabled false --ch-set uplink_enabled false --ch-index 0"
)
assert re.match(r"Connected to radio", out)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled false --ch-set uplink_enabled false --ch-index 0')
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert not re.search(r"uplinkEnabled", out, re.MULTILINE)
assert not re.search(r"downlinkEnabled", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert not re.search(r'uplinkEnabled', out, re.MULTILINE)
assert not re.search(r'downlinkEnabled', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(
"meshtastic --ch-set downlink_enabled true --ch-set uplink_enabled true --ch-index 0"
)
assert re.match(r"Connected to radio", out)
assert re.search(r"^Set downlink_enabled to true", out, re.MULTILINE)
assert re.search(r"^Set uplink_enabled to true", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled true --ch-set uplink_enabled true --ch-index 0')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set downlink_enabled to true', out, re.MULTILINE)
assert re.search(r'^Set uplink_enabled to true', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.search(r"uplinkEnabled", out, re.MULTILINE)
assert re.search(r"downlinkEnabled", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.search(r'uplinkEnabled', out, re.MULTILINE)
assert re.search(r'downlinkEnabled', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_ch_add_and_ch_del():
"""Test --ch-add"""
return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing")
assert re.search(r"Writing modified channels to device", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing')
assert re.search(r'Writing modified channels to device', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r"Connected to radio", out)
assert re.search(r"SECONDARY", out, re.MULTILINE)
assert re.search(r"testing", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'SECONDARY', out, re.MULTILINE)
assert re.search(r'testing', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --ch-index 1 --ch-del")
assert re.search(r"Deleting channel 1", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-index 1 --ch-del')
assert re.search(r'Deleting channel 1', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)
# make sure the secondar channel is not there
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r"Connected to radio", out)
assert not re.search(r"SECONDARY", out, re.MULTILINE)
assert not re.search(r"testing", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.match(r'Connected to radio', out)
assert not re.search(r'SECONDARY', out, re.MULTILINE)
assert not re.search(r'testing', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_ch_enable_and_disable():
"""Test --ch-enable and --ch-disable"""
return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing")
assert re.search(r"Writing modified channels to device", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing')
assert re.search(r'Writing modified channels to device', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r"Connected to radio", out)
assert re.search(r"SECONDARY", out, re.MULTILINE)
assert re.search(r"testing", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'SECONDARY', out, re.MULTILINE)
assert re.search(r'testing', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
# ensure they need to specify a --ch-index
return_value, out = subprocess.getstatusoutput("meshtastic --ch-disable")
return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable')
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(
"meshtastic --ch-disable --ch-index 1"
)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable --ch-index 1')
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r"Connected to radio", out)
assert re.search(r"DISABLED", out, re.MULTILINE)
assert re.search(r"testing", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'DISABLED', out, re.MULTILINE)
assert re.search(r'testing', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(
"meshtastic --ch-enable --ch-index 1"
)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-enable --ch-index 1')
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r"Connected to radio", out)
assert re.search(r"SECONDARY", out, re.MULTILINE)
assert re.search(r"testing", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'SECONDARY', out, re.MULTILINE)
assert re.search(r'testing', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 1")
return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1')
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@@ -429,32 +429,32 @@ def test_smoke1_ch_enable_and_disable():
@pytest.mark.smoke1
def test_smoke1_ch_del_a_disabled_non_primary_channel():
"""Test --ch-del will work on a disabled non-primary channel."""
return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing")
assert re.search(r"Writing modified channels to device", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing')
assert re.search(r'Writing modified channels to device', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r"Connected to radio", out)
assert re.search(r"SECONDARY", out, re.MULTILINE)
assert re.search(r"testing", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'SECONDARY', out, re.MULTILINE)
assert re.search(r'testing', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
# ensure they need to specify a --ch-index
return_value, out = subprocess.getstatusoutput("meshtastic --ch-disable")
return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable')
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 1")
return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1')
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r"Connected to radio", out)
assert not re.search(r"DISABLED", out, re.MULTILINE)
assert not re.search(r"SECONDARY", out, re.MULTILINE)
assert not re.search(r"testing", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.match(r'Connected to radio', out)
assert not re.search(r'DISABLED', out, re.MULTILINE)
assert not re.search(r'SECONDARY', out, re.MULTILINE)
assert not re.search(r'testing', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@@ -463,8 +463,8 @@ def test_smoke1_ch_del_a_disabled_non_primary_channel():
@pytest.mark.smoke1
def test_smoke1_attempt_to_delete_primary_channel():
"""Test that we cannot delete the PRIMARY channel."""
return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 0")
assert re.search(r"Warning: Cannot delete primary channel", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 0')
assert re.search(r'Warning: Cannot delete primary channel', out, re.MULTILINE)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@@ -473,10 +473,8 @@ def test_smoke1_attempt_to_delete_primary_channel():
@pytest.mark.smoke1
def test_smoke1_attempt_to_disable_primary_channel():
"""Test that we cannot disable the PRIMARY channel."""
return_value, out = subprocess.getstatusoutput(
"meshtastic --ch-disable --ch-index 0"
)
assert re.search(r"Warning: Cannot enable", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable --ch-index 0')
assert re.search(r'Warning: Cannot enable', out, re.MULTILINE)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@@ -485,10 +483,8 @@ def test_smoke1_attempt_to_disable_primary_channel():
@pytest.mark.smoke1
def test_smoke1_attempt_to_enable_primary_channel():
"""Test that we cannot enable the PRIMARY channel."""
return_value, out = subprocess.getstatusoutput(
"meshtastic --ch-enable --ch-index 0"
)
assert re.search(r"Warning: Cannot enable", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-enable --ch-index 0')
assert re.search(r'Warning: Cannot enable', out, re.MULTILINE)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@@ -497,42 +493,42 @@ def test_smoke1_attempt_to_enable_primary_channel():
@pytest.mark.smoke1
def test_smoke1_ensure_ch_del_second_of_three_channels():
"""Test that when we delete the 2nd of 3 channels, that it deletes the correct channel."""
return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing1")
assert re.match(r"Connected to radio", out)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing1')
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r"Connected to radio", out)
assert re.search(r"SECONDARY", out, re.MULTILINE)
assert re.search(r"testing1", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'SECONDARY', out, re.MULTILINE)
assert re.search(r'testing1', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing2")
assert re.match(r"Connected to radio", out)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing2')
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r"Connected to radio", out)
assert re.search(r"testing2", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'testing2', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 1")
assert re.match(r"Connected to radio", out)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1')
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r"Connected to radio", out)
assert re.search(r"testing2", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'testing2', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 1")
assert re.match(r"Connected to radio", out)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1')
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@@ -541,69 +537,91 @@ def test_smoke1_ensure_ch_del_second_of_three_channels():
@pytest.mark.smoke1
def test_smoke1_ensure_ch_del_third_of_three_channels():
"""Test that when we delete the 3rd of 3 channels, that it deletes the correct channel."""
return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing1")
assert re.match(r"Connected to radio", out)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing1')
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r"Connected to radio", out)
assert re.search(r"SECONDARY", out, re.MULTILINE)
assert re.search(r"testing1", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'SECONDARY', out, re.MULTILINE)
assert re.search(r'testing1', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing2")
assert re.match(r"Connected to radio", out)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing2')
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r"Connected to radio", out)
assert re.search(r"testing2", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'testing2', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 2")
assert re.match(r"Connected to radio", out)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 2')
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r"Connected to radio", out)
assert re.search(r"testing1", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.match(r'Connected to radio', out)
assert re.search(r'testing1', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 1")
assert re.match(r"Connected to radio", out)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1')
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@pytest.mark.smoke1
def test_smoke1_ch_set_modem_config():
"""Test --ch-set modem_config"""
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set modem_config Bw31_25Cr48Sf512')
assert re.search(r'Warning: Need to specify', out, re.MULTILINE)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert not re.search(r'Bw31_25Cr48Sf512', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set modem_config Bw31_25Cr48Sf512 --ch-index 0')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set modem_config to Bw31_25Cr48Sf512', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.search(r'Bw31_25Cr48Sf512', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_seturl_default():
"""Test --seturl with default value"""
# set some channel value so we no longer have a default channel
return_value, out = subprocess.getstatusoutput(
"meshtastic --ch-set name foo --ch-index 0"
)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name foo --ch-index 0')
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
# ensure we no longer have a default primary channel
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert not re.search("CgUYAyIBAQ", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert not re.search('CgUYAyIBAQ', out, re.MULTILINE)
assert return_value == 0
url = "https://www.meshtastic.org/d/#CgUYAyIBAQ"
return_value, out = subprocess.getstatusoutput(f"meshtastic --seturl {url}")
assert re.match(r"Connected to radio", out)
assert re.match(r'Connected to radio', out)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.search("CgUYAyIBAQ", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.search('CgUYAyIBAQ', out, re.MULTILINE)
assert return_value == 0
@@ -613,8 +631,8 @@ def test_smoke1_seturl_invalid_url():
# Note: This url is no longer a valid url.
url = "https://www.meshtastic.org/c/#GAMiENTxuzogKQdZ8Lz_q89Oab8qB0RlZmF1bHQ="
return_value, out = subprocess.getstatusoutput(f"meshtastic --seturl {url}")
assert re.match(r"Connected to radio", out)
assert re.search("Warning: There were no settings", out, re.MULTILINE)
assert re.match(r'Connected to radio', out)
assert re.search('Warning: There were no settings', out, re.MULTILINE)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@@ -623,18 +641,19 @@ def test_smoke1_seturl_invalid_url():
@pytest.mark.smoke1
def test_smoke1_configure():
"""Test --configure"""
_, out = subprocess.getstatusoutput(f"meshtastic --configure example_config.yaml")
assert re.match(r"Connected to radio", out)
assert re.search("^Setting device owner to Bob TBeam", out, re.MULTILINE)
assert re.search("^Fixing altitude at 304 meters", out, re.MULTILINE)
assert re.search("^Fixing latitude at 35.8", out, re.MULTILINE)
assert re.search("^Fixing longitude at -93.8", out, re.MULTILINE)
assert re.search("^Setting device position", out, re.MULTILINE)
assert re.search("^Set region to 1", out, re.MULTILINE)
assert re.search("^Set is_always_powered to true", out, re.MULTILINE)
assert re.search("^Set screen_on_secs to 31536000", out, re.MULTILINE)
assert re.search("^Set wait_bluetooth_secs to 31536000", out, re.MULTILINE)
assert re.search("^Writing modified preferences to device", out, re.MULTILINE)
_ , out = subprocess.getstatusoutput(f"meshtastic --configure example_config.yaml")
assert re.match(r'Connected to radio', out)
assert re.search('^Setting device owner to Bob TBeam', out, re.MULTILINE)
assert re.search('^Fixing altitude at 304 meters', out, re.MULTILINE)
assert re.search('^Fixing latitude at 35.8', out, re.MULTILINE)
assert re.search('^Fixing longitude at -93.8', out, re.MULTILINE)
assert re.search('^Setting device position', out, re.MULTILINE)
assert re.search('^Set region to 1', out, re.MULTILINE)
assert re.search('^Set is_always_powered to true', out, re.MULTILINE)
assert re.search('^Set send_owner_interval to 2', out, re.MULTILINE)
assert re.search('^Set screen_on_secs to 31536000', out, re.MULTILINE)
assert re.search('^Set wait_bluetooth_secs to 31536000', out, re.MULTILINE)
assert re.search('^Writing modified preferences to device', out, re.MULTILINE)
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)
@@ -642,47 +661,41 @@ def test_smoke1_configure():
@pytest.mark.smoke1
def test_smoke1_set_ham():
"""Test --set-ham
Note: Do a factory reset after this setting so it is very short-lived.
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)
return_value, out = subprocess.getstatusoutput('meshtastic --set-ham KI1234')
assert re.search(r'Setting Ham ID', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.search(r"Owner: KI1234", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.search(r'Owner: KI1234', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_set_wifi_settings():
"""Test --set wifi_ssid and --set wifi_password"""
return_value, out = subprocess.getstatusoutput(
'meshtastic --set wifi_ssid "some_ssid" --set wifi_password "temp1234"'
)
assert re.match(r"Connected to radio", out)
assert re.search(r"^Set wifi_ssid to some_ssid", out, re.MULTILINE)
assert re.search(r"^Set wifi_password to temp1234", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --set wifi_ssid "some_ssid" --set wifi_password "temp1234"')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set wifi_ssid to some_ssid', out, re.MULTILINE)
assert re.search(r'^Set wifi_password to temp1234', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(
"meshtastic --get wifi_ssid --get wifi_password"
)
assert re.search(r"^wifi_ssid: some_ssid", out, re.MULTILINE)
assert re.search(r"^wifi_password: sekrit", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --get wifi_ssid --get wifi_password')
assert re.search(r'^wifi_ssid: some_ssid', out, re.MULTILINE)
assert re.search(r'^wifi_password: sekrit', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_factory_reset():
"""Test factory reset"""
return_value, out = subprocess.getstatusoutput(
"meshtastic --set factory_reset true"
)
assert re.match(r"Connected to radio", out)
assert re.search(r"^Set factory_reset to true", out, re.MULTILINE)
assert re.search(r"^Writing modified preferences to device", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --set factory_reset true')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set factory_reset to true', out, re.MULTILINE)
assert re.search(r'^Writing modified preferences to device', out, re.MULTILINE)
assert return_value == 0
# NOTE: The radio may not be responsive after this, may need to do a manual reboot
# by pressing the button

View File

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

View File

@@ -12,14 +12,12 @@ import pytest
@pytest.mark.smokewifi
def test_smokewifi_info():
"""Test --info"""
return_value, out = subprocess.getstatusoutput(
"meshtastic --info --host meshtastic.local"
)
assert re.search(r"^Owner", out, re.MULTILINE)
assert re.search(r"^My info", out, re.MULTILINE)
assert re.search(r"^Nodes in mesh", out, re.MULTILINE)
assert re.search(r"^Preferences", out, re.MULTILINE)
assert re.search(r"^Channels", out, re.MULTILINE)
assert re.search(r"^ PRIMARY", out, re.MULTILINE)
assert re.search(r"^Primary channel URL", out, re.MULTILINE)
return_value, out = subprocess.getstatusoutput('meshtastic --info --host meshtastic.local')
assert re.search(r'^Owner', out, re.MULTILINE)
assert re.search(r'^My info', out, re.MULTILINE)
assert re.search(r'^Nodes in mesh', out, re.MULTILINE)
assert re.search(r'^Preferences', out, re.MULTILINE)
assert re.search(r'^Channels', out, re.MULTILINE)
assert re.search(r'^ PRIMARY', out, re.MULTILINE)
assert re.search(r'^Primary channel URL', out, re.MULTILINE)
assert return_value == 0

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,13 @@
"""Meshtastic unit tests for stream_interface.py"""
import logging
from unittest.mock import MagicMock
import re
from unittest.mock import MagicMock
import pytest
from ..stream_interface import StreamInterface
# import re
@pytest.mark.unit
def test_StreamInterface():
@@ -23,10 +22,10 @@ def test_StreamInterface():
@pytest.mark.usefixtures("reset_globals")
def test_StreamInterface_with_noProto(caplog):
"""Test that we can instantiate a StreamInterface based on nonProto
and we can read/write bytes from a mocked stream
and we can read/write bytes from a mocked stream
"""
stream = MagicMock()
test_data = b"hello"
test_data = b'hello'
stream.read.return_value = test_data
with caplog.at_level(logging.DEBUG):
iface = StreamInterface(noProto=True, connectNow=False)
@@ -36,49 +35,48 @@ def test_StreamInterface_with_noProto(caplog):
assert data == test_data
# TODO
### 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
# @pytest.mark.usefixtures("reset_globals")
# def test_sendToRadioImpl(caplog):
# """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)
## 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
@pytest.mark.usefixtures("reset_globals")
def test_sendToRadioImpl(caplog):
"""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)

View File

@@ -1,8 +1,8 @@
"""Meshtastic unit tests for tcp_interface.py"""
import re
from unittest.mock import patch
from unittest.mock import patch
import pytest
from ..tcp_interface import TCPInterface
@@ -11,18 +11,18 @@ from ..tcp_interface import TCPInterface
@pytest.mark.unit
def test_TCPInterface(capsys):
"""Test that we can instantiate a TCPInterface"""
with patch("socket.socket") as mock_socket:
iface = TCPInterface(hostname="localhost", noProto=True)
with patch('socket.socket') as mock_socket:
iface = TCPInterface(hostname='localhost', noProto=True)
iface.myConnect()
iface.showInfo()
iface.localNode.showInfo()
out, err = capsys.readouterr()
assert re.search(r"Owner: None \(None\)", out, re.MULTILINE)
assert re.search(r"Nodes", out, re.MULTILINE)
assert re.search(r"Preferences", out, re.MULTILINE)
assert re.search(r"Channels", out, re.MULTILINE)
assert re.search(r"Primary channel URL", out, re.MULTILINE)
assert err == ""
assert re.search(r'Owner: None \(None\)', out, re.MULTILINE)
assert re.search(r'Nodes', out, re.MULTILINE)
assert re.search(r'Preferences', out, re.MULTILINE)
assert re.search(r'Channels', out, re.MULTILINE)
assert re.search(r'Primary channel URL', out, re.MULTILINE)
assert err == ''
assert mock_socket.called
iface.close()
@@ -34,12 +34,10 @@ def test_TCPInterface_exception():
def throw_an_exception():
raise ValueError("Fake exception.")
with patch(
"meshtastic.tcp_interface.TCPInterface._socket_shutdown"
) as mock_shutdown:
with patch('meshtastic.tcp_interface.TCPInterface._socket_shutdown') as mock_shutdown:
mock_shutdown.side_effect = throw_an_exception
with patch("socket.socket") as mock_socket:
iface = TCPInterface(hostname="localhost", noProto=True)
with patch('socket.socket') as mock_socket:
iface = TCPInterface(hostname='localhost', noProto=True)
iface.myConnect()
iface.close()
assert mock_socket.called
@@ -49,6 +47,6 @@ def test_TCPInterface_exception():
@pytest.mark.unit
def test_TCPInterface_without_connecting():
"""Test that we can instantiate a TCPInterface with connectNow as false"""
with patch("socket.socket"):
iface = TCPInterface(hostname="localhost", noProto=True, connectNow=False)
with patch('socket.socket'):
iface = TCPInterface(hostname='localhost', noProto=True, connectNow=False)
assert iface.socket is None

View File

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

View File

@@ -1,131 +1,114 @@
"""Meshtastic unit tests for util.py"""
import logging
import re
from unittest.mock import patch
import logging
from unittest.mock import patch
import pytest
from meshtastic.util import (fixme, stripnl, pskToString, our_exit,
support_info, genPSK256, fromStr, fromPSK,
quoteBooleans, catchAndIgnore,
remove_keys_from_dict, Timeout, hexstr,
ipstr, readnet_u16, findPorts, convert_mac_addr,
snake_to_camel, camel_to_snake, eliminate_duplicate_port,
is_windows11, active_ports_on_supported_devices)
from meshtastic.supported_device import SupportedDevice
from meshtastic.util import (
Timeout,
active_ports_on_supported_devices,
camel_to_snake,
catchAndIgnore,
convert_mac_addr,
eliminate_duplicate_port,
findPorts,
fixme,
fromPSK,
fromStr,
genPSK256,
hexstr,
ipstr,
is_windows11,
our_exit,
pskToString,
quoteBooleans,
readnet_u16,
remove_keys_from_dict,
snake_to_camel,
stripnl,
support_info,
)
@pytest.mark.unit
def test_genPSK256():
"""Test genPSK256"""
assert genPSK256() != ""
assert genPSK256() != ''
@pytest.mark.unit
def test_fromStr():
"""Test fromStr"""
assert fromStr("") == b""
assert fromStr("0x12") == b"\x12"
assert fromStr("t")
assert fromStr("T")
assert fromStr("true")
assert fromStr("True")
assert fromStr("yes")
assert fromStr("Yes")
assert fromStr("f") is False
assert fromStr("F") is False
assert fromStr("false") is False
assert fromStr("False") is False
assert fromStr("no") is False
assert fromStr("No") is False
assert fromStr("100.01") == 100.01
assert fromStr("123") == 123
assert fromStr("abc") == "abc"
assert fromStr("123456789") == 123456789
assert fromStr('') == b''
assert fromStr('0x12') == b'\x12'
assert fromStr('t')
assert fromStr('T')
assert fromStr('true')
assert fromStr('True')
assert fromStr('yes')
assert fromStr('Yes')
assert fromStr('f') is False
assert fromStr('F') is False
assert fromStr('false') is False
assert fromStr('False') is False
assert fromStr('no') is False
assert fromStr('No') is False
assert fromStr('100.01') == 100.01
assert fromStr('123') == 123
assert fromStr('abc') == 'abc'
assert fromStr('123456789') == 123456789
@pytest.mark.unitslow
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'"
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"""
assert fromPSK("random") != ""
assert fromPSK("none") == b"\x00"
assert fromPSK("default") == b"\x01"
assert fromPSK("simple22") == b"\x17"
assert fromPSK("trash") == "trash"
assert fromPSK('random') != ''
assert fromPSK('none') == b'\x00'
assert fromPSK('default') == b'\x01'
assert fromPSK('simple22') == b'\x17'
assert fromPSK('trash') == 'trash'
@pytest.mark.unit
def test_stripnl():
"""Test stripnl"""
assert stripnl("") == ""
assert stripnl("a\n") == "a"
assert stripnl(" a \n ") == "a"
assert stripnl("a\nb") == "a b"
assert stripnl('') == ''
assert stripnl('a\n') == 'a'
assert stripnl(' a \n ') == 'a'
assert stripnl('a\nb') == 'a b'
@pytest.mark.unit
def test_pskToString_empty_string():
"""Test pskToString empty string"""
assert pskToString("") == "unencrypted"
assert pskToString('') == 'unencrypted'
@pytest.mark.unit
def test_pskToString_string():
"""Test pskToString string"""
assert pskToString("hunter123") == "secret"
assert pskToString('hunter123') == 'secret'
@pytest.mark.unit
def test_pskToString_one_byte_zero_value():
"""Test pskToString one byte that is value of 0"""
assert pskToString(bytes([0x00])) == "unencrypted"
assert pskToString(bytes([0x00])) == 'unencrypted'
@pytest.mark.unitslow
def test_pskToString_one_byte_non_zero_value():
"""Test pskToString one byte that is non-zero"""
assert pskToString(bytes([0x01])) == "default"
assert pskToString(bytes([0x01])) == 'default'
@pytest.mark.unitslow
def test_pskToString_many_bytes():
"""Test pskToString many bytes"""
assert pskToString(bytes([0x02, 0x01])) == "secret"
assert pskToString(bytes([0x02, 0x01])) == 'secret'
@pytest.mark.unit
def test_pskToString_simple():
"""Test pskToString simple"""
assert pskToString(bytes([0x03])) == "simple2"
assert pskToString(bytes([0x03])) == 'simple2'
@pytest.mark.unitslow
@@ -134,8 +117,8 @@ def test_our_exit_zero_return_value(capsys):
with pytest.raises(SystemExit) as pytest_wrapped_e:
our_exit("Warning: Some message", 0)
out, err = capsys.readouterr()
assert re.search(r"Warning: Some message", out, re.MULTILINE)
assert err == ""
assert re.search(r'Warning: Some message', out, re.MULTILINE)
assert err == ''
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 0
@@ -146,8 +129,8 @@ def test_our_exit_non_zero_return_value(capsys):
with pytest.raises(SystemExit) as pytest_wrapped_e:
our_exit("Error: Some message", 1)
out, err = capsys.readouterr()
assert re.search(r"Error: Some message", out, re.MULTILINE)
assert err == ""
assert re.search(r'Error: Some message', out, re.MULTILINE)
assert err == ''
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
@@ -165,23 +148,21 @@ def test_support_info(capsys):
"""Test support_info"""
support_info()
out, err = capsys.readouterr()
assert re.search(r"System", out, re.MULTILINE)
assert re.search(r"Platform", out, re.MULTILINE)
assert re.search(r"Machine", out, re.MULTILINE)
assert re.search(r"Executable", out, re.MULTILINE)
assert err == ""
assert re.search(r'System', out, re.MULTILINE)
assert re.search(r'Platform', out, re.MULTILINE)
assert re.search(r'Machine', out, re.MULTILINE)
assert re.search(r'Executable', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
def test_catchAndIgnore(caplog):
"""Test catchAndIgnore() does not actually throw an exception, but just logs"""
def some_closure():
raise Exception("foo")
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)
assert re.search(r'Exception thrown in something', caplog.text, re.MULTILINE)
@pytest.mark.unitslow
@@ -193,35 +174,35 @@ def test_remove_keys_from_dict_empty_keys_empty_dict():
@pytest.mark.unitslow
def test_remove_keys_from_dict_empty_dict():
"""Test when dict is empty"""
assert not remove_keys_from_dict(("a"), {})
assert not remove_keys_from_dict(('a'), {})
@pytest.mark.unit
def test_remove_keys_from_dict_empty_keys():
"""Test when keys is empty"""
assert remove_keys_from_dict((), {"a": 1}) == {"a": 1}
assert remove_keys_from_dict((), {'a':1}) == {'a':1}
@pytest.mark.unitslow
def test_remove_keys_from_dict():
"""Test remove_keys_from_dict()"""
assert remove_keys_from_dict(("b"), {"a": 1, "b": 2}) == {"a": 1}
assert remove_keys_from_dict(('b'), {'a':1, 'b':2}) == {'a':1}
@pytest.mark.unitslow
def test_remove_keys_from_dict_multiple_keys():
"""Test remove_keys_from_dict()"""
keys = ("a", "b")
adict = {"a": 1, "b": 2, "c": 3}
assert remove_keys_from_dict(keys, adict) == {"c": 3}
keys = ('a', 'b')
adict = {'a': 1, 'b': 2, 'c': 3}
assert remove_keys_from_dict(keys, adict) == {'c':3}
@pytest.mark.unit
def test_remove_keys_from_dict_nested():
"""Test remove_keys_from_dict()"""
keys = "b"
adict = {"a": {"b": 1}, "b": 2, "c": 3}
exp = {"a": {}, "c": 3}
keys = ('b')
adict = {'a': {'b': 1}, 'b': 2, 'c': 3}
exp = {'a': {}, 'c': 3}
assert remove_keys_from_dict(keys, adict) == exp
@@ -229,8 +210,8 @@ def test_remove_keys_from_dict_nested():
def test_Timeout_not_found():
"""Test Timeout()"""
to = Timeout(0.2)
attrs = "foo"
to.waitForSet("bar", attrs)
attrs = ('foo')
to.waitForSet('bar', attrs)
@pytest.mark.unitslow
@@ -238,31 +219,31 @@ def test_Timeout_found():
"""Test Timeout()"""
to = Timeout(0.2)
attrs = ()
to.waitForSet("bar", attrs)
to.waitForSet('bar', attrs)
@pytest.mark.unitslow
def test_hexstr():
"""Test hexstr()"""
assert hexstr(b"123") == "31:32:33"
assert hexstr(b"") == ""
assert hexstr(b'123') == '31:32:33'
assert hexstr(b'') == ''
@pytest.mark.unitslow
def test_ipstr():
"""Test ipstr()"""
assert ipstr(b"1234") == "49.50.51.52"
assert ipstr(b"") == ""
assert ipstr(b'1234') == '49.50.51.52'
assert ipstr(b'') == ''
@pytest.mark.unitslow
def test_readnet_u16():
"""Test readnet_u16()"""
assert readnet_u16(b"123456", 2) == 13108
assert readnet_u16(b'123456', 2) == 13108
@pytest.mark.unitslow
@patch("serial.tools.list_ports.comports", return_value=[])
@patch('serial.tools.list_ports.comports', return_value=[])
def test_findPorts_when_none_found(patch_comports):
"""Test findPorts()"""
assert not findPorts()
@@ -270,134 +251,99 @@ def test_findPorts_when_none_found(patch_comports):
@pytest.mark.unitslow
@patch("serial.tools.list_ports.comports")
@patch('serial.tools.list_ports.comports')
def test_findPorts_when_duplicate_found_and_duplicate_option_used(patch_comports):
"""Test findPorts()"""
class TempPort:
"""temp class for port"""
""" temp class for port"""
def __init__(self, device=None, vid=None):
self.device = device
self.vid = vid
fake1 = TempPort("/dev/cu.usbserial-1430", vid="fake1")
fake2 = TempPort("/dev/cu.wchusbserial1430", vid="fake2")
fake1 = TempPort('/dev/cu.usbserial-1430', vid='fake1')
fake2 = TempPort('/dev/cu.wchusbserial1430', vid='fake2')
patch_comports.return_value = [fake1, fake2]
assert findPorts(eliminate_duplicates=True) == ["/dev/cu.wchusbserial1430"]
assert findPorts(eliminate_duplicates=True) == ['/dev/cu.wchusbserial1430']
patch_comports.assert_called()
@pytest.mark.unitslow
@patch("serial.tools.list_ports.comports")
def test_findPorts_when_duplicate_found_and_duplicate_option_used_ports_reversed(
patch_comports,
):
@patch('serial.tools.list_ports.comports')
def test_findPorts_when_duplicate_found_and_duplicate_option_used_ports_reversed(patch_comports):
"""Test findPorts()"""
class TempPort:
"""temp class for port"""
""" temp class for port"""
def __init__(self, device=None, vid=None):
self.device = device
self.vid = vid
fake1 = TempPort("/dev/cu.usbserial-1430", vid="fake1")
fake2 = TempPort("/dev/cu.wchusbserial1430", vid="fake2")
fake1 = TempPort('/dev/cu.usbserial-1430', vid='fake1')
fake2 = TempPort('/dev/cu.wchusbserial1430', vid='fake2')
patch_comports.return_value = [fake2, fake1]
assert findPorts(eliminate_duplicates=True) == ["/dev/cu.wchusbserial1430"]
assert findPorts(eliminate_duplicates=True) == ['/dev/cu.wchusbserial1430']
patch_comports.assert_called()
@pytest.mark.unitslow
@patch("serial.tools.list_ports.comports")
@patch('serial.tools.list_ports.comports')
def test_findPorts_when_duplicate_found_and_duplicate_option_not_used(patch_comports):
"""Test findPorts()"""
class TempPort:
"""temp class for port"""
""" temp class for port"""
def __init__(self, device=None, vid=None):
self.device = device
self.vid = vid
fake1 = TempPort("/dev/cu.usbserial-1430", vid="fake1")
fake2 = TempPort("/dev/cu.wchusbserial1430", vid="fake2")
fake1 = TempPort('/dev/cu.usbserial-1430', vid='fake1')
fake2 = TempPort('/dev/cu.wchusbserial1430', vid='fake2')
patch_comports.return_value = [fake1, fake2]
assert findPorts() == ["/dev/cu.usbserial-1430", "/dev/cu.wchusbserial1430"]
assert findPorts() == ['/dev/cu.usbserial-1430', '/dev/cu.wchusbserial1430']
patch_comports.assert_called()
@pytest.mark.unitslow
def test_convert_mac_addr():
"""Test convert_mac_addr()"""
assert convert_mac_addr("/c0gFyhb") == "fd:cd:20:17:28:5b"
assert convert_mac_addr("fd:cd:20:17:28:5b") == "fd:cd:20:17:28:5b"
assert convert_mac_addr("") == ""
assert convert_mac_addr('/c0gFyhb') == 'fd:cd:20:17:28:5b'
assert convert_mac_addr('fd:cd:20:17:28:5b') == 'fd:cd:20:17:28:5b'
assert convert_mac_addr('') == ''
@pytest.mark.unit
def test_snake_to_camel():
"""Test snake_to_camel"""
assert snake_to_camel("") == ""
assert snake_to_camel("foo") == "foo"
assert snake_to_camel("foo_bar") == "fooBar"
assert snake_to_camel("fooBar") == "fooBar"
assert snake_to_camel('') == ''
assert snake_to_camel('foo') == 'foo'
assert snake_to_camel('foo_bar') == 'fooBar'
assert snake_to_camel('fooBar') == 'fooBar'
@pytest.mark.unit
def test_camel_to_snake():
"""Test camel_to_snake"""
assert camel_to_snake("") == ""
assert camel_to_snake("foo") == "foo"
assert camel_to_snake("Foo") == "foo"
assert camel_to_snake("fooBar") == "foo_bar"
assert camel_to_snake("fooBarBaz") == "foo_bar_baz"
assert camel_to_snake('') == ''
assert camel_to_snake('foo') == 'foo'
assert camel_to_snake('Foo') == 'foo'
assert camel_to_snake('fooBar') == 'foo_bar'
assert camel_to_snake('fooBarBaz') == 'foo_bar_baz'
@pytest.mark.unit
def test_eliminate_duplicate_port():
"""Test eliminate_duplicate_port()"""
assert not eliminate_duplicate_port([])
assert eliminate_duplicate_port(["/dev/fake"]) == ["/dev/fake"]
assert eliminate_duplicate_port(["/dev/fake", "/dev/fake1"]) == [
"/dev/fake",
"/dev/fake1",
]
assert eliminate_duplicate_port(["/dev/fake", "/dev/fake1", "/dev/fake2"]) == [
"/dev/fake",
"/dev/fake1",
"/dev/fake2",
]
assert eliminate_duplicate_port(
["/dev/cu.usbserial-1430", "/dev/cu.wchusbserial1430"]
) == ["/dev/cu.wchusbserial1430"]
assert eliminate_duplicate_port(
["/dev/cu.wchusbserial1430", "/dev/cu.usbserial-1430"]
) == ["/dev/cu.wchusbserial1430"]
assert eliminate_duplicate_port(
["/dev/cu.SLAB_USBtoUART", "/dev/cu.usbserial-0001"]
) == ["/dev/cu.usbserial-0001"]
assert eliminate_duplicate_port(
["/dev/cu.usbserial-0001", "/dev/cu.SLAB_USBtoUART"]
) == ["/dev/cu.usbserial-0001"]
assert eliminate_duplicate_port(
["/dev/cu.usbmodem11301", "/dev/cu.wchusbserial11301"]
) == ["/dev/cu.wchusbserial11301"]
assert eliminate_duplicate_port(
["/dev/cu.wchusbserial11301", "/dev/cu.usbmodem11301"]
) == ["/dev/cu.wchusbserial11301"]
assert eliminate_duplicate_port(
["/dev/cu.usbmodem53230051441", "/dev/cu.wchusbserial53230051441"]
) == ["/dev/cu.wchusbserial53230051441"]
assert eliminate_duplicate_port(
["/dev/cu.wchusbserial53230051441", "/dev/cu.usbmodem53230051441"]
) == ["/dev/cu.wchusbserial53230051441"]
assert eliminate_duplicate_port(['/dev/fake']) == ['/dev/fake']
assert eliminate_duplicate_port(['/dev/fake', '/dev/fake1']) == ['/dev/fake', '/dev/fake1']
assert eliminate_duplicate_port(['/dev/fake', '/dev/fake1', '/dev/fake2']) == ['/dev/fake', '/dev/fake1', '/dev/fake2']
assert eliminate_duplicate_port(['/dev/cu.usbserial-1430', '/dev/cu.wchusbserial1430']) == ['/dev/cu.wchusbserial1430']
assert eliminate_duplicate_port(['/dev/cu.wchusbserial1430', '/dev/cu.usbserial-1430']) == ['/dev/cu.wchusbserial1430']
assert eliminate_duplicate_port(['/dev/cu.SLAB_USBtoUART', '/dev/cu.usbserial-0001']) == ['/dev/cu.usbserial-0001']
assert eliminate_duplicate_port(['/dev/cu.usbserial-0001', '/dev/cu.SLAB_USBtoUART']) == ['/dev/cu.usbserial-0001']
assert eliminate_duplicate_port(['/dev/cu.usbmodem11301', '/dev/cu.wchusbserial11301']) == ['/dev/cu.wchusbserial11301']
assert eliminate_duplicate_port(['/dev/cu.usbmodem53230051441', '/dev/cu.wchusbserial53230051441']) == ['/dev/cu.wchusbserial53230051441']
assert eliminate_duplicate_port(['/dev/cu.wchusbserial53230051441', '/dev/cu.usbmodem53230051441']) == ['/dev/cu.wchusbserial53230051441']
assert eliminate_duplicate_port(['/dev/cu.wchusbserial11301', '/dev/cu.usbmodem11301']) == ['/dev/cu.wchusbserial11301']
@patch("platform.version", return_value="10.0.22000.194")
@patch("platform.release", return_value="10")
@patch("platform.system", return_value="Windows")
@patch('platform.version', return_value='10.0.22000.194')
@patch('platform.release', return_value='10')
@patch('platform.system', return_value='Windows')
def test_is_windows11_true(patched_platform, patched_release, patched_version):
"""Test is_windows11()"""
assert is_windows11() is True
@@ -406,9 +352,9 @@ def test_is_windows11_true(patched_platform, patched_release, patched_version):
patched_version.assert_called()
@patch("platform.version", return_value="10.0.a2200.foo") # made up
@patch("platform.release", return_value="10")
@patch("platform.system", return_value="Windows")
@patch('platform.version', return_value='10.0.a2200.foo') # made up
@patch('platform.release', return_value='10')
@patch('platform.system', return_value='Windows')
def test_is_windows11_true2(patched_platform, patched_release, patched_version):
"""Test is_windows11()"""
assert is_windows11() is False
@@ -417,9 +363,9 @@ def test_is_windows11_true2(patched_platform, patched_release, patched_version):
patched_version.assert_called()
@patch("platform.version", return_value="10.0.17763") # windows 10 home
@patch("platform.release", return_value="10")
@patch("platform.system", return_value="Windows")
@patch('platform.version', return_value='10.0.17763') # windows 10 home
@patch('platform.release', return_value='10')
@patch('platform.system', return_value='Windows')
def test_is_windows11_false(patched_platform, patched_release, patched_version):
"""Test is_windows11()"""
assert is_windows11() is False
@@ -428,8 +374,8 @@ def test_is_windows11_false(patched_platform, patched_release, patched_version):
patched_version.assert_called()
@patch("platform.release", return_value="8.1")
@patch("platform.system", return_value="Windows")
@patch('platform.release', return_value='8.1')
@patch('platform.system', return_value='Windows')
def test_is_windows11_false_win8_1(patched_platform, patched_release):
"""Test is_windows11()"""
assert is_windows11() is False
@@ -438,7 +384,7 @@ def test_is_windows11_false_win8_1(patched_platform, patched_release):
@pytest.mark.unit
@patch("platform.system", return_value="Linux")
@patch('platform.system', return_value='Linux')
def test_active_ports_on_supported_devices_empty(mock_platform):
"""Test active_ports_on_supported_devices()"""
sds = set()
@@ -447,101 +393,66 @@ def test_active_ports_on_supported_devices_empty(mock_platform):
@pytest.mark.unit
@patch("subprocess.getstatusoutput")
@patch("platform.system", return_value="Linux")
@patch('subprocess.getstatusoutput')
@patch('platform.system', return_value='Linux')
def test_active_ports_on_supported_devices_linux(mock_platform, mock_sp):
"""Test active_ports_on_supported_devices()"""
mock_sp.return_value = (
None,
"crw-rw-rw- 1 root wheel 0x9000000 Feb 8 22:22 /dev/ttyUSBfake",
)
fake_device = SupportedDevice(
name="a", for_firmware="heltec-v2.1", baseport_on_linux="ttyUSB"
)
mock_sp.return_value = (None, 'crw-rw-rw- 1 root wheel 0x9000000 Feb 8 22:22 /dev/ttyUSBfake')
fake_device = SupportedDevice(name='a', for_firmware='heltec-v2.1', baseport_on_linux='ttyUSB')
fake_supported_devices = [fake_device]
assert active_ports_on_supported_devices(fake_supported_devices) == {
"/dev/ttyUSBfake"
}
assert active_ports_on_supported_devices(fake_supported_devices) == {'/dev/ttyUSBfake'}
mock_platform.assert_called()
mock_sp.assert_called()
@pytest.mark.unit
@patch("subprocess.getstatusoutput")
@patch("platform.system", return_value="Darwin")
@patch('subprocess.getstatusoutput')
@patch('platform.system', return_value='Darwin')
def test_active_ports_on_supported_devices_mac(mock_platform, mock_sp):
"""Test active_ports_on_supported_devices()"""
mock_sp.return_value = (
None,
"crw-rw-rw- 1 root wheel 0x9000000 Feb 8 22:22 /dev/cu.usbserial-foo",
)
fake_device = SupportedDevice(
name="a", for_firmware="heltec-v2.1", baseport_on_linux="cu.usbserial-"
)
mock_sp.return_value = (None, 'crw-rw-rw- 1 root wheel 0x9000000 Feb 8 22:22 /dev/cu.usbserial-foo')
fake_device = SupportedDevice(name='a', for_firmware='heltec-v2.1', baseport_on_linux='cu.usbserial-')
fake_supported_devices = [fake_device]
assert active_ports_on_supported_devices(fake_supported_devices) == {
"/dev/cu.usbserial-foo"
}
assert active_ports_on_supported_devices(fake_supported_devices) == {'/dev/cu.usbserial-foo'}
mock_platform.assert_called()
mock_sp.assert_called()
@pytest.mark.unit
@patch("meshtastic.util.detect_windows_port", return_value={"COM2"})
@patch("platform.system", return_value="Windows")
@patch('meshtastic.util.detect_windows_port', return_value={'COM2'})
@patch('platform.system', return_value='Windows')
def test_active_ports_on_supported_devices_win(mock_platform, mock_dwp):
"""Test active_ports_on_supported_devices()"""
fake_device = SupportedDevice(name="a", for_firmware="heltec-v2.1")
fake_device = SupportedDevice(name='a', for_firmware='heltec-v2.1')
fake_supported_devices = [fake_device]
assert active_ports_on_supported_devices(fake_supported_devices) == {"COM2"}
assert active_ports_on_supported_devices(fake_supported_devices) == {'COM2'}
mock_platform.assert_called()
mock_dwp.assert_called()
@pytest.mark.unit
@patch("subprocess.getstatusoutput")
@patch("platform.system", return_value="Darwin")
def test_active_ports_on_supported_devices_mac_no_duplicates_check(
mock_platform, mock_sp
):
@patch('subprocess.getstatusoutput')
@patch('platform.system', return_value='Darwin')
def test_active_ports_on_supported_devices_mac_no_duplicates_check(mock_platform, mock_sp):
"""Test active_ports_on_supported_devices()"""
mock_sp.return_value = (
None,
(
"crw-rw-rw- 1 root wheel 0x9000005 Mar 8 10:05 /dev/cu.usbmodem53230051441\n"
"crw-rw-rw- 1 root wheel 0x9000003 Mar 8 10:06 /dev/cu.wchusbserial53230051441"
),
)
fake_device = SupportedDevice(
name="a", for_firmware="tbeam", baseport_on_mac="cu.usbmodem"
)
mock_sp.return_value = (None, ('crw-rw-rw- 1 root wheel 0x9000005 Mar 8 10:05 /dev/cu.usbmodem53230051441\n'
'crw-rw-rw- 1 root wheel 0x9000003 Mar 8 10:06 /dev/cu.wchusbserial53230051441'))
fake_device = SupportedDevice(name='a', for_firmware='tbeam', baseport_on_mac='cu.usbmodem')
fake_supported_devices = [fake_device]
assert active_ports_on_supported_devices(fake_supported_devices, False) == {
"/dev/cu.usbmodem53230051441",
"/dev/cu.wchusbserial53230051441",
}
assert active_ports_on_supported_devices(fake_supported_devices, False) == {'/dev/cu.usbmodem53230051441', '/dev/cu.wchusbserial53230051441'}
mock_platform.assert_called()
mock_sp.assert_called()
@pytest.mark.unit
@patch("subprocess.getstatusoutput")
@patch("platform.system", return_value="Darwin")
@patch('subprocess.getstatusoutput')
@patch('platform.system', return_value='Darwin')
def test_active_ports_on_supported_devices_mac_duplicates_check(mock_platform, mock_sp):
"""Test active_ports_on_supported_devices()"""
mock_sp.return_value = (
None,
(
"crw-rw-rw- 1 root wheel 0x9000005 Mar 8 10:05 /dev/cu.usbmodem53230051441\n"
"crw-rw-rw- 1 root wheel 0x9000003 Mar 8 10:06 /dev/cu.wchusbserial53230051441"
),
)
fake_device = SupportedDevice(
name="a", for_firmware="tbeam", baseport_on_mac="cu.usbmodem"
)
mock_sp.return_value = (None, ('crw-rw-rw- 1 root wheel 0x9000005 Mar 8 10:05 /dev/cu.usbmodem53230051441\n'
'crw-rw-rw- 1 root wheel 0x9000003 Mar 8 10:06 /dev/cu.wchusbserial53230051441'))
fake_device = SupportedDevice(name='a', for_firmware='tbeam', baseport_on_mac='cu.usbmodem')
fake_supported_devices = [fake_device]
assert active_ports_on_supported_devices(fake_supported_devices, True) == {
"/dev/cu.wchusbserial53230051441"
}
assert active_ports_on_supported_devices(fake_supported_devices, True) == {'/dev/cu.wchusbserial53230051441'}
mock_platform.assert_called()
mock_sp.assert_called()

View File

@@ -16,20 +16,20 @@
"""
import logging
import platform
import threading
import platform
from pubsub import pub
from pytap2 import TapDevice
from meshtastic import portnums_pb2
from meshtastic.globals import Globals
from meshtastic.util import ipstr, readnet_u16
from meshtastic.globals import Globals
def onTunnelReceive(packet, interface): # pylint: disable=W0613
def onTunnelReceive(packet, interface): # pylint: disable=W0613
"""Callback for received tunneled messages from mesh."""
logging.debug(f"in onTunnelReceive()")
logging.debug(f'in onTunnelReceive()')
our_globals = Globals.getInstance()
tunnelInstance = our_globals.get_tunnelInstance()
tunnelInstance.onReceive(packet)
@@ -38,7 +38,7 @@ def onTunnelReceive(packet, interface): # pylint: disable=W0613
class Tunnel:
"""A TUN based IP tunnel over meshtastic"""
def __init__(self, iface, subnet="10.115", netmask="255.255.0.0"):
def __init__(self, iface, subnet='10.115', netmask="255.255.0.0"):
"""
Constructor
@@ -52,7 +52,7 @@ class Tunnel:
self.iface = iface
self.subnetPrefix = subnet
if platform.system() != "Linux":
if platform.system() != 'Linux':
raise Exception("Tunnel() can only be run instantiated on a Linux system")
our_globals = Globals.getInstance()
@@ -80,10 +80,8 @@ class Tunnel:
self.LOG_TRACE = 5
# TODO: check if root?
logging.info(
"Starting IP to mesh tunnel (you must be root for this *pre-alpha* "
"feature to work). Mesh members:"
)
logging.info("Starting IP to mesh tunnel (you must be root for this *pre-alpha* "\
"feature to work). Mesh members:")
pub.subscribe(onTunnelReceive, "meshtastic.receive.data.IP_TUNNEL_APP")
myAddr = self._nodeNumToIp(self.iface.myInfo.my_node_num)
@@ -98,9 +96,7 @@ class Tunnel:
# FIXME - figure out real max MTU, it should be 240 - the overhead bytes for SubPacket and Data
self.tun = None
if self.iface.noProto:
logging.warning(
f"Not creating a TapDevice() because it is disabled by noProto"
)
logging.warning(f"Not creating a TapDevice() because it is disabled by noProto")
else:
self.tun = TapDevice(name="mesh")
self.tun.up()
@@ -108,14 +104,10 @@ class Tunnel:
self._rxThread = None
if self.iface.noProto:
logging.warning(
f"Not starting TUN reader because it is disabled by noProto"
)
logging.warning(f"Not starting TUN reader because it is disabled by noProto")
else:
logging.debug(f"starting TUN reader, our IP address is {myAddr}")
self._rxThread = threading.Thread(
target=self.__tunReader, args=(), daemon=True
)
self._rxThread = threading.Thread(target=self.__tunReader, args=(), daemon=True)
self._rxThread.start()
def onReceive(self, packet):
@@ -140,19 +132,15 @@ class Tunnel:
ignore = False # Assume we will be forwarding the packet
if protocol in self.protocolBlacklist:
ignore = True
logging.log(
self.LOG_TRACE, f"Ignoring blacklisted protocol 0x{protocol:02x}"
)
logging.log(self.LOG_TRACE, f"Ignoring blacklisted protocol 0x{protocol:02x}")
elif protocol == 0x01: # ICMP
icmpType = p[20]
icmpCode = p[21]
checksum = p[22:24]
# pylint: disable=line-too-long
logging.debug(
f"forwarding ICMP message src={ipstr(srcaddr)}, dest={ipstr(destAddr)}, type={icmpType}, code={icmpCode}, checksum={checksum}"
)
logging.debug(f"forwarding ICMP message src={ipstr(srcaddr)}, dest={ipstr(destAddr)}, type={icmpType}, code={icmpCode}, checksum={checksum}")
# reply to pings (swap src and dest but keep rest of packet unchanged)
# pingback = p[:12]+p[16:20]+p[12:16]+p[20:]
#pingback = p[:12]+p[16:20]+p[12:16]+p[20:]
# tap.write(pingback)
elif protocol == 0x11: # UDP
srcport = readnet_u16(p, subheader)
@@ -171,10 +159,8 @@ class Tunnel:
else:
logging.debug(f"forwarding tcp srcport={srcport}, destport={destport}")
else:
logging.warning(
f"forwarding unexpected protocol 0x{protocol:02x}, "
"src={ipstr(srcaddr)}, dest={ipstr(destAddr)}"
)
logging.warning(f"forwarding unexpected protocol 0x{protocol:02x}, "\
"src={ipstr(srcaddr)}, dest={ipstr(destAddr)}")
return ignore
@@ -183,7 +169,7 @@ class Tunnel:
logging.debug("TUN reader running")
while True:
p = tap.read()
# logging.debug(f"IP packet received on TUN interface, type={type(p)}")
#logging.debug(f"IP packet received on TUN interface, type={type(p)}")
destAddr = p[16:20]
if not self._shouldFilterPacket(p):
@@ -193,11 +179,11 @@ class Tunnel:
# We only consider the last 16 bits of the nodenum for IP address matching
ipBits = ipAddr[2] * 256 + ipAddr[3]
if ipBits == 0xFFFF:
if ipBits == 0xffff:
return "^all"
for node in self.iface.nodes.values():
nodeNum = node["num"] & 0xFFFF
nodeNum = node["num"] & 0xffff
# logging.debug(f"Considering nodenum 0x{nodeNum:x} for ipBits 0x{ipBits:x}")
if (nodeNum) == ipBits:
return node["user"]["id"]
@@ -210,14 +196,11 @@ class Tunnel:
"""Forward the provided IP packet into the mesh"""
nodeId = self._ipToNodeId(destAddr)
if nodeId is not None:
logging.debug(
f"Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}"
)
self.iface.sendData(p, nodeId, portnums_pb2.IP_TUNNEL_APP, wantAck=False)
logging.debug(f"Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}")
self.iface.sendData(
p, nodeId, portnums_pb2.IP_TUNNEL_APP, wantAck=False)
else:
logging.warning(
f"Dropping packet because no node found for destIP={ipstr(destAddr)}"
)
logging.warning(f"Dropping packet because no node found for destIP={ipstr(destAddr)}")
def close(self):
"""Close"""

View File

@@ -1,21 +1,19 @@
"""Utility functions.
"""
import base64
import logging
import os
import platform
import re
import subprocess
import sys
import threading
import time
import traceback
from queue import Queue
import pkg_resources
import requests
import os
import re
import sys
import base64
import time
import platform
import logging
import threading
import subprocess
import serial
import serial.tools.list_ports
import pkg_resources
from meshtastic.supported_device import supported_devices
@@ -25,13 +23,12 @@ blacklistVids = dict.fromkeys([0x1366])
def quoteBooleans(a_string):
"""Quote booleans
given a string that contains ": true", replace with ": 'true'" (or false)
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)
@@ -64,11 +61,9 @@ def fromStr(valstr):
"""
if len(valstr) == 0: # Treat an emptystring as an empty bytes
val = bytes()
elif valstr.startswith("0x"):
elif valstr.startswith('0x'):
# if needed convert to string with asBytes.decode('utf-8')
val = bytes.fromhex(valstr[2:])
elif valstr.startswith("base64:"):
val = base64.b64decode(valstr[7:])
elif valstr.lower() in {"t", "true", "yes"}:
val = True
elif valstr.lower() in {"f", "false", "no"}:
@@ -103,7 +98,7 @@ def pskToString(psk: bytes):
def stripnl(s):
"""Remove newlines from a string (and remove extra whitespace)"""
s = str(s).replace("\n", " ")
return " ".join(s.split())
return ' '.join(s.split())
def fixme(message):
@@ -126,15 +121,9 @@ def findPorts(eliminate_duplicates=False):
Returns:
list -- a list of device paths
"""
l = list(
map(
lambda port: port.device,
filter(
lambda port: port.vid is not None and port.vid not in blacklistVids,
serial.tools.list_ports.comports(),
),
)
)
l = list(map(lambda port: port.device,
filter(lambda port: port.vid is not None and port.vid not in blacklistVids,
serial.tools.list_ports.comports())))
l.sort()
if eliminate_duplicates:
l = eliminate_duplicate_port(l)
@@ -143,7 +132,6 @@ def findPorts(eliminate_duplicates=False):
class dotdict(dict):
"""dot.notation access to dictionary attributes"""
__getattr__ = dict.get
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
@@ -151,7 +139,6 @@ class dotdict(dict):
class Timeout:
"""Timeout class"""
def __init__(self, maxSecs=20):
self.expireTime = 0
self.sleepInterval = 0.1
@@ -170,49 +157,8 @@ class Timeout:
time.sleep(self.sleepInterval)
return False
def waitForAckNak(
self, acknowledgment, attrs=("receivedAck", "receivedNak", "receivedImplAck")
):
"""Block until an ACK or NAK has been received. Returns True if ACK or NAK has been received."""
self.reset()
while time.time() < self.expireTime:
if any(map(lambda a: getattr(acknowledgment, a, None), attrs)):
acknowledgment.reset()
return True
time.sleep(self.sleepInterval)
return False
def waitForTraceRoute(self, waitFactor, acknowledgment, attr="receivedTraceRoute"):
"""Block until traceroute response is received. Returns True if traceroute response has been received."""
self.expireTimeout *= waitFactor
self.reset()
while time.time() < self.expireTime:
if getattr(acknowledgment, attr, None):
acknowledgment.reset()
return True
time.sleep(self.sleepInterval)
return False
class Acknowledgment:
"A class that records which type of acknowledgment was just received, if any."
def __init__(self):
"""initialize"""
self.receivedAck = False
self.receivedNak = False
self.receivedImplAck = False
self.receivedTraceRoute = False
def reset(self):
"""reset"""
self.receivedAck = False
self.receivedNak = False
self.receivedImplAck = False
self.receivedTraceRoute = False
class DeferredExecution:
class DeferredExecution():
"""A thread that accepts closures to run, and runs them as they are received"""
def __init__(self, name=None):
@@ -222,7 +168,7 @@ class DeferredExecution:
self.thread.start()
def queueWork(self, runnable):
"""Queue up the work"""
""" Queue up the work"""
self.queue.put(runnable)
def _run(self):
@@ -231,15 +177,13 @@ class DeferredExecution:
o = self.queue.get()
o()
except:
logging.error(
f"Unexpected error in deferred execution {sys.exc_info()[0]}"
)
logging.error(f"Unexpected error in deferred execution {sys.exc_info()[0]}")
print(traceback.format_exc())
def our_exit(message, return_value=1):
def our_exit(message, return_value = 1):
"""Print the message and return a value.
return_value defaults to 1 (non-successful)
return_value defaults to 1 (non-successful)
"""
print(message)
sys.exit(return_value)
@@ -247,36 +191,28 @@ def our_exit(message, return_value=1):
def support_info():
"""Print out info that helps troubleshooting of the cli."""
print("")
print("If having issues with meshtastic cli or python library")
print("or wish to make feature requests, visit:")
print("https://github.com/meshtastic/python/issues")
print("When adding an issue, be sure to include the following info:")
print(f" System: {platform.system()}")
print(f" Platform: {platform.platform()}")
print(f" Release: {platform.uname().release}")
print(f" Machine: {platform.uname().machine}")
print(f" Encoding (stdin): {sys.stdin.encoding}")
print(f" Encoding (stdout): {sys.stdout.encoding}")
print('')
print('If having issues with meshtastic cli or python library')
print('or wish to make feature requests, visit:')
print('https://github.com/meshtastic/Meshtastic-python/issues')
print('When adding an issue, be sure to include the following info:')
print(f' System: {platform.system()}')
print(f' Platform: {platform.platform()}')
print(f' Release: {platform.uname().release}')
print(f' Machine: {platform.uname().machine}')
print(f' Encoding (stdin): {sys.stdin.encoding}')
print(f' Encoding (stdout): {sys.stdout.encoding}')
the_version = pkg_resources.get_distribution("meshtastic").version
pypi_version = check_if_newer_version()
if pypi_version:
print(
f" meshtastic: v{the_version} (*** newer version v{pypi_version} available ***)"
)
else:
print(f" meshtastic: v{the_version}")
print(f" Executable: {sys.argv[0]}")
print(
f" Python: {platform.python_version()} {platform.python_implementation()} {platform.python_compiler()}"
)
print("")
print("Please add the output from the command: meshtastic --info")
print(f' meshtastic: v{the_version}')
print(f' Executable: {sys.argv[0]}')
print(f' Python: {platform.python_version()} {platform.python_implementation()} {platform.python_compiler()}')
print('')
print('Please add the output from the command: meshtastic --info')
def remove_keys_from_dict(keys, adict):
"""Return a dictionary without some keys in it.
Will removed nested keys.
Will removed nested keys.
"""
for key in keys:
try:
@@ -291,12 +227,12 @@ def remove_keys_from_dict(keys, adict):
def hexstr(barray):
"""Print a string of hex digits"""
return ":".join(f"{x:02x}" for x in barray)
return ":".join(f'{x:02x}' for x in barray)
def ipstr(barray):
"""Print a string of ip digits"""
return ".".join(f"{x}" for x in barray)
return ".".join(f'{x}' for x in barray)
def readnet_u16(p, offset):
@@ -306,8 +242,8 @@ def readnet_u16(p, offset):
def convert_mac_addr(val):
"""Convert the base 64 encoded value to a mac address
val - base64 encoded value (ex: '/c0gFyhb'))
returns: a string formatted like a mac address (ex: 'fd:cd:20:17:28:5b')
val - base64 encoded value (ex: '/c0gFyhb'))
returns: a string formatted like a mac address (ex: 'fd:cd:20:17:28:5b')
"""
if not re.match("[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", val):
val_as_bytes = base64.b64decode(val)
@@ -318,23 +254,21 @@ def convert_mac_addr(val):
def snake_to_camel(a_string):
"""convert snake_case to camelCase"""
# split underscore using split
temp = a_string.split("_")
temp = a_string.split('_')
# joining result
result = temp[0] + "".join(ele.title() for ele in temp[1:])
result = temp[0] + ''.join(ele.title() for ele in temp[1:])
return result
def camel_to_snake(a_string):
"""convert camelCase to snake_case"""
return "".join(["_" + i.lower() if i.isupper() else i for i in a_string]).lstrip(
"_"
)
return ''.join(['_'+i.lower() if i.isupper() else i for i in a_string]).lstrip('_')
def detect_supported_devices():
"""detect supported devices based on vendor id"""
"""detect supported devices"""
system = platform.system()
# print(f'system:{system}')
#print(f'system:{system}')
possible_devices = set()
if system == "Linux":
@@ -342,52 +276,86 @@ def detect_supported_devices():
# linux: use lsusb
# Bus 001 Device 091: ID 10c4:ea60 Silicon Labs CP210x UART Bridge
_, lsusb_output = subprocess.getstatusoutput("lsusb")
_, lsusb_output = subprocess.getstatusoutput('lsusb')
vids = get_unique_vendor_ids()
for vid in vids:
# print(f'looking for {vid}...')
search = f" {vid}:"
# print(f'search:"{search}"')
#print(f'looking for {vid}...')
search = f' {vid}:'
#print(f'search:"{search}"')
if re.search(search, lsusb_output, re.MULTILINE):
# print(f'Found vendor id that matches')
#print(f'Found vendor id that matches')
devices = get_devices_with_vendor_id(vid)
# check device id
for device in devices:
possible_devices.add(device)
#print(f'device:{device} device.usb_product_id_in_hex:{device.usb_product_id_in_hex}')
if device.usb_product_id_in_hex:
search = f' {vid}:{device.usb_product_id_in_hex} '
#print(f'search:"{search}"')
if re.search(search, lsusb_output, re.MULTILINE):
# concatenate the devices with vendor id to possibles
possible_devices.add(device)
else:
# if there is a supported device witout a product id, then it
# might be a match... so, concatenate
possible_devices.add(device)
elif system == "Windows":
# if windows, run Get-PnpDevice
_, sp_output = subprocess.getstatusoutput(
'powershell.exe "[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8;'
'Get-PnpDevice -PresentOnly | Format-List"'
)
# print(f'sp_output:{sp_output}')
_, sp_output = subprocess.getstatusoutput('powershell.exe "[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8;'
'Get-PnpDevice -PresentOnly | Format-List"')
#print(f'sp_output:{sp_output}')
vids = get_unique_vendor_ids()
for vid in vids:
# print(f'looking for {vid.upper()}...')
search = f"DeviceID.*{vid.upper()}&"
# search = f'{vid.upper()}'
# print(f'search:"{search}"')
#print(f'looking for {vid.upper()}...')
search = f'DeviceID.*{vid.upper()}&'
#search = f'{vid.upper()}'
#print(f'search:"{search}"')
if re.search(search, sp_output, re.MULTILINE):
# print(f'Found vendor id that matches')
#print(f'Found vendor id that matches')
devices = get_devices_with_vendor_id(vid)
# check device id
for device in devices:
possible_devices.add(device)
#print(f'device:{device} device.usb_product_id_in_hex:{device.usb_product_id_in_hex}')
if device.usb_product_id_in_hex:
search = f'DeviceID.*{vid.upper()}&PID_{device.usb_product_id_in_hex.upper()}'
#print(f'search:"{search}"')
if re.search(search, sp_output, re.MULTILINE):
# concatenate the devices with vendor id to possibles
possible_devices.add(device)
# do a check to see if there is a Windows driver issue
if detect_windows_needs_driver(device, False):
print("WARNING: Need to install driver.")
else:
# if there is a supported device witout a product id, then it
# might be a match... so, concatenate
possible_devices.add(device)
elif system == "Darwin":
# run: system_profiler SPUSBDataType
# Note: If in boot mode, the 19003 reports same product ID as 5005.
_, sp_output = subprocess.getstatusoutput("system_profiler SPUSBDataType")
_, sp_output = subprocess.getstatusoutput('system_profiler SPUSBDataType')
vids = get_unique_vendor_ids()
for vid in vids:
# print(f'looking for {vid}...')
search = f"Vendor ID: 0x{vid}"
# print(f'search:"{search}"')
#print(f'looking for {vid}...')
search = f'Vendor ID: 0x{vid}'
#print(f'search:"{search}"')
if re.search(search, sp_output, re.MULTILINE):
# print(f'Found vendor id that matches')
#print(f'Found vendor id that matches')
devices = get_devices_with_vendor_id(vid)
# check device id
for device in devices:
possible_devices.add(device)
#print(f'device:{device} device.usb_product_id_in_hex:{device.usb_product_id_in_hex}')
if device.usb_product_id_in_hex:
search = f'Product ID: 0x{device.usb_product_id_in_hex}'
#print(f'search:"{search}"')
if re.search(search, sp_output, re.MULTILINE):
# concatenate the devices with vendor id to possibles
possible_devices.add(device)
else:
# if there is a supported device witout a product id, then it
# might be a match... so, concatenate
possible_devices.add(device)
return possible_devices
@@ -397,7 +365,7 @@ def detect_windows_needs_driver(sd, print_reason=False):
if sd:
system = platform.system()
# print(f'in detect_windows_needs_driver system:{system}')
#print(f'in detect_windows_needs_driver system:{system}')
if system == "Windows":
# if windows, see if we can find a DeviceId with the vendor id
@@ -406,11 +374,11 @@ def detect_windows_needs_driver(sd, print_reason=False):
command += f"'*{sd.usb_vendor_id_in_hex.upper()}*'"
command += ')} | Format-List"'
# print(f'command:{command}')
#print(f'command:{command}')
_, sp_output = subprocess.getstatusoutput(command)
# print(f'sp_output:{sp_output}')
search = f"CM_PROB_FAILED_INSTALL"
# print(f'search:"{search}"')
#print(f'sp_output:{sp_output}')
search = f'CM_PROB_FAILED_INSTALL'
#print(f'search:"{search}"')
if re.search(search, sp_output, re.MULTILINE):
need_to_install_driver = True
# if the want to see the reason
@@ -422,30 +390,30 @@ def detect_windows_needs_driver(sd, print_reason=False):
def eliminate_duplicate_port(ports):
"""Sometimes we detect 2 serial ports, but we really only need to use one of the ports.
ports is a list of ports
return a list with a single port to use, if it meets the duplicate port conditions
ports is a list of ports
return a list with a single port to use, if it meets the duplicate port conditions
examples:
Ports: ['/dev/cu.usbserial-1430', '/dev/cu.wchusbserial1430'] => ['/dev/cu.wchusbserial1430']
Ports: ['/dev/cu.usbmodem11301', '/dev/cu.wchusbserial11301'] => ['/dev/cu.wchusbserial11301']
Ports: ['/dev/cu.SLAB_USBtoUART', '/dev/cu.usbserial-0001'] => ['/dev/cu.usbserial-0001']
examples:
Ports: ['/dev/cu.usbserial-1430', '/dev/cu.wchusbserial1430'] => ['/dev/cu.wchusbserial1430']
Ports: ['/dev/cu.usbmodem11301', '/dev/cu.wchusbserial11301'] => ['/dev/cu.wchusbserial11301']
Ports: ['/dev/cu.SLAB_USBtoUART', '/dev/cu.usbserial-0001'] => ['/dev/cu.usbserial-0001']
"""
new_ports = []
if len(ports) != 2:
new_ports = ports
else:
ports.sort()
if "usbserial" in ports[0] and "wchusbserial" in ports[1]:
if 'usbserial' in ports[0] and 'wchusbserial' in ports[1]:
first = ports[0].replace("usbserial-", "")
second = ports[1].replace("wchusbserial", "")
if first == second:
new_ports.append(ports[1])
elif "usbmodem" in ports[0] and "wchusbserial" in ports[1]:
elif 'usbmodem' in ports[0] and 'wchusbserial' in ports[1]:
first = ports[0].replace("usbmodem", "")
second = ports[1].replace("wchusbserial", "")
if first == second:
new_ports.append(ports[1])
elif "SLAB_USBtoUART" in ports[0] and "usbserial" in ports[1]:
elif 'SLAB_USBtoUART' in ports[0] and 'usbserial' in ports[1]:
new_ports.append(ports[1])
else:
new_ports = ports
@@ -457,14 +425,14 @@ def is_windows11():
is_win11 = False
if platform.system() == "Windows":
if float(platform.release()) >= 10.0:
patch = platform.version().split(".")[2]
patch = platform.version().split('.')[2]
# in case they add some number suffix later, just get first 5 chars of patch
patch = patch[:5]
try:
if int(patch) >= 22000:
is_win11 = True
except Exception as e:
print(f"problem detecting win11 e:{e}")
print(f'problem detecting win11 e:{e}')
return is_win11
@@ -504,46 +472,46 @@ def active_ports_on_supported_devices(sds, eliminate_duplicates=False):
for bp in baseports:
if system == "Linux":
# see if we have any devices (ignoring any stderr output)
command = f"ls -al /dev/{bp}* 2> /dev/null"
# print(f'command:{command}')
command = f'ls -al /dev/{bp}* 2> /dev/null'
#print(f'command:{command}')
_, ls_output = subprocess.getstatusoutput(command)
# print(f'ls_output:{ls_output}')
#print(f'ls_output:{ls_output}')
# if we got output, there are ports
if len(ls_output) > 0:
# print('got output')
#print('got output')
# for each line of output
lines = ls_output.split("\n")
# print(f'lines:{lines}')
lines = ls_output.split('\n')
#print(f'lines:{lines}')
for line in lines:
parts = line.split(" ")
# print(f'parts:{parts}')
parts = line.split(' ')
#print(f'parts:{parts}')
port = parts[-1]
# print(f'port:{port}')
#print(f'port:{port}')
ports.add(port)
elif system == "Darwin":
# see if we have any devices (ignoring any stderr output)
command = f"ls -al /dev/{bp}* 2> /dev/null"
# print(f'command:{command}')
command = f'ls -al /dev/{bp}* 2> /dev/null'
#print(f'command:{command}')
_, ls_output = subprocess.getstatusoutput(command)
# print(f'ls_output:{ls_output}')
#print(f'ls_output:{ls_output}')
# if we got output, there are ports
if len(ls_output) > 0:
# print('got output')
#print('got output')
# for each line of output
lines = ls_output.split("\n")
# print(f'lines:{lines}')
lines = ls_output.split('\n')
#print(f'lines:{lines}')
for line in lines:
parts = line.split(" ")
# print(f'parts:{parts}')
parts = line.split(' ')
#print(f'parts:{parts}')
port = parts[-1]
# print(f'port:{port}')
#print(f'port:{port}')
ports.add(port)
elif system == "Windows":
# for each device in supported devices found
for d in sds:
# find the port(s)
com_ports = detect_windows_port(d)
# print(f'com_ports:{com_ports}')
#print(f'com_ports:{com_ports}')
# add all ports
for com_port in com_ports:
ports.add(com_port)
@@ -562,35 +530,16 @@ def detect_windows_port(sd):
system = platform.system()
if system == "Windows":
command = (
'powershell.exe "[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8;'
"Get-PnpDevice -PresentOnly | Where-Object{ ($_.DeviceId -like "
)
command = ('powershell.exe "[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8;'
'Get-PnpDevice -PresentOnly | Where-Object{ ($_.DeviceId -like ')
command += f"'*{sd.usb_vendor_id_in_hex.upper()}*'"
command += ')} | Format-List"'
# print(f'command:{command}')
#print(f'command:{command}')
_, sp_output = subprocess.getstatusoutput(command)
# print(f'sp_output:{sp_output}')
p = re.compile(r"\(COM(.*)\)")
#print(f'sp_output:{sp_output}')
p = re.compile(r'\(COM(.*)\)')
for x in p.findall(sp_output):
# print(f'x:{x}')
ports.add(f"COM{x}")
#print(f'x:{x}')
ports.add(f'COM{x}')
return ports
def check_if_newer_version():
"""Check pip to see if we are running the latest version."""
pypi_version = None
try:
url = "https://pypi.org/pypi/meshtastic/json"
data = requests.get(url, timeout=5).json()
pypi_version = data["info"]["version"]
except Exception:
pass
act_version = pkg_resources.get_distribution("meshtastic").version
if pypi_version and pkg_resources.parse_version(
pypi_version
) <= pkg_resources.parse_version(act_version):
return None
return pypi_version

View File

@@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/xmodem.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17meshtastic/xmodem.proto\"\xab\x01\n\x06XModem\x12 \n\x07\x63ontrol\x18\x01 \x01(\x0e\x32\x0f.XModem.Control\x12\x0b\n\x03seq\x18\x02 \x01(\r\x12\r\n\x05\x63rc16\x18\x03 \x01(\r\x12\x0e\n\x06\x62uffer\x18\x04 \x01(\x0c\"S\n\x07\x43ontrol\x12\x07\n\x03NUL\x10\x00\x12\x07\n\x03SOH\x10\x01\x12\x07\n\x03STX\x10\x02\x12\x07\n\x03\x45OT\x10\x04\x12\x07\n\x03\x41\x43K\x10\x06\x12\x07\n\x03NAK\x10\x15\x12\x07\n\x03\x43\x41N\x10\x18\x12\t\n\x05\x43TRLZ\x10\x1a\x42\x61\n\x13\x63om.geeksville.meshB\x0cXmodemProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_XMODEM = DESCRIPTOR.message_types_by_name['XModem']
_XMODEM_CONTROL = _XMODEM.enum_types_by_name['Control']
XModem = _reflection.GeneratedProtocolMessageType('XModem', (_message.Message,), {
'DESCRIPTOR' : _XMODEM,
'__module__' : 'meshtastic.xmodem_pb2'
# @@protoc_insertion_point(class_scope:XModem)
})
_sym_db.RegisterMessage(XModem)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\014XmodemProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_XMODEM._serialized_start=28
_XMODEM._serialized_end=199
_XMODEM_CONTROL._serialized_start=116
_XMODEM_CONTROL._serialized_end=199
# @@protoc_insertion_point(module_scope)

1
proto Submodule

Submodule proto added at 7c49bdad99

Submodule protobufs deleted from 1a8f80d7fe

View File

@@ -7,7 +7,6 @@ pyqrcode
tabulate
timeago
webencodings
requests
pyparsing
twine
autopep8

View File

@@ -1,7 +1,6 @@
# Note: you shouldn't need to run this script manually. It is run implicitly by the pip3 install command.
import pathlib
from setuptools import setup
# The directory containing this file
@@ -13,17 +12,18 @@ with open("README.md", "r") as fh:
# This call to setup() does all the work
setup(
name="meshtastic",
version="2.1.7",
version="1.2.91",
description="Python API & client shell for talking to Meshtastic devices",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/meshtastic/python",
author="Meshtastic Developers",
author_email="contact@meshtastic.org",
license="GPL-3.0-only",
url="https://github.com/meshtastic/Meshtastic-python",
author="Kevin Hester",
author_email="kevinh@geeksville.com",
license="MIT",
classifiers=[
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Development Status :: 4 - Beta",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
@@ -31,25 +31,18 @@ setup(
],
packages=["meshtastic"],
include_package_data=True,
install_requires=[
"pyserial>=3.4",
"protobuf>=3.13.0",
"requests>=2.25.0",
"pypubsub>=4.0.3",
"dotmap>=1.3.14",
"pexpect>=4.6.0",
"pyqrcode>=1.2.1",
"tabulate>=0.8.9",
"timeago>=1.0.15",
"pyyaml",
"pygatt>=4.0.5 ; platform_system=='Linux'",
],
extras_require={"tunnel": ["pytap2>=2.0.0"]},
python_requires=">=3.7",
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",
"tabulate>=0.8.9", "timeago>=1.0.15", "pyyaml",
"pygatt>=4.0.5 ; platform_system=='Linux'"],
extras_require={
'tunnel': ["pytap2>=2.0.0"]
},
python_requires='>=3.6',
entry_points={
"console_scripts": [
"meshtastic=meshtastic.__main__:main",
"mesh-tunnel=meshtastic.__main__:tunnelMain [tunnel]",
"mesh-tunnel=meshtastic.__main__:tunnelMain [tunnel]"
]
},
)

View File

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

View File

@@ -1,14 +1,11 @@
import datetime
import logging
import sys
import meshtastic
import datetime, logging
from pubsub import pub
import meshtastic
# logging.basicConfig(level=logging.DEBUG)
#logging.basicConfig(level=logging.DEBUG)
print(str(datetime.datetime.now()) + ": start")
interface = meshtastic.TCPInterface(sys.argv[1])
print(str(datetime.datetime.now()) + ": middle")
interface.close()
print(str(datetime.datetime.now()) + ": after close")
print(str(datetime.datetime.now()) + ": after close")

View File

@@ -1,9 +1,6 @@
import meshtastic
import time
import meshtastic
interface = (
meshtastic.SerialInterface()
) # By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0
interface = meshtastic.SerialInterface() # By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0
interface.sendText("hello mesh")
interface.close()

View File

@@ -1,18 +1,10 @@
# reported by @ScriptBlock
import sys
import meshtastic, sys
from pubsub import pub
import meshtastic
def onConnection(
interface, topic=pub.AUTO_TOPIC
): # called when we (re)connect to the radio
def onConnection(interface, topic=pub.AUTO_TOPIC): # called when we (re)connect to the radio
print(interface.myInfo)
interface.close()
pub.subscribe(onConnection, "meshtastic.connection.established")
interface = meshtastic.TCPInterface(sys.argv[1])

View File

@@ -8,16 +8,15 @@
# select local ip address based on nodeid
# print known node ids as IP addresses
from pytap2 import TapDevice
import logging
from _thread import start_new_thread
from pytap2 import TapDevice
"""A list of chatty UDP services we should never accidentally
forward to our slow network"""
udpBlacklist = {
1900, # SSDP
5353, # multicast DNS
1900, # SSDP
5353, # multicast DNS
}
"""A list of TCP services to block"""
@@ -25,26 +24,22 @@ tcpBlacklist = {}
"""A list of protocols we ignore"""
protocolBlacklist = {
0x02, # IGMP
0x80, # Service-Specific Connection-Oriented Protocol in a Multilink and Connectionless Environment
0x02, # IGMP
0x80, # Service-Specific Connection-Oriented Protocol in a Multilink and Connectionless Environment
}
def hexstr(barray):
"""Print a string of hex digits"""
return ":".join("{:02x}".format(x) for x in barray)
return ":".join('{:02x}'.format(x) for x in barray)
def ipstr(barray):
"""Print a string of ip digits"""
return ".".join("{}".format(x) for x in barray)
return ".".join('{}'.format(x) for x in barray)
def readnet_u16(p, offset):
"""Read big endian u16 (network byte order)"""
return p[offset] * 256 + p[offset + 1]
def readtest(tap):
while True:
p = tap.read()
@@ -53,23 +48,23 @@ def readtest(tap):
srcaddr = p[12:16]
destaddr = p[16:20]
subheader = 20
ignore = False # Assume we will be forwarding the packet
ignore = False # Assume we will be forwarding the packet
if protocol in protocolBlacklist:
ignore = True
logging.debug(f"Ignoring blacklisted protocol 0x{protocol:02x}")
elif protocol == 0x01: # ICMP
elif protocol == 0x01: # ICMP
logging.warn("Generating fake ping reply")
# reply to pings (swap src and dest but keep rest of packet unchanged)
pingback = p[:12] + p[16:20] + p[12:16] + p[20:]
pingback = p[:12]+p[16:20]+p[12:16]+p[20:]
tap.write(pingback)
elif protocol == 0x11: # UDP
elif protocol == 0x11: # UDP
srcport = readnet_u16(p, subheader)
destport = readnet_u16(p, subheader + 2)
logging.debug(f"udp srcport={srcport}, destport={destport}")
if destport in udpBlacklist:
ignore = True
logging.debug(f"ignoring blacklisted UDP port {destport}")
elif protocol == 0x06: # TCP
elif protocol == 0x06: # TCP
srcport = readnet_u16(p, subheader)
destport = readnet_u16(p, subheader + 2)
logging.debug(f"tcp srcport={srcport}, destport={destport}")
@@ -77,23 +72,22 @@ def readtest(tap):
ignore = True
logging.debug(f"ignoring blacklisted TCP port {destport}")
else:
logging.warning(
f"unexpected protocol 0x{protocol:02x}, src={ipstr(srcaddr)}, dest={ipstr(destaddr)}"
)
logging.warning(f"unexpected protocol 0x{protocol:02x}, src={ipstr(srcaddr)}, dest={ipstr(destaddr)}")
if not ignore:
logging.debug(
f"Forwarding packet bytelen={len(p)} src={ipstr(srcaddr)}, dest={ipstr(destaddr)}"
)
logging.debug(f"Forwarding packet bytelen={len(p)} src={ipstr(srcaddr)}, dest={ipstr(destaddr)}")
logging.basicConfig(level=logging.DEBUG)
tun = TapDevice(mtu=200)
# tun.create()
tun.up()
tun.ifconfig(address="10.115.1.2", netmask="255.255.0.0")
tun.ifconfig(address="10.115.1.2",netmask="255.255.0.0")
start_new_thread(readtest, (tun,))
start_new_thread(readtest,(tun,))
input("press return key to quit!")
tun.close()

View File

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