mirror of
https://github.com/meshtastic/python.git
synced 2026-01-16 11:47:57 -05:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96afa703ba | ||
|
|
1b14b1ef20 | ||
|
|
68836b1af1 | ||
|
|
195f0c9d90 | ||
|
|
1ff7334385 | ||
|
|
b15e27c7b6 | ||
|
|
267923fdc5 | ||
|
|
9ab1b32bdb | ||
|
|
3a4795d3b8 | ||
|
|
c2a2d5a77c | ||
|
|
b30cde979c | ||
|
|
8456f36c6b | ||
|
|
e6a88e055f | ||
|
|
7bea6f6120 | ||
|
|
725de4c2f9 | ||
|
|
4203553a44 | ||
|
|
9e319f3c52 | ||
|
|
cd5913ae6d | ||
|
|
ccfb04720f | ||
|
|
c34d08b0e5 | ||
|
|
b5d1b7612f | ||
|
|
b58094b9ce | ||
|
|
23f41bff0a | ||
|
|
a1021c4f78 | ||
|
|
b06329f47e | ||
|
|
53b0e35b0c | ||
|
|
9ac5aeeaf0 |
32
.github/workflows/ci.yml
vendored
32
.github/workflows/ci.yml
vendored
@@ -18,30 +18,29 @@ jobs:
|
|||||||
- "3.10"
|
- "3.10"
|
||||||
- "3.11"
|
- "3.11"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Install Python 3
|
- name: Install Python 3
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v5
|
||||||
- name: Uninstall meshtastic
|
- name: Uninstall meshtastic
|
||||||
run: |
|
run: |
|
||||||
pip3 uninstall meshtastic
|
pip3 uninstall -y meshtastic
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip3 install -r requirements.txt
|
pip3 install poetry
|
||||||
- name: Install meshtastic from local
|
- name: Install meshtastic from local
|
||||||
run: |
|
run: |
|
||||||
pip3 install .
|
poetry install
|
||||||
which meshtastic
|
poetry run meshtastic --version
|
||||||
meshtastic --version
|
|
||||||
- name: Run pylint
|
- name: Run pylint
|
||||||
run: pylint meshtastic examples/ --ignore-patterns ".*_pb2.pyi?$"
|
run: poetry run pylint meshtastic examples/ --ignore-patterns ".*_pb2.pyi?$"
|
||||||
- name: Check types with mypy
|
- name: Check types with mypy
|
||||||
run: mypy meshtastic/
|
run: poetry run mypy meshtastic/
|
||||||
- name: Run tests with pytest
|
- name: Run tests with pytest
|
||||||
run: pytest --cov=meshtastic
|
run: poetry run pytest --cov=meshtastic
|
||||||
- name: Generate coverage report
|
- name: Generate coverage report
|
||||||
run: |
|
run: |
|
||||||
pytest --cov=meshtastic --cov-report=xml
|
poetry run pytest --cov=meshtastic --cov-report=xml
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v4
|
||||||
with:
|
with:
|
||||||
@@ -62,11 +61,12 @@ jobs:
|
|||||||
- "3.10"
|
- "3.10"
|
||||||
- "3.11"
|
- "3.11"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Install Python 3
|
- name: Install Python 3
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v5
|
||||||
- name: Install meshtastic from local
|
- name: Install meshtastic from local
|
||||||
run: |
|
run: |
|
||||||
pip3 install .
|
python -m pip install --upgrade pip
|
||||||
which meshtastic
|
pip3 install poetry
|
||||||
meshtastic --version
|
poetry install
|
||||||
|
poetry run meshtastic --version
|
||||||
|
|||||||
69
.github/workflows/release.yml
vendored
69
.github/workflows/release.yml
vendored
@@ -12,26 +12,36 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python 3.9
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.9"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip3 install poetry
|
||||||
|
|
||||||
- name: Bump version
|
- name: Bump version
|
||||||
run: >-
|
run: >-
|
||||||
bin/bump_version.py
|
poetry version patch
|
||||||
|
|
||||||
- name: Commit updated version.py
|
- name: Commit updated version.
|
||||||
id: commit_updated
|
id: commit_updated
|
||||||
run: |
|
run: |
|
||||||
git config --global user.name 'github-actions'
|
git config --global user.name 'github-actions'
|
||||||
git config --global user.email 'bot@noreply.github.com'
|
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 remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
|
||||||
git add setup.py
|
git add pyproject.toml
|
||||||
git commit -m "bump version" && git push || echo "No changes to commit"
|
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}'
|
git log -n 1 --pretty=format:"%H" | tail -n 1 | awk '{print "::set-output name=sha::"$0}'
|
||||||
|
|
||||||
- name: Get version
|
- name: Get version
|
||||||
id: get_version
|
id: get_version
|
||||||
run: >-
|
run: >-
|
||||||
bin/show_version.py
|
poetry version --short | sed 's/^/::set-output name=version::/'
|
||||||
|
|
||||||
- name: Create GitHub release
|
- name: Create GitHub release
|
||||||
uses: actions/create-release@v1
|
uses: actions/create-release@v1
|
||||||
@@ -47,26 +57,9 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up Python 3.9
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.9
|
|
||||||
|
|
||||||
- name: Install pypa/build
|
|
||||||
run: >-
|
|
||||||
python -m
|
|
||||||
pip install
|
|
||||||
build
|
|
||||||
--user
|
|
||||||
|
|
||||||
- name: Build a binary wheel and a source tarball
|
- name: Build a binary wheel and a source tarball
|
||||||
run: >-
|
run: >-
|
||||||
python -m
|
poetry build
|
||||||
build
|
|
||||||
--sdist
|
|
||||||
--wheel
|
|
||||||
--outdir dist/
|
|
||||||
.
|
|
||||||
|
|
||||||
- name: Publish to PyPI
|
- name: Publish to PyPI
|
||||||
uses: pypa/gh-action-pypi-publish@master
|
uses: pypa/gh-action-pypi-publish@master
|
||||||
@@ -79,14 +72,14 @@ jobs:
|
|||||||
# needs: release_create
|
# needs: release_create
|
||||||
# steps:
|
# steps:
|
||||||
# - name: Checkout
|
# - name: Checkout
|
||||||
# uses: actions/checkout@v3
|
# uses: actions/checkout@v4
|
||||||
# with:
|
# with:
|
||||||
# ref: ${{ needs.release_create.outputs.new_sha }}
|
# ref: ${{ needs.release_create.outputs.new_sha }}
|
||||||
|
|
||||||
# - name: Set up Python 3.9
|
# - name: Set up Python 3.9
|
||||||
# uses: actions/setup-python@v2
|
# uses: actions/setup-python@v5
|
||||||
# with:
|
# with:
|
||||||
# python-version: 3.9
|
# python-version: "3.9"
|
||||||
|
|
||||||
# - name: Setup code signing
|
# - name: Setup code signing
|
||||||
# env:
|
# env:
|
||||||
@@ -125,21 +118,19 @@ jobs:
|
|||||||
needs: release_create
|
needs: release_create
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.release_create.outputs.new_sha }}
|
ref: ${{ needs.release_create.outputs.new_sha }}
|
||||||
|
|
||||||
- name: Set up Python 3.9
|
- name: Set up Python 3.9
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: "3.9"
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
pip install pyinstaller
|
pip install poetry
|
||||||
pip install -r requirements.txt
|
bin/build-bin.sh
|
||||||
pip install .
|
|
||||||
pyinstaller -F -n meshtastic --collect-all meshtastic meshtastic/__main__.py
|
|
||||||
|
|
||||||
- name: Add ubuntu to release
|
- name: Add ubuntu to release
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
@@ -166,21 +157,19 @@ jobs:
|
|||||||
needs: release_create
|
needs: release_create
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.release_create.outputs.new_sha }}
|
ref: ${{ needs.release_create.outputs.new_sha }}
|
||||||
|
|
||||||
- name: Set up Python 3.9
|
- name: Set up Python 3.9
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: "3.9"
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
pip install pyinstaller
|
pip install poetry
|
||||||
pip install -r requirements.txt
|
bin/build-bin.sh
|
||||||
pip install .
|
|
||||||
pyinstaller -F -n meshtastic --collect-all meshtastic meshtastic/__main__.py
|
|
||||||
|
|
||||||
- name: Add windows to release
|
- name: Add windows to release
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
|
|||||||
13
.github/workflows/update_protobufs.yml
vendored
13
.github/workflows/update_protobufs.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
@@ -18,9 +18,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Download nanopb
|
- name: Download nanopb
|
||||||
run: |
|
run: |
|
||||||
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.6-linux-x86.tar.gz
|
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.8-linux-x86.tar.gz
|
||||||
tar xvzf nanopb-0.4.6-linux-x86.tar.gz
|
tar xvzf nanopb-0.4.8-linux-x86.tar.gz
|
||||||
mv nanopb-0.4.6-linux-x86 nanopb-0.4.6
|
mv nanopb-0.4.8-linux-x86 nanopb-0.4.8
|
||||||
|
|
||||||
|
- name: Install poetry (needed by regen-protobufs.sh)
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip3 install poetry
|
||||||
|
|
||||||
- name: Re-generate protocol buffers
|
- name: Re-generate protocol buffers
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -16,3 +16,4 @@ __pycache__
|
|||||||
examples/__pycache__
|
examples/__pycache__
|
||||||
meshtastic.spec
|
meshtastic.spec
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
|
coverage.xml
|
||||||
9
bin/build-bin.sh
Executable file
9
bin/build-bin.sh
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo Building ubuntu binary
|
||||||
|
poetry install
|
||||||
|
source $(poetry env info --path)/bin/activate
|
||||||
|
pyinstaller -F -n meshtastic --collect-all meshtastic meshtastic/__main__.py
|
||||||
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#!/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:
|
|
||||||
lines = f.readlines()
|
|
||||||
|
|
||||||
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('"', "")
|
|
||||||
# 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", ""
|
|
||||||
)
|
|
||||||
f.write(f' version="{ver}",\n')
|
|
||||||
else:
|
|
||||||
f.write(line)
|
|
||||||
@@ -3,20 +3,24 @@ set -e
|
|||||||
# You may consider running: "pytest -m smoke1" instead of this test.
|
# You may consider running: "pytest -m smoke1" instead of this test.
|
||||||
|
|
||||||
echo "Running (crude) prerelease tests to verify sanity"
|
echo "Running (crude) prerelease tests to verify sanity"
|
||||||
|
|
||||||
|
# Use the python environment created by poetry
|
||||||
|
source $(poetry env info --path)/bin/activate
|
||||||
|
|
||||||
echo running hello
|
echo running hello
|
||||||
python3 tests/hello_world.py
|
python3 tests/hello_world.py
|
||||||
# bin/run.sh --help
|
# meshtastic --help
|
||||||
echo toggling router
|
echo toggling router
|
||||||
bin/run.sh --set is_router true
|
meshtastic --set is_router true
|
||||||
bin/run.sh --set is_router false
|
meshtastic --set is_router false
|
||||||
# TODO: This does not seem to work.
|
# TODO: This does not seem to work.
|
||||||
echo setting channel
|
echo setting channel
|
||||||
bin/run.sh --seturl "https://www.meshtastic.org/c/#GAMiENTxuzogKQdZ8Lz_q89Oab8qB0RlZmF1bHQ="
|
meshtastic --seturl "https://www.meshtastic.org/c/#GAMiENTxuzogKQdZ8Lz_q89Oab8qB0RlZmF1bHQ="
|
||||||
echo setting owner
|
echo setting owner
|
||||||
bin/run.sh --set-owner "Test Build"
|
meshtastic --set-owner "Test Build"
|
||||||
echo setting position
|
echo setting position
|
||||||
bin/run.sh --setlat 32.7767 --setlon -96.7970 --setalt 1337
|
meshtastic --setlat 32.7767 --setlon -96.7970 --setalt 1337
|
||||||
echo dumping info
|
echo dumping info
|
||||||
bin/run.sh --info
|
meshtastic run meshtastic --info
|
||||||
echo sending closing message
|
echo sending closing message
|
||||||
bin/run.sh --sendtext "Sanity complete"
|
meshtastic --sendtext "Sanity complete"
|
||||||
|
|||||||
@@ -4,8 +4,11 @@
|
|||||||
#gsed -i 's/import "\//import ".\//g' ./protobufs/meshtastic/*
|
#gsed -i 's/import "\//import ".\//g' ./protobufs/meshtastic/*
|
||||||
#gsed -i 's/package meshtastic;//g' ./protobufs/meshtastic/*
|
#gsed -i 's/package meshtastic;//g' ./protobufs/meshtastic/*
|
||||||
|
|
||||||
./nanopb-0.4.7/generator-bin/protoc -I=protobufs --python_out ./ --mypy_out ./ ./protobufs/meshtastic/*.proto
|
# protoc looks for mypy plugin in the python path
|
||||||
./nanopb-0.4.7/generator-bin/protoc -I=protobufs --python_out ./meshtastic/ --mypy_out ./meshtastic/ ./protobufs/nanopb.proto
|
source $(poetry env info --path)/bin/activate
|
||||||
|
|
||||||
|
./nanopb-0.4.8/generator-bin/protoc -I=protobufs --python_out ./ --mypy_out ./ ./protobufs/meshtastic/*.proto
|
||||||
|
./nanopb-0.4.8/generator-bin/protoc -I=protobufs --python_out ./meshtastic/ --mypy_out ./meshtastic/ ./protobufs/nanopb.proto
|
||||||
|
|
||||||
# workaround for import bug in protoc https://github.com/protocolbuffers/protobuf/issues/1491#issuecomment-690618628
|
# workaround for import bug in protoc https://github.com/protocolbuffers/protobuf/issues/1491#issuecomment-690618628
|
||||||
|
|
||||||
|
|||||||
11
bin/run-ci-local.sh
Executable file
11
bin/run-ci-local.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This script lets you run github ci actions locally
|
||||||
|
# You need to have act installed. You can get it at https://nektosact.com/
|
||||||
|
|
||||||
|
# by default it simulates a push event
|
||||||
|
# other useful options
|
||||||
|
# -j build-and-publish-ubuntu
|
||||||
|
|
||||||
|
# also: we only run one of the 4 matrix tests, because otherwise it absolutely hammers the CPU (so many containers and threads)
|
||||||
|
act -P ubuntu-latest=-self-hosted --matrix "python-version:3.8" "$@"
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
rm log_*
|
|
||||||
python3 -m meshtastic "$@"
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
"""Show the version number"""
|
|
||||||
|
|
||||||
version_filename = "setup.py"
|
|
||||||
|
|
||||||
lines = None
|
|
||||||
|
|
||||||
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('"', "")
|
|
||||||
# get rid of the trailing comma
|
|
||||||
line2 = line2.replace(",", "")
|
|
||||||
# split on =
|
|
||||||
words = line2.split("=")
|
|
||||||
# Note: This format is for github actions
|
|
||||||
print(f"::set-output name=version::{words[1].strip()}")
|
|
||||||
@@ -3,8 +3,6 @@ set -e
|
|||||||
|
|
||||||
bin/regen-docs.sh
|
bin/regen-docs.sh
|
||||||
pandoc --from=markdown --to=rst --output=README README.md
|
pandoc --from=markdown --to=rst --output=README README.md
|
||||||
python3 setup.py sdist bdist_wheel
|
|
||||||
python3 -m twine check dist/*
|
poetry publish -r test-pypi --build
|
||||||
# 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"
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
rm dist/*
|
rm dist/*
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
python3 setup.py sdist bdist_wheel
|
poetry build
|
||||||
python3 -m twine upload dist/*
|
poetry run pytest
|
||||||
|
poetry publish
|
||||||
|
#python3 setup.py sdist bdist_wheel
|
||||||
|
#python3 -m twine upload dist/*
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ from typing import *
|
|||||||
|
|
||||||
import google.protobuf.json_format
|
import google.protobuf.json_format
|
||||||
import serial # type: ignore[import-untyped]
|
import serial # type: ignore[import-untyped]
|
||||||
import timeago # type: ignore[import-untyped]
|
|
||||||
from dotmap import DotMap # type: ignore[import-untyped]
|
from dotmap import DotMap # type: ignore[import-untyped]
|
||||||
from google.protobuf.json_format import MessageToJson
|
from google.protobuf.json_format import MessageToJson
|
||||||
from pubsub import pub # type: ignore[import-untyped]
|
from pubsub import pub # type: ignore[import-untyped]
|
||||||
@@ -128,6 +127,7 @@ class ResponseHandler(NamedTuple):
|
|||||||
|
|
||||||
# requestId: int - used only as a key
|
# requestId: int - used only as a key
|
||||||
callback: Callable
|
callback: Callable
|
||||||
|
ackPermitted: bool = False
|
||||||
# FIXME, add timestamp and age out old requests
|
# FIXME, add timestamp and age out old requests
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from decimal import Decimal
|
|||||||
from typing import Any, Callable, Dict, List, Optional, Union
|
from typing import Any, Callable, Dict, List, Optional, Union
|
||||||
|
|
||||||
import google.protobuf.json_format
|
import google.protobuf.json_format
|
||||||
import timeago # type: ignore[import-untyped]
|
|
||||||
from pubsub import pub # type: ignore[import-untyped]
|
from pubsub import pub # type: ignore[import-untyped]
|
||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
|
|
||||||
@@ -42,6 +41,29 @@ from meshtastic.util import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _timeago(delta_secs: int) -> str:
|
||||||
|
"""Convert a number of seconds in the past into a short, friendly string
|
||||||
|
e.g. "now", "30 sec ago", "1 hour ago"
|
||||||
|
Zero or negative intervals simply return "now"
|
||||||
|
"""
|
||||||
|
intervals = (
|
||||||
|
("year", 60 * 60 * 24 * 365),
|
||||||
|
("month", 60 * 60 * 24 * 30),
|
||||||
|
("day", 60 * 60 * 24),
|
||||||
|
("hour", 60 * 60),
|
||||||
|
("min", 60),
|
||||||
|
("sec", 1),
|
||||||
|
)
|
||||||
|
for name, interval_duration in intervals:
|
||||||
|
if delta_secs < interval_duration:
|
||||||
|
continue
|
||||||
|
x = delta_secs // interval_duration
|
||||||
|
plur = "s" if x > 1 else ""
|
||||||
|
return f"{x} {name}{plur} ago"
|
||||||
|
|
||||||
|
return "now"
|
||||||
|
|
||||||
|
|
||||||
class MeshInterface: # pylint: disable=R0902
|
class MeshInterface: # pylint: disable=R0902
|
||||||
"""Interface class for meshtastic devices
|
"""Interface class for meshtastic devices
|
||||||
|
|
||||||
@@ -158,11 +180,13 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
|
|
||||||
def getTimeAgo(ts) -> Optional[str]:
|
def getTimeAgo(ts) -> Optional[str]:
|
||||||
"""Format how long ago have we heard from this node (aka timeago)."""
|
"""Format how long ago have we heard from this node (aka timeago)."""
|
||||||
return (
|
if ts is None:
|
||||||
timeago.format(datetime.fromtimestamp(ts), datetime.now())
|
return None
|
||||||
if ts
|
delta = datetime.now() - datetime.fromtimestamp(ts)
|
||||||
else None
|
delta_secs = int(delta.total_seconds())
|
||||||
)
|
if delta_secs < 0:
|
||||||
|
return None # not handling a timestamp from the future
|
||||||
|
return _timeago(delta_secs)
|
||||||
|
|
||||||
rows: List[Dict[str, Any]] = []
|
rows: List[Dict[str, Any]] = []
|
||||||
if self.nodesByNum:
|
if self.nodesByNum:
|
||||||
@@ -296,6 +320,7 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
wantAck: bool=False,
|
wantAck: bool=False,
|
||||||
wantResponse: bool=False,
|
wantResponse: bool=False,
|
||||||
onResponse: Optional[Callable[[dict], Any]]=None,
|
onResponse: Optional[Callable[[dict], Any]]=None,
|
||||||
|
onResponseAckPermitted: bool=False,
|
||||||
channelIndex: int=0,
|
channelIndex: int=0,
|
||||||
):
|
):
|
||||||
"""Send a data packet to some other node
|
"""Send a data packet to some other node
|
||||||
@@ -315,6 +340,10 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
onResponse -- A closure of the form funct(packet), that will be
|
onResponse -- A closure of the form funct(packet), that will be
|
||||||
called when a response packet arrives (or the transaction
|
called when a response packet arrives (or the transaction
|
||||||
is NAKed due to non receipt)
|
is NAKed due to non receipt)
|
||||||
|
onResponseAckPermitted -- should the onResponse callback be called
|
||||||
|
for regular ACKs (True) or just data responses & NAKs (False)
|
||||||
|
Note that if the onResponse callback is called 'onAckNak' this
|
||||||
|
will implicitly be true.
|
||||||
channelIndex - channel number to use
|
channelIndex - channel number to use
|
||||||
|
|
||||||
Returns the sent packet. The id field will be populated in this packet
|
Returns the sent packet. The id field will be populated in this packet
|
||||||
@@ -346,7 +375,7 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
|
|
||||||
if onResponse is not None:
|
if onResponse is not None:
|
||||||
logging.debug(f"Setting a response handler for requestId {meshPacket.id}")
|
logging.debug(f"Setting a response handler for requestId {meshPacket.id}")
|
||||||
self._addResponseHandler(meshPacket.id, onResponse)
|
self._addResponseHandler(meshPacket.id, onResponse, ackPermitted=onResponseAckPermitted)
|
||||||
p = self._sendPacket(meshPacket, destinationId, wantAck=wantAck)
|
p = self._sendPacket(meshPacket, destinationId, wantAck=wantAck)
|
||||||
return p
|
return p
|
||||||
|
|
||||||
@@ -528,8 +557,8 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
if p["decoded"]["routing"]["errorReason"] == 'NO_RESPONSE':
|
if p["decoded"]["routing"]["errorReason"] == 'NO_RESPONSE':
|
||||||
our_exit("No response from node. At least firmware 2.1.22 is required on the destination node.")
|
our_exit("No response from node. At least firmware 2.1.22 is required on the destination node.")
|
||||||
|
|
||||||
def _addResponseHandler(self, requestId: int, callback: Callable[[dict], Any]):
|
def _addResponseHandler(self, requestId: int, callback: Callable[[dict], Any], ackPermitted: bool=False):
|
||||||
self.responseHandlers[requestId] = ResponseHandler(callback)
|
self.responseHandlers[requestId] = ResponseHandler(callback=callback, ackPermitted=ackPermitted)
|
||||||
|
|
||||||
def _sendPacket(self, meshPacket: mesh_pb2.MeshPacket, destinationId: Union[int,str]=BROADCAST_ADDR, wantAck: bool=False):
|
def _sendPacket(self, meshPacket: mesh_pb2.MeshPacket, destinationId: Union[int,str]=BROADCAST_ADDR, wantAck: bool=False):
|
||||||
"""Send a MeshPacket to the specified node (or if unspecified, broadcast).
|
"""Send a MeshPacket to the specified node (or if unspecified, broadcast).
|
||||||
@@ -1129,16 +1158,18 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
requestId = decoded.get("requestId")
|
requestId = decoded.get("requestId")
|
||||||
if requestId is not None:
|
if requestId is not None:
|
||||||
logging.debug(f"Got a response for requestId {requestId}")
|
logging.debug(f"Got a response for requestId {requestId}")
|
||||||
# We ignore ACK packets, but send NAKs and data responses to the handlers
|
# We ignore ACK packets unless the callback is named `onAckNak`
|
||||||
|
# or the handler is set as ackPermitted, but send NAKs and
|
||||||
|
# other, data-containing responses to the handlers
|
||||||
routing = decoded.get("routing")
|
routing = decoded.get("routing")
|
||||||
isAck = routing is not None and ("errorReason" not in routing or routing["errorReason"] == "NONE")
|
isAck = routing is not None and ("errorReason" not in routing or routing["errorReason"] == "NONE")
|
||||||
if not isAck:
|
# we keep the responseHandler in dict until we actually call it
|
||||||
# we keep the responseHandler in dict until we get a non ack
|
handler = self.responseHandlers.get(requestId, None)
|
||||||
handler = self.responseHandlers.pop(requestId, None)
|
if handler is not None:
|
||||||
if handler is not None:
|
if (not isAck) or handler.callback.__name__ == "onAckNak" or handler.ackPermitted:
|
||||||
if not isAck or (isAck and handler.__name__ == "onAckNak"):
|
handler = self.responseHandlers.pop(requestId, None)
|
||||||
logging.debug(f"Calling response handler for requestId {requestId}")
|
logging.debug(f"Calling response handler for requestId {requestId}")
|
||||||
handler.callback(asDict)
|
handler.callback(asDict)
|
||||||
|
|
||||||
logging.debug(f"Publishing {topic}: packet={stripnl(asDict)} ")
|
logging.debug(f"Publishing {topic}: packet={stripnl(asDict)} ")
|
||||||
publishingThread.queueWork(
|
publishingThread.queueWork(
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -116,6 +116,10 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
|||||||
"""
|
"""
|
||||||
wiphone https://www.wiphone.io/
|
wiphone https://www.wiphone.io/
|
||||||
"""
|
"""
|
||||||
|
WIO_WM1110: _HardwareModel.ValueType # 21
|
||||||
|
"""
|
||||||
|
WIO Tracker WM1110 family from Seeed Studio. Includes wio-1110-tracker and wio-1110-sdk
|
||||||
|
"""
|
||||||
STATION_G1: _HardwareModel.ValueType # 25
|
STATION_G1: _HardwareModel.ValueType # 25
|
||||||
"""
|
"""
|
||||||
B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
|
B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
|
||||||
@@ -391,6 +395,10 @@ WIPHONE: HardwareModel.ValueType # 20
|
|||||||
"""
|
"""
|
||||||
wiphone https://www.wiphone.io/
|
wiphone https://www.wiphone.io/
|
||||||
"""
|
"""
|
||||||
|
WIO_WM1110: HardwareModel.ValueType # 21
|
||||||
|
"""
|
||||||
|
WIO Tracker WM1110 family from Seeed Studio. Includes wio-1110-tracker and wio-1110-sdk
|
||||||
|
"""
|
||||||
STATION_G1: HardwareModel.ValueType # 25
|
STATION_G1: HardwareModel.ValueType # 25
|
||||||
"""
|
"""
|
||||||
B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
|
B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
|
||||||
@@ -2417,3 +2425,102 @@ class NodeRemoteHardwarePin(google.protobuf.message.Message):
|
|||||||
def ClearField(self, field_name: typing_extensions.Literal["node_num", b"node_num", "pin", b"pin"]) -> None: ...
|
def ClearField(self, field_name: typing_extensions.Literal["node_num", b"node_num", "pin", b"pin"]) -> None: ...
|
||||||
|
|
||||||
global___NodeRemoteHardwarePin = NodeRemoteHardwarePin
|
global___NodeRemoteHardwarePin = NodeRemoteHardwarePin
|
||||||
|
|
||||||
|
@typing_extensions.final
|
||||||
|
class ChunkedPayload(google.protobuf.message.Message):
|
||||||
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||||
|
|
||||||
|
PAYLOAD_ID_FIELD_NUMBER: builtins.int
|
||||||
|
CHUNK_COUNT_FIELD_NUMBER: builtins.int
|
||||||
|
CHUNK_INDEX_FIELD_NUMBER: builtins.int
|
||||||
|
PAYLOAD_CHUNK_FIELD_NUMBER: builtins.int
|
||||||
|
payload_id: builtins.int
|
||||||
|
"""
|
||||||
|
The ID of the entire payload
|
||||||
|
"""
|
||||||
|
chunk_count: builtins.int
|
||||||
|
"""
|
||||||
|
The total number of chunks in the payload
|
||||||
|
"""
|
||||||
|
chunk_index: builtins.int
|
||||||
|
"""
|
||||||
|
The current chunk index in the total
|
||||||
|
"""
|
||||||
|
payload_chunk: builtins.bytes
|
||||||
|
"""
|
||||||
|
The binary data of the current chunk
|
||||||
|
"""
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
payload_id: builtins.int = ...,
|
||||||
|
chunk_count: builtins.int = ...,
|
||||||
|
chunk_index: builtins.int = ...,
|
||||||
|
payload_chunk: builtins.bytes = ...,
|
||||||
|
) -> None: ...
|
||||||
|
def ClearField(self, field_name: typing_extensions.Literal["chunk_count", b"chunk_count", "chunk_index", b"chunk_index", "payload_chunk", b"payload_chunk", "payload_id", b"payload_id"]) -> None: ...
|
||||||
|
|
||||||
|
global___ChunkedPayload = ChunkedPayload
|
||||||
|
|
||||||
|
@typing_extensions.final
|
||||||
|
class resend_chunks(google.protobuf.message.Message):
|
||||||
|
"""
|
||||||
|
Wrapper message for broken repeated oneof support
|
||||||
|
"""
|
||||||
|
|
||||||
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||||
|
|
||||||
|
CHUNKS_FIELD_NUMBER: builtins.int
|
||||||
|
@property
|
||||||
|
def chunks(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: ...
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
chunks: collections.abc.Iterable[builtins.int] | None = ...,
|
||||||
|
) -> None: ...
|
||||||
|
def ClearField(self, field_name: typing_extensions.Literal["chunks", b"chunks"]) -> None: ...
|
||||||
|
|
||||||
|
global___resend_chunks = resend_chunks
|
||||||
|
|
||||||
|
@typing_extensions.final
|
||||||
|
class ChunkedPayloadResponse(google.protobuf.message.Message):
|
||||||
|
"""
|
||||||
|
Responses to a ChunkedPayload request
|
||||||
|
"""
|
||||||
|
|
||||||
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||||
|
|
||||||
|
PAYLOAD_ID_FIELD_NUMBER: builtins.int
|
||||||
|
REQUEST_TRANSFER_FIELD_NUMBER: builtins.int
|
||||||
|
ACCEPT_TRANSFER_FIELD_NUMBER: builtins.int
|
||||||
|
RESEND_CHUNKS_FIELD_NUMBER: builtins.int
|
||||||
|
payload_id: builtins.int
|
||||||
|
"""
|
||||||
|
The ID of the entire payload
|
||||||
|
"""
|
||||||
|
request_transfer: builtins.bool
|
||||||
|
"""
|
||||||
|
Request to transfer chunked payload
|
||||||
|
"""
|
||||||
|
accept_transfer: builtins.bool
|
||||||
|
"""
|
||||||
|
Accept the transfer chunked payload
|
||||||
|
"""
|
||||||
|
@property
|
||||||
|
def resend_chunks(self) -> global___resend_chunks:
|
||||||
|
"""
|
||||||
|
Request missing indexes in the chunked payload
|
||||||
|
"""
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
payload_id: builtins.int = ...,
|
||||||
|
request_transfer: builtins.bool = ...,
|
||||||
|
accept_transfer: builtins.bool = ...,
|
||||||
|
resend_chunks: global___resend_chunks | None = ...,
|
||||||
|
) -> None: ...
|
||||||
|
def HasField(self, field_name: typing_extensions.Literal["accept_transfer", b"accept_transfer", "payload_variant", b"payload_variant", "request_transfer", b"request_transfer", "resend_chunks", b"resend_chunks"]) -> builtins.bool: ...
|
||||||
|
def ClearField(self, field_name: typing_extensions.Literal["accept_transfer", b"accept_transfer", "payload_id", b"payload_id", "payload_variant", b"payload_variant", "request_transfer", b"request_transfer", "resend_chunks", b"resend_chunks"]) -> None: ...
|
||||||
|
def WhichOneof(self, oneof_group: typing_extensions.Literal["payload_variant", b"payload_variant"]) -> typing_extensions.Literal["request_transfer", "accept_transfer", "resend_chunks"] | None: ...
|
||||||
|
|
||||||
|
global___ChunkedPayloadResponse = ChunkedPayloadResponse
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ameshtastic/telemetry.proto\x12\nmeshtastic\"\x81\x01\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\x12\x16\n\x0euptime_seconds\x18\x05 \x01(\r\"\xda\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\x12\x0b\n\x03iaq\x18\x07 \x01(\r\x12\x10\n\x08\x64istance\x18\x08 \x01(\x02\x12\x0b\n\x03lux\x18\t \x01(\x02\x12\x11\n\twhite_lux\x18\n \x01(\x02\"\x8c\x01\n\x0cPowerMetrics\x12\x13\n\x0b\x63h1_voltage\x18\x01 \x01(\x02\x12\x13\n\x0b\x63h1_current\x18\x02 \x01(\x02\x12\x13\n\x0b\x63h2_voltage\x18\x03 \x01(\x02\x12\x13\n\x0b\x63h2_current\x18\x04 \x01(\x02\x12\x13\n\x0b\x63h3_voltage\x18\x05 \x01(\x02\x12\x13\n\x0b\x63h3_current\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\"\x89\x02\n\tTelemetry\x12\x0c\n\x04time\x18\x01 \x01(\x07\x12\x33\n\x0e\x64\x65vice_metrics\x18\x02 \x01(\x0b\x32\x19.meshtastic.DeviceMetricsH\x00\x12=\n\x13\x65nvironment_metrics\x18\x03 \x01(\x0b\x32\x1e.meshtastic.EnvironmentMetricsH\x00\x12<\n\x13\x61ir_quality_metrics\x18\x04 \x01(\x0b\x32\x1d.meshtastic.AirQualityMetricsH\x00\x12\x31\n\rpower_metrics\x18\x05 \x01(\x0b\x32\x18.meshtastic.PowerMetricsH\x00\x42\t\n\x07variant*\xcb\x02\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\r\x12\x0b\n\x07INA3221\x10\x0e\x12\n\n\x06\x42MP085\x10\x0f\x12\x0c\n\x08RCWL9620\x10\x10\x12\t\n\x05SHT4X\x10\x11\x12\x0c\n\x08VEML7700\x10\x12\x12\x0c\n\x08MLX90632\x10\x13\x12\x0b\n\x07OPT3001\x10\x14\x12\x0c\n\x08LTR390UV\x10\x15\x12\x0e\n\nTSL25911FN\x10\x16\x12\t\n\x05\x41HT10\x10\x17\x42\x64\n\x13\x63om.geeksville.meshB\x0fTelemetryProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ameshtastic/telemetry.proto\x12\nmeshtastic\"\x81\x01\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\x12\x16\n\x0euptime_seconds\x18\x05 \x01(\r\"\xa6\x02\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\x12\x0b\n\x03iaq\x18\x07 \x01(\r\x12\x10\n\x08\x64istance\x18\x08 \x01(\x02\x12\x0b\n\x03lux\x18\t \x01(\x02\x12\x11\n\twhite_lux\x18\n \x01(\x02\x12\x0e\n\x06ir_lux\x18\x0b \x01(\x02\x12\x0e\n\x06uv_lux\x18\x0c \x01(\x02\x12\x16\n\x0ewind_direction\x18\r \x01(\r\x12\x12\n\nwind_speed\x18\x0e \x01(\x02\"\x8c\x01\n\x0cPowerMetrics\x12\x13\n\x0b\x63h1_voltage\x18\x01 \x01(\x02\x12\x13\n\x0b\x63h1_current\x18\x02 \x01(\x02\x12\x13\n\x0b\x63h2_voltage\x18\x03 \x01(\x02\x12\x13\n\x0b\x63h2_current\x18\x04 \x01(\x02\x12\x13\n\x0b\x63h3_voltage\x18\x05 \x01(\x02\x12\x13\n\x0b\x63h3_current\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\"\x89\x02\n\tTelemetry\x12\x0c\n\x04time\x18\x01 \x01(\x07\x12\x33\n\x0e\x64\x65vice_metrics\x18\x02 \x01(\x0b\x32\x19.meshtastic.DeviceMetricsH\x00\x12=\n\x13\x65nvironment_metrics\x18\x03 \x01(\x0b\x32\x1e.meshtastic.EnvironmentMetricsH\x00\x12<\n\x13\x61ir_quality_metrics\x18\x04 \x01(\x0b\x32\x1d.meshtastic.AirQualityMetricsH\x00\x12\x31\n\rpower_metrics\x18\x05 \x01(\x0b\x32\x18.meshtastic.PowerMetricsH\x00\x42\t\n\x07variant*\xdd\x02\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\r\x12\x0b\n\x07INA3221\x10\x0e\x12\n\n\x06\x42MP085\x10\x0f\x12\x0c\n\x08RCWL9620\x10\x10\x12\t\n\x05SHT4X\x10\x11\x12\x0c\n\x08VEML7700\x10\x12\x12\x0c\n\x08MLX90632\x10\x13\x12\x0b\n\x07OPT3001\x10\x14\x12\x0c\n\x08LTR390UV\x10\x15\x12\x0e\n\nTSL25911FN\x10\x16\x12\t\n\x05\x41HT10\x10\x17\x12\x10\n\x0c\x44\x46ROBOT_LARK\x10\x18\x42\x64\n\x13\x63om.geeksville.meshB\x0fTelemetryProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.telemetry_pb2', globals())
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.telemetry_pb2', globals())
|
||||||
@@ -21,16 +21,16 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|||||||
|
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\017TelemetryProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\017TelemetryProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_TELEMETRYSENSORTYPE._serialized_start=1129
|
_TELEMETRYSENSORTYPE._serialized_start=1205
|
||||||
_TELEMETRYSENSORTYPE._serialized_end=1460
|
_TELEMETRYSENSORTYPE._serialized_end=1554
|
||||||
_DEVICEMETRICS._serialized_start=43
|
_DEVICEMETRICS._serialized_start=43
|
||||||
_DEVICEMETRICS._serialized_end=172
|
_DEVICEMETRICS._serialized_end=172
|
||||||
_ENVIRONMENTMETRICS._serialized_start=175
|
_ENVIRONMENTMETRICS._serialized_start=175
|
||||||
_ENVIRONMENTMETRICS._serialized_end=393
|
_ENVIRONMENTMETRICS._serialized_end=469
|
||||||
_POWERMETRICS._serialized_start=396
|
_POWERMETRICS._serialized_start=472
|
||||||
_POWERMETRICS._serialized_end=536
|
_POWERMETRICS._serialized_end=612
|
||||||
_AIRQUALITYMETRICS._serialized_start=539
|
_AIRQUALITYMETRICS._serialized_start=615
|
||||||
_AIRQUALITYMETRICS._serialized_end=858
|
_AIRQUALITYMETRICS._serialized_end=934
|
||||||
_TELEMETRY._serialized_start=861
|
_TELEMETRY._serialized_start=937
|
||||||
_TELEMETRY._serialized_end=1126
|
_TELEMETRY._serialized_end=1202
|
||||||
# @@protoc_insertion_point(module_scope)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
|||||||
@@ -118,6 +118,10 @@ class _TelemetrySensorTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wra
|
|||||||
"""
|
"""
|
||||||
AHT10 Integrated temperature and humidity sensor
|
AHT10 Integrated temperature and humidity sensor
|
||||||
"""
|
"""
|
||||||
|
DFROBOT_LARK: _TelemetrySensorType.ValueType # 24
|
||||||
|
"""
|
||||||
|
DFRobot Lark Weather station (temperature, humidity, pressure, wind speed and direction)
|
||||||
|
"""
|
||||||
|
|
||||||
class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
|
class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
|
||||||
"""
|
"""
|
||||||
@@ -220,6 +224,10 @@ AHT10: TelemetrySensorType.ValueType # 23
|
|||||||
"""
|
"""
|
||||||
AHT10 Integrated temperature and humidity sensor
|
AHT10 Integrated temperature and humidity sensor
|
||||||
"""
|
"""
|
||||||
|
DFROBOT_LARK: TelemetrySensorType.ValueType # 24
|
||||||
|
"""
|
||||||
|
DFRobot Lark Weather station (temperature, humidity, pressure, wind speed and direction)
|
||||||
|
"""
|
||||||
global___TelemetrySensorType = TelemetrySensorType
|
global___TelemetrySensorType = TelemetrySensorType
|
||||||
|
|
||||||
@typing_extensions.final
|
@typing_extensions.final
|
||||||
@@ -286,6 +294,10 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
|||||||
DISTANCE_FIELD_NUMBER: builtins.int
|
DISTANCE_FIELD_NUMBER: builtins.int
|
||||||
LUX_FIELD_NUMBER: builtins.int
|
LUX_FIELD_NUMBER: builtins.int
|
||||||
WHITE_LUX_FIELD_NUMBER: builtins.int
|
WHITE_LUX_FIELD_NUMBER: builtins.int
|
||||||
|
IR_LUX_FIELD_NUMBER: builtins.int
|
||||||
|
UV_LUX_FIELD_NUMBER: builtins.int
|
||||||
|
WIND_DIRECTION_FIELD_NUMBER: builtins.int
|
||||||
|
WIND_SPEED_FIELD_NUMBER: builtins.int
|
||||||
temperature: builtins.float
|
temperature: builtins.float
|
||||||
"""
|
"""
|
||||||
Temperature measured
|
Temperature measured
|
||||||
@@ -327,6 +339,23 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
VEML7700 high accuracy white light(irradiance) not calibrated digital 16-bit resolution sensor.
|
VEML7700 high accuracy white light(irradiance) not calibrated digital 16-bit resolution sensor.
|
||||||
"""
|
"""
|
||||||
|
ir_lux: builtins.float
|
||||||
|
"""
|
||||||
|
Infrared lux
|
||||||
|
"""
|
||||||
|
uv_lux: builtins.float
|
||||||
|
"""
|
||||||
|
Ultraviolet lux
|
||||||
|
"""
|
||||||
|
wind_direction: builtins.int
|
||||||
|
"""
|
||||||
|
Wind direction in degrees
|
||||||
|
0 degrees = North, 90 = East, etc...
|
||||||
|
"""
|
||||||
|
wind_speed: builtins.float
|
||||||
|
"""
|
||||||
|
Wind speed in m/s
|
||||||
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -340,8 +369,12 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
|||||||
distance: builtins.float = ...,
|
distance: builtins.float = ...,
|
||||||
lux: builtins.float = ...,
|
lux: builtins.float = ...,
|
||||||
white_lux: builtins.float = ...,
|
white_lux: builtins.float = ...,
|
||||||
|
ir_lux: builtins.float = ...,
|
||||||
|
uv_lux: builtins.float = ...,
|
||||||
|
wind_direction: builtins.int = ...,
|
||||||
|
wind_speed: builtins.float = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing_extensions.Literal["barometric_pressure", b"barometric_pressure", "current", b"current", "distance", b"distance", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "lux", b"lux", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "voltage", b"voltage", "white_lux", b"white_lux"]) -> None: ...
|
def ClearField(self, field_name: typing_extensions.Literal["barometric_pressure", b"barometric_pressure", "current", b"current", "distance", b"distance", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "ir_lux", b"ir_lux", "lux", b"lux", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "uv_lux", b"uv_lux", "voltage", b"voltage", "white_lux", b"white_lux", "wind_direction", b"wind_direction", "wind_speed", b"wind_speed"]) -> None: ...
|
||||||
|
|
||||||
global___EnvironmentMetrics = EnvironmentMetrics
|
global___EnvironmentMetrics = EnvironmentMetrics
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import re
|
|||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from hypothesis import given, strategies as st
|
||||||
|
|
||||||
from .. import mesh_pb2, config_pb2, BROADCAST_ADDR, LOCAL_ADDR
|
from .. import mesh_pb2, config_pb2, BROADCAST_ADDR, LOCAL_ADDR
|
||||||
from ..mesh_interface import MeshInterface
|
from ..mesh_interface import MeshInterface, _timeago
|
||||||
from ..node import Node
|
from ..node import Node
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
@@ -684,3 +685,21 @@ def test_waitConnected_isConnected_timeout(capsys):
|
|||||||
out, err = capsys.readouterr()
|
out, err = capsys.readouterr()
|
||||||
assert re.search(r"warn about something", err, re.MULTILINE)
|
assert re.search(r"warn about something", err, re.MULTILINE)
|
||||||
assert out == ""
|
assert out == ""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_timeago():
|
||||||
|
"""Test that the _timeago function returns sane values"""
|
||||||
|
assert _timeago(0) == "now"
|
||||||
|
assert _timeago(1) == "1 sec ago"
|
||||||
|
assert _timeago(15) == "15 secs ago"
|
||||||
|
assert _timeago(333) == "5 mins ago"
|
||||||
|
assert _timeago(99999) == "1 day ago"
|
||||||
|
assert _timeago(9999999) == "3 months ago"
|
||||||
|
assert _timeago(-999) == "now"
|
||||||
|
|
||||||
|
@given(seconds=st.integers())
|
||||||
|
def test_timeago_fuzz(seconds):
|
||||||
|
"""Fuzz _timeago to ensure it works with any integer"""
|
||||||
|
val = _timeago(seconds)
|
||||||
|
assert re.match(r"(now|\d+ (secs?|mins?|hours?|days?|months?|years?))", val)
|
||||||
|
|||||||
@@ -24,8 +24,16 @@ import serial.tools.list_ports # type: ignore[import-untyped]
|
|||||||
from meshtastic.supported_device import supported_devices
|
from meshtastic.supported_device import supported_devices
|
||||||
from meshtastic.version import get_active_version
|
from meshtastic.version import get_active_version
|
||||||
|
|
||||||
"""Some devices such as a seger jlink we never want to accidentally open"""
|
"""Some devices such as a seger jlink or st-link we never want to accidentally open
|
||||||
blacklistVids = dict.fromkeys([0x1366])
|
0x1915 NordicSemi (PPK2)
|
||||||
|
"""
|
||||||
|
blacklistVids = dict.fromkeys([0x1366, 0x0483, 0x1915])
|
||||||
|
|
||||||
|
"""Some devices are highly likely to be meshtastic.
|
||||||
|
0x239a RAK4631
|
||||||
|
0x303a Heltec tracker"""
|
||||||
|
whitelistVids = dict.fromkeys([0x239a, 0x303a])
|
||||||
|
|
||||||
|
|
||||||
def quoteBooleans(a_string):
|
def quoteBooleans(a_string):
|
||||||
"""Quote booleans
|
"""Quote booleans
|
||||||
@@ -130,19 +138,35 @@ def findPorts(eliminate_duplicates: bool=False) -> List[str]:
|
|||||||
Returns:
|
Returns:
|
||||||
list -- a list of device paths
|
list -- a list of device paths
|
||||||
"""
|
"""
|
||||||
l = list(
|
all_ports = serial.tools.list_ports.comports()
|
||||||
|
|
||||||
|
# look for 'likely' meshtastic devices
|
||||||
|
ports = list(
|
||||||
map(
|
map(
|
||||||
lambda port: port.device,
|
lambda port: port.device,
|
||||||
filter(
|
filter(
|
||||||
lambda port: port.vid is not None and port.vid not in blacklistVids,
|
lambda port: port.vid is not None and port.vid in whitelistVids,
|
||||||
serial.tools.list_ports.comports(),
|
all_ports,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
l.sort()
|
|
||||||
|
# if no likely devices, just list everything not blacklisted
|
||||||
|
if len(ports) == 0:
|
||||||
|
ports = list(
|
||||||
|
map(
|
||||||
|
lambda port: port.device,
|
||||||
|
filter(
|
||||||
|
lambda port: port.vid is not None and port.vid not in blacklistVids,
|
||||||
|
all_ports,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
ports.sort()
|
||||||
if eliminate_duplicates:
|
if eliminate_duplicates:
|
||||||
l = eliminate_duplicate_port(l)
|
ports = eliminate_duplicate_port(ports)
|
||||||
return l
|
return ports
|
||||||
|
|
||||||
|
|
||||||
class dotdict(dict):
|
class dotdict(dict):
|
||||||
|
|||||||
1555
poetry.lock
generated
Normal file
1555
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
Submodule protobufs updated: a45a6154d0...a641c5ce4f
51
pyproject.toml
Normal file
51
pyproject.toml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "meshtastic"
|
||||||
|
version = "2.3.11"
|
||||||
|
description = "Python API & client shell for talking to Meshtastic devices"
|
||||||
|
authors = ["Meshtastic Developers <contact@meshtastic.org>"]
|
||||||
|
license = "GPL-3.0-only"
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.8,<3.13" # was 3.7 for production but, 3.8 is needed for pytap2, 3.9 is needed for pandas, bleak requires a max of 3.13 for some reason
|
||||||
|
pyserial = "^3.5"
|
||||||
|
protobuf = ">=5.26.0"
|
||||||
|
dotmap = "^1.3.30"
|
||||||
|
pexpect = "^4.9.0"
|
||||||
|
pyqrcode = "^1.2.1"
|
||||||
|
tabulate = "^0.9.0"
|
||||||
|
webencodings = "^0.5.1"
|
||||||
|
requests = "^2.31.0"
|
||||||
|
pyparsing = "^3.1.2"
|
||||||
|
pyyaml = "^6.0.1"
|
||||||
|
pypubsub = "^4.0.3"
|
||||||
|
bleak = "^0.21.1"
|
||||||
|
packaging = "^24.0"
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
hypothesis = "^6.103.2"
|
||||||
|
pytest = "^8.2.2"
|
||||||
|
pytest-cov = "^5.0.0"
|
||||||
|
pdoc3 = "^0.10.0"
|
||||||
|
autopep8 = "^2.1.0"
|
||||||
|
pylint = "^3.2.3"
|
||||||
|
pytap2 = "^2.3.0"
|
||||||
|
pyinstaller = "^6.8.0"
|
||||||
|
mypy = "^1.10.0"
|
||||||
|
mypy-protobuf = "^3.6.0"
|
||||||
|
types-protobuf = "^5.26.0.20240422"
|
||||||
|
types-tabulate = "^0.9.0.20240106"
|
||||||
|
types-requests = "^2.31.0.20240406"
|
||||||
|
types-setuptools = "^69.5.0.20240423"
|
||||||
|
types-pyyaml = "^6.0.12.20240311"
|
||||||
|
|
||||||
|
[tool.poetry.extras]
|
||||||
|
tunnel = ["pytap2"]
|
||||||
|
|
||||||
|
[tool.poetry.scripts]
|
||||||
|
meshtastic = "meshtastic.__main__:main"
|
||||||
|
mesh-tunnel = "meshtastic.__main__:tunnelMain [tunnel]"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
markdown
|
|
||||||
pyserial
|
|
||||||
protobuf>=5.26.0
|
|
||||||
dotmap
|
|
||||||
pexpect
|
|
||||||
pyqrcode
|
|
||||||
tabulate
|
|
||||||
timeago
|
|
||||||
webencodings
|
|
||||||
requests
|
|
||||||
pyparsing
|
|
||||||
twine
|
|
||||||
autopep8
|
|
||||||
pylint
|
|
||||||
pytest
|
|
||||||
pytest-cov
|
|
||||||
hypothesis
|
|
||||||
pyyaml
|
|
||||||
pytap2
|
|
||||||
pdoc3
|
|
||||||
pypubsub
|
|
||||||
bleak
|
|
||||||
packaging
|
|
||||||
mypy
|
|
||||||
mypy-protobuf
|
|
||||||
types-protobuf>=5.26.0
|
|
||||||
types-tabulate
|
|
||||||
types-requests
|
|
||||||
types-setuptools
|
|
||||||
types-PyYAML
|
|
||||||
57
setup.py
57
setup.py
@@ -1,57 +0,0 @@
|
|||||||
# 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
|
|
||||||
HERE = pathlib.Path(__file__).parent
|
|
||||||
|
|
||||||
with open("README.md", "r") as fh:
|
|
||||||
long_description = fh.read()
|
|
||||||
|
|
||||||
# This call to setup() does all the work
|
|
||||||
setup(
|
|
||||||
name="meshtastic",
|
|
||||||
version="2.3.10",
|
|
||||||
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",
|
|
||||||
classifiers=[
|
|
||||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
|
||||||
"Development Status :: 4 - Beta",
|
|
||||||
"Programming Language :: Python :: 3.7",
|
|
||||||
"Programming Language :: Python :: 3.8",
|
|
||||||
"Programming Language :: Python :: 3.9",
|
|
||||||
"Programming Language :: Python :: 3.10",
|
|
||||||
"Programming Language :: Python :: 3.11",
|
|
||||||
],
|
|
||||||
packages=["meshtastic"],
|
|
||||||
include_package_data=True,
|
|
||||||
install_requires=[
|
|
||||||
"pyserial>=3.4",
|
|
||||||
"protobuf>=5.26.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",
|
|
||||||
"bleak>=0.21.1",
|
|
||||||
"packaging",
|
|
||||||
],
|
|
||||||
extras_require={"tunnel": ["pytap2>=2.0.0"]},
|
|
||||||
python_requires=">=3.7",
|
|
||||||
entry_points={
|
|
||||||
"console_scripts": [
|
|
||||||
"meshtastic=meshtastic.__main__:main",
|
|
||||||
"mesh-tunnel=meshtastic.__main__:tunnelMain [tunnel]",
|
|
||||||
]
|
|
||||||
},
|
|
||||||
)
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
import time
|
import meshtastic.serial_interface
|
||||||
|
|
||||||
import meshtastic
|
|
||||||
|
|
||||||
interface = (
|
interface = (
|
||||||
meshtastic.SerialInterface()
|
meshtastic.serial_interface.SerialInterface()
|
||||||
) # By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0
|
) # By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0
|
||||||
interface.sendText("hello mesh")
|
interface.sendText("hello mesh")
|
||||||
interface.close()
|
interface.close()
|
||||||
|
|||||||
Reference in New Issue
Block a user