mirror of
https://github.com/meshtastic/python.git
synced 2025-12-25 17:07:53 -05:00
Compare commits
154 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0aac077ce7 | ||
|
|
aa6f09635a | ||
|
|
83b0dcad56 | ||
|
|
78d8403bbd | ||
|
|
0813e8dba6 | ||
|
|
23bb2e26f9 | ||
|
|
b59ecff272 | ||
|
|
17f3605736 | ||
|
|
a689fd73a2 | ||
|
|
da30e1141a | ||
|
|
b4bd9568e4 | ||
|
|
aed4f25cf5 | ||
|
|
5c312bedc1 | ||
|
|
428e9a228c | ||
|
|
4500850063 | ||
|
|
aedaa3748d | ||
|
|
bcce5687c5 | ||
|
|
b9d805057f | ||
|
|
d77335caa7 | ||
|
|
b692ef4cfb | ||
|
|
e725292ee0 | ||
|
|
abf9e96d3d | ||
|
|
740f0f0961 | ||
|
|
abb00251c0 | ||
|
|
3335b3d651 | ||
|
|
4ad776f219 | ||
|
|
d5f732263a | ||
|
|
c59583e4bd | ||
|
|
28d8355547 | ||
|
|
d57186d1e4 | ||
|
|
a8d86dee2d | ||
|
|
40d03a6ea1 | ||
|
|
6757f5cdb5 | ||
|
|
b8c0a62b27 | ||
|
|
72de803195 | ||
|
|
84ffdcdb8c | ||
|
|
5366ddf770 | ||
|
|
fd4282b401 | ||
|
|
e84a3cb468 | ||
|
|
2ae18c1903 | ||
|
|
8096d10276 | ||
|
|
b0e1d961fd | ||
|
|
dfa3d46a34 | ||
|
|
de29bf34ef | ||
|
|
bf71e09091 | ||
|
|
4906f79be5 | ||
|
|
a4715171e4 | ||
|
|
c8eb202c15 | ||
|
|
ea0c7abc3d | ||
|
|
66f83835d9 | ||
|
|
39e03dbad8 | ||
|
|
4dbf9b94e9 | ||
|
|
b464e90368 | ||
|
|
3c76e19c33 | ||
|
|
7e007e7e24 | ||
|
|
d996965f0f | ||
|
|
fd9b691b74 | ||
|
|
628a4cb9be | ||
|
|
d0db5cae13 | ||
|
|
0bc608d8cf | ||
|
|
043530afca | ||
|
|
eb45c16f89 | ||
|
|
3c772b5a31 | ||
|
|
865bb6a497 | ||
|
|
c04943308a | ||
|
|
62cfe2d7fe | ||
|
|
ec4e521001 | ||
|
|
19d7e914bc | ||
|
|
64bb668251 | ||
|
|
8c63f4dec6 | ||
|
|
9297732806 | ||
|
|
a6c3e5cba8 | ||
|
|
d35423a816 | ||
|
|
84b4188211 | ||
|
|
72e0f2a92b | ||
|
|
ecbda74bd6 | ||
|
|
fb191092fb | ||
|
|
1e447cb52a | ||
|
|
462d9a83df | ||
|
|
4c02114b75 | ||
|
|
42e069455e | ||
|
|
1511d4ea99 | ||
|
|
b59aee91f2 | ||
|
|
2c8fd8b606 | ||
|
|
15b03b704c | ||
|
|
63327986b4 | ||
|
|
5695ec7102 | ||
|
|
ae2ef78560 | ||
|
|
2f5a736e1f | ||
|
|
775108b47b | ||
|
|
ae904f6dbe | ||
|
|
a14cc4f573 | ||
|
|
dc5f59260f | ||
|
|
91c42d598e | ||
|
|
6da04f7a15 | ||
|
|
8f98878cac | ||
|
|
13ca8fd681 | ||
|
|
1da687cf2d | ||
|
|
42236f2de8 | ||
|
|
821d3e95f1 | ||
|
|
542f99b28f | ||
|
|
dabb4ea44c | ||
|
|
119be81000 | ||
|
|
c9351236e6 | ||
|
|
2294546560 | ||
|
|
67bb6665f2 | ||
|
|
1587c31d18 | ||
|
|
715a085183 | ||
|
|
047a56d554 | ||
|
|
320bb30d29 | ||
|
|
f2c427430c | ||
|
|
ef4b534396 | ||
|
|
b063d33d77 | ||
|
|
8761b3270a | ||
|
|
4ca9aa29c2 | ||
|
|
231bc25255 | ||
|
|
ff20ad5d05 | ||
|
|
f8ad4fef7c | ||
|
|
d1aadf0c8e | ||
|
|
d448ea5767 | ||
|
|
402622f427 | ||
|
|
220241448f | ||
|
|
9b61f11c88 | ||
|
|
8d94458e55 | ||
|
|
1b045bec88 | ||
|
|
07fc991f4e | ||
|
|
c6561713db | ||
|
|
9cdfde47ec | ||
|
|
91066f6aed | ||
|
|
7ce7d73e89 | ||
|
|
43e1f65a75 | ||
|
|
dc8348b99e | ||
|
|
26a672ed58 | ||
|
|
ea18057c1f | ||
|
|
5ff4025ed6 | ||
|
|
1add293414 | ||
|
|
8b781d3245 | ||
|
|
6c0e978470 | ||
|
|
b7f7a40192 | ||
|
|
7b18fd599c | ||
|
|
46edd78f92 | ||
|
|
67e1e7c318 | ||
|
|
51c6c2cae1 | ||
|
|
9c657c6c8a | ||
|
|
79c65c1706 | ||
|
|
0e45637f2c | ||
|
|
be74c3eea0 | ||
|
|
362c1f3d2a | ||
|
|
cc60f3ebc0 | ||
|
|
a1f86a351a | ||
|
|
21e5601b23 | ||
|
|
338f00a64a | ||
|
|
a3462e0209 | ||
|
|
b41cb7d8df |
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Python 3
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
pip3 install poetry
|
||||
- name: Install meshtastic from local
|
||||
run: |
|
||||
poetry install
|
||||
poetry install --all-extras --with dev,powermon
|
||||
poetry run meshtastic --version
|
||||
- name: Run pylint
|
||||
run: poetry run pylint meshtastic examples/ --ignore-patterns ".*_pb2.pyi?$"
|
||||
@@ -56,10 +56,10 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Python 3
|
||||
|
||||
13
.github/workflows/release.yml
vendored
13
.github/workflows/release.yml
vendored
@@ -28,6 +28,11 @@ jobs:
|
||||
run: >-
|
||||
poetry version patch
|
||||
|
||||
- name: Get version
|
||||
id: get_version
|
||||
run: >-
|
||||
poetry version --short | sed 's/^/::set-output name=version::/'
|
||||
|
||||
- name: Commit updated version.
|
||||
id: commit_updated
|
||||
run: |
|
||||
@@ -35,14 +40,9 @@ jobs:
|
||||
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 pyproject.toml
|
||||
git commit -m "bump version" && git push || echo "No changes to commit"
|
||||
git commit -m "bump version to ${{ steps.get_version.outputs.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: Get version
|
||||
id: get_version
|
||||
run: >-
|
||||
poetry version --short | sed 's/^/::set-output name=version::/'
|
||||
|
||||
- name: Create GitHub release
|
||||
uses: actions/create-release@v1
|
||||
id: create_release
|
||||
@@ -52,6 +52,7 @@ jobs:
|
||||
prerelease: true
|
||||
release_name: Meshtastic Python ${{ steps.get_version.outputs.version }}
|
||||
tag_name: ${{ steps.get_version.outputs.version }}
|
||||
commitish: ${{ steps.commit_updated.outputs.sha }}
|
||||
body: |
|
||||
Autogenerated by github action, developer should edit as required before publishing...
|
||||
env:
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,4 +16,5 @@ __pycache__
|
||||
examples/__pycache__
|
||||
meshtastic.spec
|
||||
.hypothesis/
|
||||
coverage.xml
|
||||
coverage.xml
|
||||
.ipynb_checkpoints
|
||||
@@ -23,7 +23,7 @@ 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,pointless-string-statement,too-few-public-methods,broad-except,no-else-return,no-else-raise,bare-except,too-many-public-methods,nested-min-max
|
||||
|
||||
[BASIC]
|
||||
|
||||
|
||||
88
.vscode/launch.json
vendored
88
.vscode/launch.json
vendored
@@ -6,11 +6,11 @@
|
||||
"configurations": [
|
||||
{
|
||||
"name": "meshtastic BLE",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": false,
|
||||
"args": ["--ble", "Meshtastic_9f6e"]
|
||||
"args": ["--ble", "--info", "--seriallog", "stdout"]
|
||||
},
|
||||
{
|
||||
"name": "meshtastic BLE scan",
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic admin",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -30,15 +30,23 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic tunnel",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
"args": ["--tunnel", "--debug"]
|
||||
},
|
||||
{
|
||||
"name": "meshtastic analysis",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic.analysis",
|
||||
"justMyCode": false,
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"name": "meshtastic set chan",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -46,7 +54,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -54,7 +62,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic listen",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -62,7 +70,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug getPref",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -70,7 +78,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug getPref telemetry",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -78,7 +86,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug info",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -94,7 +102,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug set region",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -102,7 +110,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug set bluetooth fixed pin",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -110,7 +118,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug get bluetooth fixed pin",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -118,15 +126,15 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug setPref",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
"args": ["--debug", "--set", "power.is_power_saving", "1"]
|
||||
"args": ["--set", "power.powermon_enables", "65527"]
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug setPref telemetry.environment_measurement_enabled",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -134,7 +142,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug setPref telemetry.environment_screen_enabled",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -142,7 +150,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug setPref telemetry",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -150,7 +158,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic setpref",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -158,7 +166,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic --ch-set",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -167,7 +175,7 @@
|
||||
|
||||
{
|
||||
"name": "meshtastic seturl",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -176,15 +184,39 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic shell",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
"args": ["--debug", "--seriallog"]
|
||||
"justMyCode": false,
|
||||
"args": ["--noproto", "--seriallog", "stdout"]
|
||||
},
|
||||
{
|
||||
"name": "meshtastic powermon sim",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": false,
|
||||
"args": ["--slog-out", "default", "--power-sim", "--power-voltage", "3.3", "--port", "/dev/ttyUSB0", "--noproto", "--seriallog", "stdout"]
|
||||
},
|
||||
{
|
||||
"name": "meshtastic powermon ppk2",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": false,
|
||||
"args": ["--slog-out", "default", "--power-ppk2-meter", "--power-wait", "--power-voltage", "3.3", "--noproto", "--seriallog", "stdout"]
|
||||
},
|
||||
{
|
||||
"name": "meshtastic stress ppk2",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": false,
|
||||
"args": ["--slog", "--power-ppk2-supply", "--power-stress", "--power-voltage", "3.3", "--ble"]
|
||||
},
|
||||
{
|
||||
"name": "meshtastic test",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -192,7 +224,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic settime",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -200,7 +232,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic sendtext",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -208,7 +240,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic showNodes",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
|
||||
16
.vscode/settings.json
vendored
16
.vscode/settings.json
vendored
@@ -1,8 +1,22 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"bitmask",
|
||||
"boardid",
|
||||
"DEEPSLEEP",
|
||||
"Meshtastic",
|
||||
"milliwatt",
|
||||
"portnums",
|
||||
"powermon",
|
||||
"POWERSTRESS",
|
||||
"pyarrow",
|
||||
"TORADIO",
|
||||
"Vids"
|
||||
],
|
||||
"python.pythonPath": "/usr/bin/python3"
|
||||
"python.pythonPath": "/usr/bin/python3",
|
||||
"flake8.enabled": false,
|
||||
"python.testing.pytestArgs": [
|
||||
"meshtastic/tests"
|
||||
],
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestEnabled": true // we are using trunk for formatting/linting rules, don't yell at us about line length
|
||||
}
|
||||
@@ -2,6 +2,12 @@ set -e
|
||||
|
||||
# You may consider running: "pytest -m smoke1" instead of this test.
|
||||
|
||||
echo "Linting"
|
||||
poetry run pylint meshtastic examples/ --ignore-patterns ".*_pb2.pyi?$"
|
||||
|
||||
echo "Checking types"
|
||||
poetry run mypy meshtastic/
|
||||
|
||||
echo "Running (crude) prerelease tests to verify sanity"
|
||||
|
||||
# Use the python environment created by poetry
|
||||
|
||||
@@ -31,6 +31,7 @@ type of packet, you should subscribe to the full topic name. If you want to see
|
||||
- meshtastic.receive.user(packet)
|
||||
- meshtastic.receive.data.portnum(packet) (where portnum is an integer or well known PortNum enum)
|
||||
- meshtastic.node.updated(node = NodeInfo) - published when a node in the DB changes (appears, location changed, username changed, etc...)
|
||||
- meshtastic.log.line(line) - a raw unparsed log line from the radio
|
||||
|
||||
We receive position, user, or data packets from the mesh. You probably only care about meshtastic.receive.data. The first argument for
|
||||
that publish will be the packet. Text or binary data packets (from sendData or sendText) will both arrive this way. If you print packet
|
||||
@@ -96,6 +97,7 @@ from .protobuf import (
|
||||
remote_hardware_pb2,
|
||||
storeforward_pb2,
|
||||
telemetry_pb2,
|
||||
powermon_pb2
|
||||
)
|
||||
from . import (
|
||||
util,
|
||||
@@ -189,6 +191,16 @@ def _onNodeInfoReceive(iface, asDict):
|
||||
iface.nodes[p["id"]] = n
|
||||
_receiveInfoUpdate(iface, asDict)
|
||||
|
||||
def _onTelemetryReceive(iface, asDict):
|
||||
"""Automatically update device metrics on received packets"""
|
||||
logging.debug(f"in _onTelemetryReceive() asDict:{asDict}")
|
||||
deviceMetrics = asDict.get("decoded", {}).get("telemetry", {}).get("deviceMetrics")
|
||||
if "from" in asDict and deviceMetrics is not None:
|
||||
node = iface._getOrCreateByNum(asDict["from"])
|
||||
newMetrics = node.get("deviceMetrics", {})
|
||||
newMetrics.update(deviceMetrics)
|
||||
logging.debug(f"updating metrics for {asDict['from']} to {newMetrics}")
|
||||
node["deviceMetrics"] = newMetrics
|
||||
|
||||
def _receiveInfoUpdate(iface, asDict):
|
||||
if "from" in asDict:
|
||||
@@ -219,7 +231,7 @@ protocols = {
|
||||
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
|
||||
"telemetry", telemetry_pb2.Telemetry, _onTelemetryReceive
|
||||
),
|
||||
portnums_pb2.PortNum.REMOTE_HARDWARE_APP: KnownProtocol(
|
||||
"remotehw", remote_hardware_pb2.HardwareMessage
|
||||
@@ -228,6 +240,9 @@ protocols = {
|
||||
portnums_pb2.PortNum.TRACEROUTE_APP: KnownProtocol(
|
||||
"traceroute", mesh_pb2.RouteDiscovery
|
||||
),
|
||||
portnums_pb2.PortNum.POWERSTRESS_APP: KnownProtocol(
|
||||
"powerstress", powermon_pb2.PowerStressMessage
|
||||
),
|
||||
portnums_pb2.PortNum.WAYPOINT_APP: KnownProtocol("waypoint", mesh_pb2.Waypoint),
|
||||
portnums_pb2.PortNum.PAXCOUNTER_APP: KnownProtocol("paxcounter", paxcount_pb2.Paxcount),
|
||||
portnums_pb2.PortNum.STORE_FORWARD_APP: KnownProtocol("storeforward", storeforward_pb2.StoreAndForward),
|
||||
|
||||
@@ -1,27 +1,46 @@
|
||||
#!python3
|
||||
""" Main Meshtastic
|
||||
"""
|
||||
|
||||
# We just hit the 1600 line limit for main.py, but I currently have a huge set of powermon/structured logging changes
|
||||
# later we can have a separate changelist to refactor main.py into smaller files
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
import pyqrcode # type: ignore[import-untyped]
|
||||
import pyqrcode # type: ignore[import-untyped]
|
||||
import yaml
|
||||
from google.protobuf.json_format import MessageToDict
|
||||
from pubsub import pub # type: ignore[import-untyped]
|
||||
from pubsub import pub # type: ignore[import-untyped]
|
||||
|
||||
import meshtastic.test
|
||||
import meshtastic.util
|
||||
from meshtastic import mt_config
|
||||
from meshtastic.protobuf import channel_pb2, config_pb2, portnums_pb2
|
||||
from meshtastic import remote_hardware, BROADCAST_ADDR
|
||||
from meshtastic.version import get_active_version
|
||||
from meshtastic import BROADCAST_ADDR, mt_config, remote_hardware
|
||||
from meshtastic.ble_interface import BLEInterface
|
||||
from meshtastic.mesh_interface import MeshInterface
|
||||
try:
|
||||
from meshtastic.powermon import (
|
||||
PowerMeter,
|
||||
PowerStress,
|
||||
PPK2PowerSupply,
|
||||
RidenPowerSupply,
|
||||
SimPowerSupply,
|
||||
)
|
||||
from meshtastic.slog import LogSet
|
||||
have_powermon = True
|
||||
powermon_exception = None
|
||||
meter: Optional[PowerMeter] = None
|
||||
except ImportError as e:
|
||||
have_powermon = False
|
||||
powermon_exception = e
|
||||
meter = None
|
||||
from meshtastic.protobuf import channel_pb2, config_pb2, portnums_pb2
|
||||
from meshtastic.version import get_active_version
|
||||
|
||||
def onReceive(packet, interface):
|
||||
"""Callback invoked when a packet arrives"""
|
||||
@@ -58,11 +77,13 @@ def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=W0613
|
||||
"""Callback invoked when we connect/disconnect from a radio"""
|
||||
print(f"Connection changed: {topic.getName()}")
|
||||
|
||||
|
||||
def checkChannel(interface: MeshInterface, channelIndex: int) -> bool:
|
||||
"""Given an interface and channel index, return True if that channel is non-disabled on the local node"""
|
||||
ch = interface.localNode.getChannelByChannelIndex(channelIndex)
|
||||
logging.debug(f"ch:{ch}")
|
||||
return (ch and ch.role != channel_pb2.Channel.Role.DISABLED)
|
||||
return ch and ch.role != channel_pb2.Channel.Role.DISABLED
|
||||
|
||||
|
||||
def getPref(node, comp_name):
|
||||
"""Get a channel or preferences value"""
|
||||
@@ -138,6 +159,7 @@ def splitCompoundName(comp_name):
|
||||
name.append(comp_name)
|
||||
return name
|
||||
|
||||
|
||||
def traverseConfig(config_root, config, interface_config):
|
||||
"""Iterate through current config level preferences and either traverse deeper if preference is a dict or set preference"""
|
||||
snake_name = meshtastic.util.camel_to_snake(config_root)
|
||||
@@ -146,14 +168,11 @@ def traverseConfig(config_root, config, interface_config):
|
||||
if isinstance(config[pref], dict):
|
||||
traverseConfig(pref_name, config[pref], interface_config)
|
||||
else:
|
||||
setPref(
|
||||
interface_config,
|
||||
pref_name,
|
||||
str(config[pref])
|
||||
)
|
||||
setPref(interface_config, pref_name, str(config[pref]))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def setPref(config, comp_name, valStr) -> bool:
|
||||
"""Set a channel or preferences value"""
|
||||
|
||||
@@ -267,7 +286,9 @@ def onConnected(interface):
|
||||
interface.localNode.removeFixedPosition()
|
||||
elif args.setlat or args.setlon or args.setalt:
|
||||
if args.dest != BROADCAST_ADDR:
|
||||
print("Setting latitude, longitude, and altitude of remote nodes is not supported.")
|
||||
print(
|
||||
"Setting latitude, longitude, and altitude of remote nodes is not supported."
|
||||
)
|
||||
return
|
||||
closeNow = True
|
||||
|
||||
@@ -293,28 +314,17 @@ def onConnected(interface):
|
||||
print("Setting device position and enabling fixed position setting")
|
||||
# can include lat/long/alt etc: latitude = 37.5, longitude = -122.1
|
||||
interface.localNode.setFixedPosition(lat, lon, alt)
|
||||
elif not args.no_time:
|
||||
# We normally provide a current time to the mesh when we connect
|
||||
if interface.localNode.nodeNum in interface.nodesByNum and "position" in interface.nodesByNum[interface.localNode.nodeNum]:
|
||||
# send the same position the node already knows, just to update time
|
||||
position = interface.nodesByNum[interface.localNode.nodeNum]["position"]
|
||||
interface.sendPosition(position.get("latitude", 0.0), position.get("longitude", 0.0), position.get("altitude", 0.0))
|
||||
else:
|
||||
interface.sendPosition()
|
||||
|
||||
if args.set_owner:
|
||||
if args.set_owner or args.set_owner_short:
|
||||
closeNow = True
|
||||
waitForAckNak = True
|
||||
print(f"Setting device owner to {args.set_owner}")
|
||||
interface.getNode(args.dest, False).setOwner(args.set_owner)
|
||||
|
||||
if args.set_owner_short:
|
||||
closeNow = True
|
||||
waitForAckNak = True
|
||||
print(f"Setting device owner short to {args.set_owner_short}")
|
||||
interface.getNode(args.dest, False).setOwner(
|
||||
long_name=None, short_name=args.set_owner_short
|
||||
)
|
||||
if args.set_owner and args.set_owner_short:
|
||||
print(f"Setting device owner to {args.set_owner} and short name to {args.set_owner_short}")
|
||||
elif args.set_owner:
|
||||
print(f"Setting device owner to {args.set_owner}")
|
||||
else: # short name only
|
||||
print(f"Setting device owner short to {args.set_owner_short}")
|
||||
interface.getNode(args.dest, False).setOwner(long_name=args.set_owner, short_name=args.set_owner_short)
|
||||
|
||||
# TODO: add to export-config and configure
|
||||
if args.set_canned_message:
|
||||
@@ -446,7 +456,9 @@ def onConnected(interface):
|
||||
dest = str(args.traceroute)
|
||||
channelIndex = mt_config.channel_index or 0
|
||||
if checkChannel(interface, channelIndex):
|
||||
print(f"Sending traceroute request to {dest} on channelIndex:{channelIndex} (this could take a while)")
|
||||
print(
|
||||
f"Sending traceroute request to {dest} on channelIndex:{channelIndex} (this could take a while)"
|
||||
)
|
||||
interface.sendTraceRoute(dest, hopLimit, channelIndex=channelIndex)
|
||||
|
||||
if args.request_telemetry:
|
||||
@@ -455,8 +467,14 @@ def onConnected(interface):
|
||||
else:
|
||||
channelIndex = mt_config.channel_index or 0
|
||||
if checkChannel(interface, channelIndex):
|
||||
print(f"Sending telemetry request to {args.dest} on channelIndex:{channelIndex} (this could take a while)")
|
||||
interface.sendTelemetry(destinationId=args.dest, wantResponse=True, channelIndex=channelIndex)
|
||||
print(
|
||||
f"Sending telemetry request to {args.dest} on channelIndex:{channelIndex} (this could take a while)"
|
||||
)
|
||||
interface.sendTelemetry(
|
||||
destinationId=args.dest,
|
||||
wantResponse=True,
|
||||
channelIndex=channelIndex,
|
||||
)
|
||||
|
||||
if args.request_position:
|
||||
if args.dest == BROADCAST_ADDR:
|
||||
@@ -464,8 +482,14 @@ def onConnected(interface):
|
||||
else:
|
||||
channelIndex = mt_config.channel_index or 0
|
||||
if checkChannel(interface, channelIndex):
|
||||
print(f"Sending position request to {args.dest} on channelIndex:{channelIndex} (this could take a while)")
|
||||
interface.sendPosition(destinationId=args.dest, wantResponse=True, channelIndex=channelIndex)
|
||||
print(
|
||||
f"Sending position request to {args.dest} on channelIndex:{channelIndex} (this could take a while)"
|
||||
)
|
||||
interface.sendPosition(
|
||||
destinationId=args.dest,
|
||||
wantResponse=True,
|
||||
channelIndex=channelIndex,
|
||||
)
|
||||
|
||||
if args.gpio_wrb or args.gpio_rd or args.gpio_watch:
|
||||
if args.dest == BROADCAST_ADDR:
|
||||
@@ -607,7 +631,9 @@ def onConnected(interface):
|
||||
if "config" in configuration:
|
||||
localConfig = interface.getNode(args.dest).localConfig
|
||||
for section in configuration["config"]:
|
||||
traverseConfig(section, configuration["config"][section], localConfig)
|
||||
traverseConfig(
|
||||
section, configuration["config"][section], localConfig
|
||||
)
|
||||
interface.getNode(args.dest).writeConfig(
|
||||
meshtastic.util.camel_to_snake(section)
|
||||
)
|
||||
@@ -615,7 +641,11 @@ def onConnected(interface):
|
||||
if "module_config" in configuration:
|
||||
moduleConfig = interface.getNode(args.dest).moduleConfig
|
||||
for section in configuration["module_config"]:
|
||||
traverseConfig(section, configuration["module_config"][section], moduleConfig)
|
||||
traverseConfig(
|
||||
section,
|
||||
configuration["module_config"][section],
|
||||
moduleConfig,
|
||||
)
|
||||
interface.getNode(args.dest).writeConfig(
|
||||
meshtastic.util.camel_to_snake(section)
|
||||
)
|
||||
@@ -668,7 +698,9 @@ def onConnected(interface):
|
||||
print(f"Writing modified channels to device")
|
||||
n.writeChannel(ch.index)
|
||||
if channelIndex is None:
|
||||
print(f"Setting newly-added channel's {ch.index} as '--ch-index' for further modifications")
|
||||
print(
|
||||
f"Setting newly-added channel's {ch.index} as '--ch-index' for further modifications"
|
||||
)
|
||||
mt_config.channel_index = ch.index
|
||||
|
||||
if args.ch_del:
|
||||
@@ -728,7 +760,8 @@ def onConnected(interface):
|
||||
channelIndex = mt_config.channel_index
|
||||
if channelIndex is None:
|
||||
meshtastic.util.our_exit("Warning: Need to specify '--ch-index'.", 1)
|
||||
ch = interface.getNode(args.dest).channels[channelIndex]
|
||||
node = interface.getNode(args.dest)
|
||||
ch = node.channels[channelIndex]
|
||||
|
||||
if args.ch_enable or args.ch_disable:
|
||||
print(
|
||||
@@ -754,7 +787,7 @@ def onConnected(interface):
|
||||
else:
|
||||
found = setPref(ch.settings, pref[0], pref[1])
|
||||
if not found:
|
||||
category_settings = ['module_settings']
|
||||
category_settings = ["module_settings"]
|
||||
print(
|
||||
f"{ch.settings.__class__.__name__} does not have an attribute {pref[0]}."
|
||||
)
|
||||
@@ -764,7 +797,9 @@ def onConnected(interface):
|
||||
print(f"{field.name}")
|
||||
else:
|
||||
print(f"{field.name}:")
|
||||
config = ch.settings.DESCRIPTOR.fields_by_name.get(field.name)
|
||||
config = ch.settings.DESCRIPTOR.fields_by_name.get(
|
||||
field.name
|
||||
)
|
||||
names = []
|
||||
for sub_field in config.message_type.fields:
|
||||
tmp_name = f"{field.name}.{sub_field.name}"
|
||||
@@ -784,7 +819,7 @@ def onConnected(interface):
|
||||
ch.role = channel_pb2.Channel.Role.DISABLED
|
||||
|
||||
print(f"Writing modified channels to device")
|
||||
interface.getNode(args.dest).writeChannel(channelIndex)
|
||||
node.writeChannel(channelIndex)
|
||||
|
||||
if args.get_canned_message:
|
||||
closeNow = True
|
||||
@@ -844,6 +879,27 @@ def onConnected(interface):
|
||||
qr = pyqrcode.create(url)
|
||||
print(qr.terminal())
|
||||
|
||||
log_set: Optional = None # type: ignore[annotation-unchecked]
|
||||
# we need to keep a reference to the logset so it doesn't get GCed early
|
||||
|
||||
if args.slog or args.power_stress:
|
||||
if have_powermon:
|
||||
# Setup loggers
|
||||
global meter # pylint: disable=global-variable-not-assigned
|
||||
log_set = LogSet(
|
||||
interface, args.slog if args.slog != "default" else None, meter
|
||||
)
|
||||
|
||||
if args.power_stress:
|
||||
stress = PowerStress(interface)
|
||||
stress.run()
|
||||
closeNow = True # exit immediately after stress test
|
||||
else:
|
||||
meshtastic.util.our_exit("The powermon module could not be loaded. "
|
||||
"You may need to run `poetry install --with powermon`. "
|
||||
"Import Error was: " + powermon_exception)
|
||||
|
||||
|
||||
if args.listen:
|
||||
closeNow = False
|
||||
|
||||
@@ -872,13 +928,17 @@ def onConnected(interface):
|
||||
interface.getNode(args.dest, False).iface.waitForAckNak()
|
||||
|
||||
if args.wait_to_disconnect:
|
||||
print(f"Waiting {args.wait_to_disconnect} seconds before disconnecting" )
|
||||
print(f"Waiting {args.wait_to_disconnect} seconds before disconnecting")
|
||||
time.sleep(int(args.wait_to_disconnect))
|
||||
|
||||
# if the user didn't ask for serial debugging output, we might want to exit after we've done our operation
|
||||
if (not args.seriallog) and closeNow:
|
||||
interface.close() # after running command then exit
|
||||
|
||||
# Close any structured logs after we've done all of our API operations
|
||||
if log_set:
|
||||
log_set.close()
|
||||
|
||||
except Exception as ex:
|
||||
print(f"Aborting due to: {ex}")
|
||||
interface.close() # close the connection now, so that our app exits
|
||||
@@ -982,8 +1042,43 @@ def export_config(interface):
|
||||
return config
|
||||
|
||||
|
||||
def create_power_meter():
|
||||
"""Setup the power meter."""
|
||||
|
||||
global meter # pylint: disable=global-statement
|
||||
args = mt_config.args
|
||||
|
||||
# If the user specified a voltage, make sure it is valid
|
||||
v = 0.0
|
||||
if args.power_voltage:
|
||||
v = float(args.power_voltage)
|
||||
if v < 0.8 or v > 5.0:
|
||||
meshtastic.util.our_exit("Voltage must be between 0.8 and 5.0")
|
||||
|
||||
if args.power_riden:
|
||||
meter = RidenPowerSupply(args.power_riden)
|
||||
elif args.power_ppk2_supply or args.power_ppk2_meter:
|
||||
meter = PPK2PowerSupply()
|
||||
assert v > 0, "Voltage must be specified for PPK2"
|
||||
meter.v = v # PPK2 requires setting voltage before selecting supply mode
|
||||
meter.setIsSupply(args.power_ppk2_supply)
|
||||
elif args.power_sim:
|
||||
meter = SimPowerSupply()
|
||||
|
||||
if meter and v:
|
||||
logging.info(f"Setting power supply to {v} volts")
|
||||
meter.v = v
|
||||
meter.powerOn()
|
||||
|
||||
if args.power_wait:
|
||||
input("Powered on, press enter to continue...")
|
||||
else:
|
||||
logging.info("Powered-on, waiting for device to boot")
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
def common():
|
||||
"""Shared code for all of our command line wrappers"""
|
||||
"""Shared code for all of our command line wrappers."""
|
||||
logfile = None
|
||||
args = mt_config.args
|
||||
parser = mt_config.parser
|
||||
@@ -1000,6 +1095,9 @@ def common():
|
||||
meshtastic.util.support_info()
|
||||
meshtastic.util.our_exit("", 0)
|
||||
|
||||
if have_powermon:
|
||||
create_power_meter()
|
||||
|
||||
if args.ch_index is not None:
|
||||
channelIndex = int(args.ch_index)
|
||||
mt_config.channel_index = channelIndex
|
||||
@@ -1046,20 +1144,29 @@ def common():
|
||||
print(f"Found: name='{x.name}' address='{x.address}'")
|
||||
meshtastic.util.our_exit("BLE scan finished", 0)
|
||||
elif args.ble:
|
||||
client = BLEInterface(args.ble if args.ble != "any" else None, debugOut=logfile, noProto=args.noproto, noNodes=args.no_nodes)
|
||||
client = BLEInterface(
|
||||
args.ble if args.ble != "any" else None,
|
||||
debugOut=logfile,
|
||||
noProto=args.noproto,
|
||||
noNodes=args.no_nodes,
|
||||
)
|
||||
elif args.host:
|
||||
try:
|
||||
client = meshtastic.tcp_interface.TCPInterface(
|
||||
args.host, debugOut=logfile, noProto=args.noproto, noNodes=args.no_nodes
|
||||
args.host,
|
||||
debugOut=logfile,
|
||||
noProto=args.noproto,
|
||||
noNodes=args.no_nodes,
|
||||
)
|
||||
except Exception as ex:
|
||||
meshtastic.util.our_exit(
|
||||
f"Error connecting to {args.host}:{ex}", 1
|
||||
)
|
||||
meshtastic.util.our_exit(f"Error connecting to {args.host}:{ex}", 1)
|
||||
else:
|
||||
try:
|
||||
client = meshtastic.serial_interface.SerialInterface(
|
||||
args.port, debugOut=logfile, noProto=args.noproto, noNodes=args.no_nodes
|
||||
args.port,
|
||||
debugOut=logfile,
|
||||
noProto=args.noproto,
|
||||
noNodes=args.no_nodes,
|
||||
)
|
||||
except PermissionError as ex:
|
||||
username = os.getlogin()
|
||||
@@ -1074,34 +1181,48 @@ def common():
|
||||
if client.devPath is None:
|
||||
try:
|
||||
client = meshtastic.tcp_interface.TCPInterface(
|
||||
"localhost", debugOut=logfile, noProto=args.noproto, noNodes=args.no_nodes
|
||||
"localhost",
|
||||
debugOut=logfile,
|
||||
noProto=args.noproto,
|
||||
noNodes=args.no_nodes,
|
||||
)
|
||||
except Exception as ex:
|
||||
meshtastic.util.our_exit(
|
||||
f"Error connecting to localhost:{ex}", 1
|
||||
)
|
||||
|
||||
|
||||
# We assume client is fully connected now
|
||||
onConnected(client)
|
||||
|
||||
have_tunnel = platform.system() == "Linux"
|
||||
if (
|
||||
args.noproto or args.reply or (have_tunnel and args.tunnel) or args.listen
|
||||
args.noproto
|
||||
or args.reply
|
||||
or (have_tunnel and args.tunnel)
|
||||
or args.listen
|
||||
): # loop until someone presses ctrlc
|
||||
while True:
|
||||
time.sleep(1000)
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1000)
|
||||
except KeyboardInterrupt:
|
||||
logging.info("Exiting due to keyboard interrupt")
|
||||
|
||||
# don't call exit, background threads might be running still
|
||||
# sys.exit(0)
|
||||
|
||||
|
||||
def addConnectionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
|
||||
"""Add connection specifiation arguments"""
|
||||
|
||||
outer = parser.add_argument_group('Connection', 'Optional arguments that specify how to connect to a Meshtastic device.')
|
||||
outer = parser.add_argument_group(
|
||||
"Connection",
|
||||
"Optional arguments that specify how to connect to a Meshtastic device.",
|
||||
)
|
||||
group = outer.add_mutually_exclusive_group()
|
||||
group.add_argument(
|
||||
"--port", "--serial", "-s",
|
||||
"--port",
|
||||
"--serial",
|
||||
"-s",
|
||||
help="The port of the device to connect to using serial, e.g. /dev/ttyUSB0. (defaults to trying to detect a port)",
|
||||
nargs="?",
|
||||
const=None,
|
||||
@@ -1109,19 +1230,22 @@ def addConnectionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParse
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--host", "--tcp", "-t",
|
||||
"--host",
|
||||
"--tcp",
|
||||
"-t",
|
||||
help="Connect to a device using TCP, optionally passing hostname or IP address to use. (defaults to '%(const)s')",
|
||||
nargs="?",
|
||||
default=None,
|
||||
const="localhost"
|
||||
const="localhost",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--ble", "-b",
|
||||
"--ble",
|
||||
"-b",
|
||||
help="Connect to a BLE device, optionally specifying a device name (defaults to '%(const)s')",
|
||||
nargs="?",
|
||||
default=None,
|
||||
const="any"
|
||||
const="any",
|
||||
)
|
||||
|
||||
return parser
|
||||
@@ -1133,9 +1257,11 @@ def initParser():
|
||||
args = mt_config.args
|
||||
|
||||
# The "Help" group includes the help option and other informational stuff about the CLI itself
|
||||
outerHelpGroup = parser.add_argument_group('Help')
|
||||
outerHelpGroup = parser.add_argument_group("Help")
|
||||
helpGroup = outerHelpGroup.add_mutually_exclusive_group()
|
||||
helpGroup.add_argument("-h", "--help", action="help", help="show this help message and exit")
|
||||
helpGroup.add_argument(
|
||||
"-h", "--help", action="help", help="show this help message and exit"
|
||||
)
|
||||
|
||||
the_version = get_active_version()
|
||||
helpGroup.add_argument("--version", action="version", version=f"{the_version}")
|
||||
@@ -1172,9 +1298,9 @@ def initParser():
|
||||
group.add_argument(
|
||||
"--seriallog",
|
||||
help="Log device serial output to either 'none' or a filename to append to. Defaults to 'stdout' if no filename specified.",
|
||||
nargs='?',
|
||||
nargs="?",
|
||||
const="stdout",
|
||||
default=None
|
||||
default=None,
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
@@ -1323,6 +1449,10 @@ def initParser():
|
||||
|
||||
group.add_argument("--set-owner", help="Set device owner name", action="store")
|
||||
|
||||
group.add_argument(
|
||||
"--set-owner-short", help="Set device owner short name", action="store"
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--set-canned-message",
|
||||
help="Set the canned messages plugin message (up to 200 characters).",
|
||||
@@ -1335,10 +1465,6 @@ def initParser():
|
||||
action="store",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--set-owner-short", help="Set device owner short name", action="store"
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--set-ham", help="Set licensed Ham ID and turn off encryption", action="store"
|
||||
)
|
||||
@@ -1430,7 +1556,7 @@ def initParser():
|
||||
|
||||
group.add_argument(
|
||||
"--remove-node",
|
||||
help="Tell the destination node to remove a specific node from its DB, by node number or ID"
|
||||
help="Tell the destination node to remove a specific node from its DB, by node number or ID",
|
||||
)
|
||||
group.add_argument(
|
||||
"--reset-nodedb",
|
||||
@@ -1444,7 +1570,7 @@ def initParser():
|
||||
|
||||
group.add_argument(
|
||||
"--no-time",
|
||||
help="Suppress sending the current time to the mesh",
|
||||
help="Deprecated. Retained for backwards compatibility in scripts, but is a no-op.",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
@@ -1457,17 +1583,20 @@ def initParser():
|
||||
|
||||
group.add_argument(
|
||||
"--setalt",
|
||||
help="Set device altitude in meters (allows use without GPS), and enable fixed position.",
|
||||
help="Set device altitude in meters (allows use without GPS), and enable fixed position. "
|
||||
"When providing positions with `--setlat`, `--setlon`, and `--setalt`, missing values will be set to 0.",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--setlat",
|
||||
help="Set device latitude (allows use without GPS), and enable fixed position. Accepts a decimal value or an integer premultiplied by 1e7.",
|
||||
help="Set device latitude (allows use without GPS), and enable fixed position. Accepts a decimal value or an integer premultiplied by 1e7. "
|
||||
"When providing positions with `--setlat`, `--setlon`, and `--setalt`, missing values will be set to 0.",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--setlon",
|
||||
help="Set device longitude (allows use without GPS), and enable fixed position. Accepts a decimal value or an integer premultiplied by 1e7.",
|
||||
help="Set device longitude (allows use without GPS), and enable fixed position. Accepts a decimal value or an integer premultiplied by 1e7. "
|
||||
"When providing positions with `--setlat`, `--setlon`, and `--setalt`, missing values will be set to 0.",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
@@ -1495,6 +1624,60 @@ def initParser():
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
power_group = parser.add_argument_group(
|
||||
"Power Testing", "Options for power testing/logging."
|
||||
)
|
||||
|
||||
power_supply_group = power_group.add_mutually_exclusive_group()
|
||||
|
||||
power_supply_group.add_argument(
|
||||
"--power-riden",
|
||||
help="Talk to a Riden power-supply. You must specify the device path, i.e. /dev/ttyUSBxxx",
|
||||
)
|
||||
|
||||
power_supply_group.add_argument(
|
||||
"--power-ppk2-meter",
|
||||
help="Talk to a Nordic Power Profiler Kit 2 (in meter mode)",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
power_supply_group.add_argument(
|
||||
"--power-ppk2-supply",
|
||||
help="Talk to a Nordic Power Profiler Kit 2 (in supply mode)",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
power_supply_group.add_argument(
|
||||
"--power-sim",
|
||||
help="Use a simulated power meter (for development)",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
power_group.add_argument(
|
||||
"--power-voltage",
|
||||
help="Set the specified voltage on the power-supply. Be VERY careful, you can burn things up.",
|
||||
)
|
||||
|
||||
power_group.add_argument(
|
||||
"--power-stress",
|
||||
help="Perform power monitor stress testing, to capture a power consumption profile for the device (also requires --power-mon)",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
power_group.add_argument(
|
||||
"--power-wait",
|
||||
help="Prompt the user to wait for device reset before looking for device serial ports (some boards kill power to USB serial port)",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
power_group.add_argument(
|
||||
"--slog",
|
||||
help="Store structured-logs (slogs) for this run, optionally you can specifiy a destination directory",
|
||||
nargs="?",
|
||||
default=None,
|
||||
const="default",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--ble-scan",
|
||||
help="Scan for Meshtastic BLE devices",
|
||||
@@ -1521,7 +1704,9 @@ def initParser():
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
remoteHardwareArgs = parser.add_argument_group('Remote Hardware', 'Arguments related to the Remote Hardware module')
|
||||
remoteHardwareArgs = parser.add_argument_group(
|
||||
"Remote Hardware", "Arguments related to the Remote Hardware module"
|
||||
)
|
||||
|
||||
remoteHardwareArgs.add_argument(
|
||||
"--gpio-wrb", nargs=2, help="Set a particular GPIO # to 1 or 0", action="append"
|
||||
@@ -1535,10 +1720,11 @@ def initParser():
|
||||
"--gpio-watch", help="Start watching a GPIO mask for changes (ex: '0x10')"
|
||||
)
|
||||
|
||||
|
||||
have_tunnel = platform.system() == "Linux"
|
||||
if have_tunnel:
|
||||
tunnelArgs = parser.add_argument_group('Tunnel', 'Arguments related to establishing a tunnel device over the mesh.')
|
||||
tunnelArgs = parser.add_argument_group(
|
||||
"Tunnel", "Arguments related to establishing a tunnel device over the mesh."
|
||||
)
|
||||
tunnelArgs.add_argument(
|
||||
"--tunnel",
|
||||
action="store_true",
|
||||
@@ -1553,7 +1739,6 @@ def initParser():
|
||||
|
||||
parser.set_defaults(deprecated=None)
|
||||
|
||||
|
||||
args = parser.parse_args()
|
||||
mt_config.args = args
|
||||
mt_config.parser = parser
|
||||
@@ -1564,7 +1749,8 @@ def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
epilog="If no connection arguments are specified, we search for a compatible serial device, "
|
||||
"and if none is found, then attempt a TCP connection to localhost.")
|
||||
"and if none is found, then attempt a TCP connection to localhost.",
|
||||
)
|
||||
mt_config.parser = parser
|
||||
initParser()
|
||||
common()
|
||||
|
||||
1
meshtastic/analysis/__init__.py
Normal file
1
meshtastic/analysis/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Post-run analysis tools for meshtastic."""
|
||||
206
meshtastic/analysis/__main__.py
Normal file
206
meshtastic/analysis/__main__.py
Normal file
@@ -0,0 +1,206 @@
|
||||
"""Post-run analysis tools for meshtastic."""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
from typing import cast, List
|
||||
|
||||
import dash_bootstrap_components as dbc # type: ignore[import-untyped]
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import plotly.express as px # type: ignore[import-untyped]
|
||||
import plotly.graph_objects as go # type: ignore[import-untyped]
|
||||
import pyarrow as pa
|
||||
from dash import Dash, dcc, html # type: ignore[import-untyped]
|
||||
from pyarrow import feather
|
||||
|
||||
from .. import mesh_pb2, powermon_pb2
|
||||
from ..slog import root_dir
|
||||
|
||||
# Configure panda options
|
||||
pd.options.mode.copy_on_write = True
|
||||
|
||||
|
||||
def to_pmon_names(arr) -> List[str]:
|
||||
"""Convert the power monitor state numbers to their corresponding names.
|
||||
|
||||
arr (list): List of power monitor state numbers.
|
||||
|
||||
Returns the List of corresponding power monitor state names.
|
||||
"""
|
||||
|
||||
def to_pmon_name(n):
|
||||
try:
|
||||
s = powermon_pb2.PowerMon.State.Name(int(n))
|
||||
return s if s != "None" else None
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
return [to_pmon_name(x) for x in arr]
|
||||
|
||||
|
||||
def read_pandas(filepath: str) -> pd.DataFrame:
|
||||
"""Read a feather file and convert it to a pandas DataFrame.
|
||||
|
||||
filepath (str): Path to the feather file.
|
||||
|
||||
Returns the pandas DataFrame.
|
||||
"""
|
||||
# per https://arrow.apache.org/docs/python/pandas.html#reducing-memory-use-in-table-to-pandas
|
||||
# use this to get nullable int fields treated as ints rather than floats in pandas
|
||||
dtype_mapping = {
|
||||
pa.int8(): pd.Int8Dtype(),
|
||||
pa.int16(): pd.Int16Dtype(),
|
||||
pa.int32(): pd.Int32Dtype(),
|
||||
pa.int64(): pd.Int64Dtype(),
|
||||
pa.uint8(): pd.UInt8Dtype(),
|
||||
pa.uint16(): pd.UInt16Dtype(),
|
||||
pa.uint32(): pd.UInt32Dtype(),
|
||||
pa.uint64(): pd.UInt64Dtype(),
|
||||
pa.bool_(): pd.BooleanDtype(),
|
||||
pa.float32(): pd.Float32Dtype(),
|
||||
pa.float64(): pd.Float64Dtype(),
|
||||
pa.string(): pd.StringDtype(),
|
||||
}
|
||||
|
||||
return cast(pd.DataFrame, feather.read_table(filepath).to_pandas(types_mapper=dtype_mapping.get)) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def get_pmon_raises(dslog: pd.DataFrame) -> pd.DataFrame:
|
||||
"""Get the power monitor raises from the slog DataFrame.
|
||||
|
||||
dslog (pd.DataFrame): The slog DataFrame.
|
||||
|
||||
Returns the DataFrame containing the power monitor raises.
|
||||
"""
|
||||
pmon_events = dslog[dslog["pm_mask"].notnull()]
|
||||
|
||||
pm_masks = pd.Series(pmon_events["pm_mask"]).to_numpy()
|
||||
|
||||
# possible to do this with pandas rolling windows if I was smarter?
|
||||
pm_changes = [
|
||||
(pm_masks[i - 1] ^ x if i != 0 else x) for i, x in enumerate(pm_masks)
|
||||
]
|
||||
pm_raises = [(pm_masks[i] & x) for i, x in enumerate(pm_changes)]
|
||||
pm_falls = [(~pm_masks[i] & x if i != 0 else 0) for i, x in enumerate(pm_changes)]
|
||||
|
||||
pmon_events["pm_raises"] = to_pmon_names(pm_raises)
|
||||
pmon_events["pm_falls"] = to_pmon_names(pm_falls)
|
||||
|
||||
pmon_raises = pmon_events[pmon_events["pm_raises"].notnull()][["time", "pm_raises"]]
|
||||
pmon_falls = pmon_events[pmon_events["pm_falls"].notnull()]
|
||||
|
||||
# pylint: disable=unused-variable
|
||||
def get_endtime(row):
|
||||
"""Find the corresponding fall event."""
|
||||
following = pmon_falls[
|
||||
(pmon_falls["pm_falls"] == row["pm_raises"])
|
||||
& (pmon_falls["time"] > row["time"])
|
||||
]
|
||||
return following.iloc[0] if not following.empty else None
|
||||
|
||||
# HMM - setting end_time doesn't work yet - leave off for now
|
||||
# pmon_raises['end_time'] = pmon_raises.apply(get_endtime, axis=1)
|
||||
|
||||
return pmon_raises
|
||||
|
||||
|
||||
def get_board_info(dslog: pd.DataFrame) -> tuple:
|
||||
"""Get the board information from the slog DataFrame.
|
||||
|
||||
dslog (pd.DataFrame): The slog DataFrame.
|
||||
|
||||
Returns a tuple containing the board ID and software version.
|
||||
"""
|
||||
board_info = dslog[dslog["sw_version"].notnull()]
|
||||
sw_version = board_info.iloc[0]["sw_version"]
|
||||
board_id = mesh_pb2.HardwareModel.Name(board_info.iloc[0]["board_id"])
|
||||
return (board_id, sw_version)
|
||||
|
||||
|
||||
def create_argparser() -> argparse.ArgumentParser:
|
||||
"""Create the argument parser for the script."""
|
||||
parser = argparse.ArgumentParser(description="Meshtastic power analysis tools")
|
||||
group = parser
|
||||
group.add_argument(
|
||||
"--slog",
|
||||
help="Specify the structured-logs directory (defaults to latest log directory)",
|
||||
)
|
||||
group.add_argument(
|
||||
"--no-server",
|
||||
action="store_true",
|
||||
help="Exit immediately, without running the visualization web server",
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def create_dash(slog_path: str) -> Dash:
|
||||
"""Create a Dash application for visualizing power consumption data.
|
||||
|
||||
slog_path (str): Path to the slog directory.
|
||||
|
||||
Returns the Dash application.
|
||||
"""
|
||||
app = Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
|
||||
|
||||
dpwr = read_pandas(f"{slog_path}/power.feather")
|
||||
dslog = read_pandas(f"{slog_path}/slog.feather")
|
||||
|
||||
pmon_raises = get_pmon_raises(dslog)
|
||||
|
||||
def set_legend(f, name):
|
||||
f["data"][0]["showlegend"] = True
|
||||
f["data"][0]["name"] = name
|
||||
return f
|
||||
|
||||
avg_pwr_lines = px.line(dpwr, x="time", y="average_mW").update_traces(
|
||||
line_color="red"
|
||||
)
|
||||
set_legend(avg_pwr_lines, "avg power")
|
||||
max_pwr_points = px.scatter(dpwr, x="time", y="max_mW").update_traces(
|
||||
marker_color="blue"
|
||||
)
|
||||
set_legend(max_pwr_points, "max power")
|
||||
min_pwr_points = px.scatter(dpwr, x="time", y="min_mW").update_traces(
|
||||
marker_color="green"
|
||||
)
|
||||
set_legend(min_pwr_points, "min power")
|
||||
|
||||
fake_y = np.full(len(pmon_raises), 10.0)
|
||||
pmon_points = px.scatter(pmon_raises, x="time", y=fake_y, text="pm_raises")
|
||||
|
||||
fig = go.Figure(data=max_pwr_points.data + avg_pwr_lines.data + pmon_points.data)
|
||||
|
||||
fig.update_layout(
|
||||
legend={"yanchor": "top", "y": 0.99, "xanchor": "left", "x": 0.01}
|
||||
)
|
||||
|
||||
# App layout
|
||||
app.layout = [
|
||||
html.Div(children="Meshtastic power analysis tool testing..."),
|
||||
dcc.Graph(figure=fig),
|
||||
]
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def main():
|
||||
"""Entry point of the script."""
|
||||
|
||||
parser = create_argparser()
|
||||
args = parser.parse_args()
|
||||
if not args.slog:
|
||||
args.slog = f"{root_dir()}/latest"
|
||||
|
||||
app = create_dash(slog_path=args.slog)
|
||||
port = 8051
|
||||
logging.info(f"Running Dash visualization of {args.slog} (publicly accessible)")
|
||||
|
||||
if not args.no_server:
|
||||
app.run_server(debug=True, host="0.0.0.0", port=port)
|
||||
else:
|
||||
logging.info("Exiting without running visualization server")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -7,18 +7,15 @@ import struct
|
||||
import time
|
||||
from threading import Thread
|
||||
from typing import List, Optional
|
||||
import print_color # type: ignore[import-untyped]
|
||||
|
||||
import google.protobuf
|
||||
from bleak import BleakClient, BleakScanner, BLEDevice
|
||||
from bleak.exc import BleakDBusError, BleakError
|
||||
|
||||
import google.protobuf
|
||||
|
||||
from meshtastic.mesh_interface import MeshInterface
|
||||
|
||||
from .protobuf import (
|
||||
mesh_pb2,
|
||||
)
|
||||
from .protobuf import mesh_pb2
|
||||
|
||||
SERVICE_UUID = "6ba1b218-15a8-461f-9fa8-5dcae273eafd"
|
||||
TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7"
|
||||
FROMRADIO_UUID = "2c55e69e-4993-11ed-b878-0242ac120002"
|
||||
@@ -54,16 +51,19 @@ class BLEInterface(MeshInterface):
|
||||
self._receiveThread.start()
|
||||
logging.debug("Threads running")
|
||||
|
||||
self.client: Optional[BLEClient] = None
|
||||
try:
|
||||
logging.debug(f"BLE connecting to: {address if address else 'any'}")
|
||||
self.client: Optional[BLEClient] = self.connect(address)
|
||||
self.client = self.connect(address)
|
||||
logging.debug("BLE connected")
|
||||
except BLEInterface.BLEError as e:
|
||||
self.close()
|
||||
raise e
|
||||
|
||||
if self.client.has_characteristic(LEGACY_LOGRADIO_UUID):
|
||||
self.client.start_notify(LEGACY_LOGRADIO_UUID, self.legacy_log_radio_handler)
|
||||
self.client.start_notify(
|
||||
LEGACY_LOGRADIO_UUID, self.legacy_log_radio_handler
|
||||
)
|
||||
|
||||
if self.client.has_characteristic(LOGRADIO_UUID):
|
||||
self.client.start_notify(LOGRADIO_UUID, self.log_radio_handler)
|
||||
@@ -94,34 +94,19 @@ class BLEInterface(MeshInterface):
|
||||
log_record = mesh_pb2.LogRecord()
|
||||
try:
|
||||
log_record.ParseFromString(bytes(b))
|
||||
|
||||
message = (
|
||||
f"[{log_record.source}] {log_record.message}"
|
||||
if log_record.source
|
||||
else log_record.message
|
||||
)
|
||||
self._handleLogLine(message)
|
||||
except google.protobuf.message.DecodeError:
|
||||
return
|
||||
|
||||
message = f'[{log_record.source}] {log_record.message}' if log_record.source else log_record.message
|
||||
|
||||
if log_record.DEBUG:
|
||||
print_color.print(message, color="cyan", end=None)
|
||||
elif log_record.INFO:
|
||||
print_color.print(message, color="white", end=None)
|
||||
elif log_record.WARNING:
|
||||
print_color.print(message, color="yellow", end=None)
|
||||
elif log_record.ERROR:
|
||||
print_color.print(message, color="red", end=None)
|
||||
else:
|
||||
print_color.print(message, end=None)
|
||||
logging.warning("Malformed LogRecord received. Skipping.")
|
||||
|
||||
async def legacy_log_radio_handler(self, _, b): # pylint: disable=C0116
|
||||
log_radio = b.decode("utf-8").replace("\n", "")
|
||||
if log_radio.startswith("DEBUG"):
|
||||
print_color.print(log_radio, color="cyan", end=None)
|
||||
elif log_radio.startswith("INFO"):
|
||||
print_color.print(log_radio, color="white", end=None)
|
||||
elif log_radio.startswith("WARN"):
|
||||
print_color.print(log_radio, color="yellow", end=None)
|
||||
elif log_radio.startswith("ERROR"):
|
||||
print_color.print(log_radio, color="red", end=None)
|
||||
else:
|
||||
print_color.print(log_radio, end=None)
|
||||
self._handleLogLine(log_radio)
|
||||
|
||||
@staticmethod
|
||||
def scan() -> List[BLEDevice]:
|
||||
@@ -211,7 +196,7 @@ class BLEInterface(MeshInterface):
|
||||
|
||||
def _sendToRadioImpl(self, toRadio):
|
||||
b = toRadio.SerializeToString()
|
||||
if b:
|
||||
if b and self.client: # we silently ignore writes while we are shutting down
|
||||
logging.debug(f"TORADIO write: {b.hex()}")
|
||||
try:
|
||||
self.client.write_gatt_char(
|
||||
@@ -227,7 +212,6 @@ class BLEInterface(MeshInterface):
|
||||
self.should_read = True
|
||||
|
||||
def close(self):
|
||||
atexit.unregister(self._exit_handler)
|
||||
try:
|
||||
MeshInterface.close(self)
|
||||
except Exception as e:
|
||||
@@ -235,10 +219,14 @@ class BLEInterface(MeshInterface):
|
||||
|
||||
if self._want_receive:
|
||||
self.want_receive = False # Tell the thread we want it to stop
|
||||
self._receiveThread.join(timeout=2) # If bleak is hung, don't wait for the thread to exit (it is critical we disconnect)
|
||||
self._receiveThread = None
|
||||
if self._receiveThread:
|
||||
self._receiveThread.join(
|
||||
timeout=2
|
||||
) # If bleak is hung, don't wait for the thread to exit (it is critical we disconnect)
|
||||
self._receiveThread = None
|
||||
|
||||
if self.client:
|
||||
atexit.unregister(self._exit_handler)
|
||||
self.client.disconnect()
|
||||
self.client.close()
|
||||
self.client = None
|
||||
|
||||
@@ -10,20 +10,14 @@ import threading
|
||||
import time
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from typing import Any, Callable, Dict, List, Optional, Union
|
||||
|
||||
import google.protobuf.json_format
|
||||
from pubsub import pub # type: ignore[import-untyped]
|
||||
import print_color # type: ignore[import-untyped]
|
||||
from pubsub import pub # type: ignore[import-untyped]
|
||||
from tabulate import tabulate
|
||||
|
||||
import meshtastic.node
|
||||
|
||||
from meshtastic.protobuf import (
|
||||
mesh_pb2,
|
||||
portnums_pb2,
|
||||
telemetry_pb2,
|
||||
)
|
||||
from meshtastic import (
|
||||
BROADCAST_ADDR,
|
||||
BROADCAST_NUM,
|
||||
@@ -33,14 +27,15 @@ from meshtastic import (
|
||||
protocols,
|
||||
publishingThread,
|
||||
)
|
||||
from meshtastic.protobuf import mesh_pb2, portnums_pb2, telemetry_pb2
|
||||
from meshtastic.util import (
|
||||
Acknowledgment,
|
||||
Timeout,
|
||||
convert_mac_addr,
|
||||
message_to_json,
|
||||
our_exit,
|
||||
remove_keys_from_dict,
|
||||
stripnl,
|
||||
message_to_json,
|
||||
)
|
||||
|
||||
|
||||
@@ -67,7 +62,7 @@ def _timeago(delta_secs: int) -> str:
|
||||
return "now"
|
||||
|
||||
|
||||
class MeshInterface: # pylint: disable=R0902
|
||||
class MeshInterface: # pylint: disable=R0902
|
||||
"""Interface class for meshtastic devices
|
||||
|
||||
Properties:
|
||||
@@ -79,11 +74,14 @@ class MeshInterface: # pylint: disable=R0902
|
||||
|
||||
class MeshInterfaceError(Exception):
|
||||
"""An exception class for general mesh interface errors"""
|
||||
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
super().__init__(self.message)
|
||||
|
||||
def __init__(self, debugOut=None, noProto: bool=False, noNodes: bool=False) -> None:
|
||||
def __init__(
|
||||
self, debugOut=None, noProto: bool = False, noNodes: bool = False
|
||||
) -> None:
|
||||
"""Constructor
|
||||
|
||||
Keyword Arguments:
|
||||
@@ -93,13 +91,21 @@ class MeshInterface: # pylint: disable=R0902
|
||||
on startup, just other configuration information.
|
||||
"""
|
||||
self.debugOut = debugOut
|
||||
self.nodes: Optional[Dict[str,Dict]] = None # FIXME
|
||||
self.nodes: Optional[Dict[str, Dict]] = None # FIXME
|
||||
self.isConnected: threading.Event = threading.Event()
|
||||
self.noProto: bool = noProto
|
||||
self.localNode: meshtastic.node.Node = meshtastic.node.Node(self, -1) # We fixup nodenum later
|
||||
self.myInfo: Optional[mesh_pb2.MyNodeInfo] = None # We don't have device info yet
|
||||
self.metadata: Optional[mesh_pb2.DeviceMetadata] = None # We don't have device metadata yet
|
||||
self.responseHandlers: Dict[int,ResponseHandler] = {} # A map from request ID to the handler
|
||||
self.localNode: meshtastic.node.Node = meshtastic.node.Node(
|
||||
self, -1
|
||||
) # We fixup nodenum later
|
||||
self.myInfo: Optional[
|
||||
mesh_pb2.MyNodeInfo
|
||||
] = None # We don't have device info yet
|
||||
self.metadata: Optional[
|
||||
mesh_pb2.DeviceMetadata
|
||||
] = None # We don't have device metadata yet
|
||||
self.responseHandlers: Dict[
|
||||
int, ResponseHandler
|
||||
] = {} # A map from request ID to the handler
|
||||
self.failure = (
|
||||
None # If we've encountered a fatal exception it will be kept here
|
||||
)
|
||||
@@ -117,6 +123,12 @@ class MeshInterface: # pylint: disable=R0902
|
||||
self.queue: collections.OrderedDict = collections.OrderedDict()
|
||||
self._localChannels = None
|
||||
|
||||
# We could have just not passed in debugOut to MeshInterface, and instead told consumers to subscribe to
|
||||
# the meshtastic.log.line publish instead. Alas though changing that now would be a breaking API change
|
||||
# for any external consumers of the library.
|
||||
if debugOut:
|
||||
pub.subscribe(MeshInterface._printLogLine, "meshtastic.log.line")
|
||||
|
||||
def close(self):
|
||||
"""Shutdown this interface"""
|
||||
if self.heartbeatTimer:
|
||||
@@ -136,6 +148,39 @@ class MeshInterface: # pylint: disable=R0902
|
||||
logging.error(f"Traceback: {traceback}")
|
||||
self.close()
|
||||
|
||||
@staticmethod
|
||||
def _printLogLine(line, interface):
|
||||
"""Print a line of log output."""
|
||||
if interface.debugOut == sys.stdout:
|
||||
# this isn't quite correct (could cause false positives), but currently our formatting differs between different log representations
|
||||
if "DEBUG" in line:
|
||||
print_color.print(line, color="cyan", end=None)
|
||||
elif "INFO" in line:
|
||||
print_color.print(line, color="white", end=None)
|
||||
elif "WARN" in line:
|
||||
print_color.print(line, color="yellow", end=None)
|
||||
elif "ERR" in line:
|
||||
print_color.print(line, color="red", end=None)
|
||||
else:
|
||||
print_color.print(line, end=None)
|
||||
else:
|
||||
interface.debugOut.write(line + "\n")
|
||||
|
||||
def _handleLogLine(self, line: str) -> None:
|
||||
"""Handle a line of log output from the device."""
|
||||
|
||||
# Devices should _not_ be including a newline at the end of each log-line str (especially when
|
||||
# encapsulated as a LogRecord). But to cope with old device loads, we check for that and fix it here:
|
||||
if line.endswith("\n"):
|
||||
line = line[:-1]
|
||||
|
||||
pub.sendMessage("meshtastic.log.line", line=line, interface=self)
|
||||
|
||||
def _handleLogRecord(self, record: mesh_pb2.LogRecord) -> None:
|
||||
"""Handle a log record which was received encapsulated in a protobuf."""
|
||||
# For now we just try to format the line as if it had come in over the serial port
|
||||
self._handleLogLine(record.message)
|
||||
|
||||
def showInfo(self, file=sys.stdout) -> str: # pylint: disable=W0613
|
||||
"""Show human readable summary about this object"""
|
||||
owner = f"Owner: {self.getLongName()} ({self.getShortName()})"
|
||||
@@ -168,7 +213,9 @@ class MeshInterface: # pylint: disable=R0902
|
||||
print(infos)
|
||||
return infos
|
||||
|
||||
def showNodes(self, includeSelf: bool=True, file=sys.stdout) -> str: # pylint: disable=W0613
|
||||
def showNodes(
|
||||
self, includeSelf: bool = True
|
||||
) -> str: # pylint: disable=W0613
|
||||
"""Show table summary of nodes in mesh"""
|
||||
|
||||
def formatFloat(value, precision=2, unit="") -> Optional[str]:
|
||||
@@ -199,7 +246,11 @@ class MeshInterface: # pylint: disable=R0902
|
||||
continue
|
||||
|
||||
presumptive_id = f"!{node['num']:08x}"
|
||||
row = {"N": 0, "User": f"Meshtastic {presumptive_id[-4:]}", "ID": presumptive_id}
|
||||
row = {
|
||||
"N": 0,
|
||||
"User": f"Meshtastic {presumptive_id[-4:]}",
|
||||
"ID": presumptive_id,
|
||||
}
|
||||
|
||||
user = node.get("user")
|
||||
if user:
|
||||
@@ -208,7 +259,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
"User": user.get("longName", "N/A"),
|
||||
"AKA": user.get("shortName", "N/A"),
|
||||
"ID": user["id"],
|
||||
"Hardware": user.get("hwModel", "UNSET")
|
||||
"Hardware": user.get("hwModel", "UNSET"),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -262,7 +313,9 @@ class MeshInterface: # pylint: disable=R0902
|
||||
print(table)
|
||||
return table
|
||||
|
||||
def getNode(self, nodeId: str, requestChannels: bool=True) -> meshtastic.node.Node:
|
||||
def getNode(
|
||||
self, nodeId: str, requestChannels: bool = True
|
||||
) -> meshtastic.node.Node:
|
||||
"""Return a node object which contains device settings and channel info"""
|
||||
if nodeId in (LOCAL_ADDR, BROADCAST_ADDR):
|
||||
return self.localNode
|
||||
@@ -279,11 +332,11 @@ class MeshInterface: # pylint: disable=R0902
|
||||
def sendText(
|
||||
self,
|
||||
text: str,
|
||||
destinationId: Union[int, str]=BROADCAST_ADDR,
|
||||
wantAck: bool=False,
|
||||
wantResponse: bool=False,
|
||||
onResponse: Optional[Callable[[dict], Any]]=None,
|
||||
channelIndex: int=0,
|
||||
destinationId: Union[int, str] = BROADCAST_ADDR,
|
||||
wantAck: bool = False,
|
||||
wantResponse: bool = False,
|
||||
onResponse: Optional[Callable[[dict], Any]] = None,
|
||||
channelIndex: int = 0,
|
||||
):
|
||||
"""Send a utf8 string to some other node, if the node has a display it
|
||||
will also be shown on the device.
|
||||
@@ -325,6 +378,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
onResponse: Optional[Callable[[dict], Any]]=None,
|
||||
onResponseAckPermitted: bool=False,
|
||||
channelIndex: int=0,
|
||||
hopLimit: Optional[int]=None,
|
||||
):
|
||||
"""Send a data packet to some other node
|
||||
|
||||
@@ -347,7 +401,8 @@ class MeshInterface: # pylint: disable=R0902
|
||||
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
|
||||
hopLimit -- hop limit to use
|
||||
|
||||
Returns the sent packet. The id field will be populated in this packet
|
||||
and can be used to track future message acks/naks.
|
||||
@@ -379,19 +434,18 @@ class MeshInterface: # pylint: disable=R0902
|
||||
if onResponse is not None:
|
||||
logging.debug(f"Setting a response handler for requestId {meshPacket.id}")
|
||||
self._addResponseHandler(meshPacket.id, onResponse, ackPermitted=onResponseAckPermitted)
|
||||
p = self._sendPacket(meshPacket, destinationId, wantAck=wantAck)
|
||||
p = self._sendPacket(meshPacket, destinationId, wantAck=wantAck, hopLimit=hopLimit)
|
||||
return p
|
||||
|
||||
def sendPosition(
|
||||
self,
|
||||
latitude: float=0.0,
|
||||
longitude: float=0.0,
|
||||
altitude: int=0,
|
||||
timeSec: int=0,
|
||||
destinationId: Union[int, str]=BROADCAST_ADDR,
|
||||
wantAck: bool=False,
|
||||
wantResponse: bool=False,
|
||||
channelIndex: int=0,
|
||||
latitude: float = 0.0,
|
||||
longitude: float = 0.0,
|
||||
altitude: int = 0,
|
||||
destinationId: Union[int, str] = BROADCAST_ADDR,
|
||||
wantAck: bool = False,
|
||||
wantResponse: bool = False,
|
||||
channelIndex: int = 0,
|
||||
):
|
||||
"""
|
||||
Send a position packet to some other node (normally a broadcast)
|
||||
@@ -399,8 +453,6 @@ class MeshInterface: # pylint: disable=R0902
|
||||
Also, the device software will notice this packet and use it to automatically
|
||||
set its notion of the local position.
|
||||
|
||||
If timeSec is not specified (recommended), we will use the local machine time.
|
||||
|
||||
Returns the sent packet. The id field will be populated in this packet and
|
||||
can be used to track future message acks/naks.
|
||||
"""
|
||||
@@ -417,11 +469,6 @@ class MeshInterface: # pylint: disable=R0902
|
||||
p.altitude = int(altitude)
|
||||
logging.debug(f"p.altitude:{p.altitude}")
|
||||
|
||||
if timeSec == 0:
|
||||
timeSec = int(time.time()) # returns unix timestamp in seconds
|
||||
p.time = timeSec
|
||||
logging.debug(f"p.time:{p.time}")
|
||||
|
||||
if wantResponse:
|
||||
onResponse = self.onResponsePosition
|
||||
else:
|
||||
@@ -442,20 +489,22 @@ class MeshInterface: # pylint: disable=R0902
|
||||
|
||||
def onResponsePosition(self, p):
|
||||
"""on response for position"""
|
||||
if p["decoded"]["portnum"] == 'POSITION_APP':
|
||||
if p["decoded"]["portnum"] == "POSITION_APP":
|
||||
self._acknowledgment.receivedPosition = True
|
||||
position = mesh_pb2.Position()
|
||||
position.ParseFromString(p["decoded"]["payload"])
|
||||
|
||||
ret = "Position received: "
|
||||
if position.latitude_i != 0 and position.longitude_i != 0:
|
||||
ret += f"({position.latitude_i * 10**-7}, {position.longitude_i * 10**-7})"
|
||||
ret += (
|
||||
f"({position.latitude_i * 10**-7}, {position.longitude_i * 10**-7})"
|
||||
)
|
||||
else:
|
||||
ret += "(unknown)"
|
||||
if position.altitude != 0:
|
||||
ret += f" {position.altitude}m"
|
||||
|
||||
if position.precision_bits not in [0,32]:
|
||||
if position.precision_bits not in [0, 32]:
|
||||
ret += f" precision:{position.precision_bits}"
|
||||
elif position.precision_bits == 32:
|
||||
ret += " full precision"
|
||||
@@ -464,11 +513,15 @@ class MeshInterface: # pylint: disable=R0902
|
||||
|
||||
print(ret)
|
||||
|
||||
elif p["decoded"]["portnum"] == 'ROUTING_APP':
|
||||
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.")
|
||||
elif p["decoded"]["portnum"] == "ROUTING_APP":
|
||||
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."
|
||||
)
|
||||
|
||||
def sendTraceRoute(self, dest: Union[int, str], hopLimit: int, channelIndex: int=0):
|
||||
def sendTraceRoute(
|
||||
self, dest: Union[int, str], hopLimit: int, channelIndex: int = 0
|
||||
):
|
||||
"""Send the trace route"""
|
||||
r = mesh_pb2.RouteDiscovery()
|
||||
self.sendData(
|
||||
@@ -478,6 +531,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
wantResponse=True,
|
||||
onResponse=self.onResponseTraceRoute,
|
||||
channelIndex=channelIndex,
|
||||
hopLimit=hopLimit,
|
||||
)
|
||||
# extend timeout based on number of nodes, limit by configured hopLimit
|
||||
waitFactor = min(len(self.nodes) - 1 if self.nodes else 0, hopLimit)
|
||||
@@ -499,12 +553,19 @@ class MeshInterface: # pylint: disable=R0902
|
||||
|
||||
self._acknowledgment.receivedTraceRoute = True
|
||||
|
||||
def sendTelemetry(self, destinationId: Union[int,str]=BROADCAST_ADDR, wantResponse: bool=False, channelIndex: int=0):
|
||||
def sendTelemetry(
|
||||
self,
|
||||
destinationId: Union[int, str] = BROADCAST_ADDR,
|
||||
wantResponse: bool = False,
|
||||
channelIndex: int = 0,
|
||||
):
|
||||
"""Send telemetry and optionally ask for a response"""
|
||||
r = telemetry_pb2.Telemetry()
|
||||
|
||||
if self.nodes is not None:
|
||||
node = next(n for n in self.nodes.values() if n["num"] == self.localNode.nodeNum)
|
||||
node = next(
|
||||
n for n in self.nodes.values() if n["num"] == self.localNode.nodeNum
|
||||
)
|
||||
if node is not None:
|
||||
metrics = node.get("deviceMetrics")
|
||||
if metrics:
|
||||
@@ -520,6 +581,9 @@ class MeshInterface: # pylint: disable=R0902
|
||||
air_util_tx = metrics.get("airUtilTx")
|
||||
if air_util_tx is not None:
|
||||
r.device_metrics.air_util_tx = air_util_tx
|
||||
uptime_seconds = metrics.get("uptimeSeconds")
|
||||
if uptime_seconds is not None:
|
||||
r.device_metrics.uptime_seconds = uptime_seconds
|
||||
|
||||
if wantResponse:
|
||||
onResponse = self.onResponseTelemetry
|
||||
@@ -539,7 +603,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
|
||||
def onResponseTelemetry(self, p: dict):
|
||||
"""on response for telemetry"""
|
||||
if p["decoded"]["portnum"] == 'TELEMETRY_APP':
|
||||
if p["decoded"]["portnum"] == "TELEMETRY_APP":
|
||||
self._acknowledgment.receivedTelemetry = True
|
||||
telemetry = telemetry_pb2.Telemetry()
|
||||
telemetry.ParseFromString(p["decoded"]["payload"])
|
||||
@@ -554,16 +618,35 @@ class MeshInterface: # pylint: disable=R0902
|
||||
f"Total channel utilization: {telemetry.device_metrics.channel_utilization:.2f}%"
|
||||
)
|
||||
if telemetry.device_metrics.air_util_tx is not None:
|
||||
print(f"Transmit air utilization: {telemetry.device_metrics.air_util_tx:.2f}%")
|
||||
print(
|
||||
f"Transmit air utilization: {telemetry.device_metrics.air_util_tx:.2f}%"
|
||||
)
|
||||
if telemetry.device_metrics.uptime_seconds is not None:
|
||||
print(f"Uptime: {telemetry.device_metrics.uptime_seconds} s")
|
||||
|
||||
elif p["decoded"]["portnum"] == 'ROUTING_APP':
|
||||
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.")
|
||||
elif p["decoded"]["portnum"] == "ROUTING_APP":
|
||||
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."
|
||||
)
|
||||
|
||||
def _addResponseHandler(self, requestId: int, callback: Callable[[dict], Any], ackPermitted: bool=False):
|
||||
self.responseHandlers[requestId] = ResponseHandler(callback=callback, ackPermitted=ackPermitted)
|
||||
def _addResponseHandler(
|
||||
self,
|
||||
requestId: int,
|
||||
callback: Callable[[dict], Any],
|
||||
ackPermitted: bool = False,
|
||||
):
|
||||
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,
|
||||
hopLimit: Optional[int]=None
|
||||
):
|
||||
"""Send a MeshPacket to the specified node (or if unspecified, broadcast).
|
||||
You probably don't want this - use sendData instead.
|
||||
|
||||
@@ -604,9 +687,12 @@ class MeshInterface: # pylint: disable=R0902
|
||||
|
||||
meshPacket.to = nodeNum
|
||||
meshPacket.want_ack = wantAck
|
||||
loraConfig = getattr(self.localNode.localConfig, "lora")
|
||||
hopLimit = getattr(loraConfig, "hop_limit")
|
||||
meshPacket.hop_limit = hopLimit
|
||||
|
||||
if hopLimit is not None:
|
||||
meshPacket.hop_limit = hopLimit
|
||||
else:
|
||||
loraConfig = getattr(self.localNode.localConfig, "lora")
|
||||
meshPacket.hop_limit = getattr(loraConfig, "hop_limit")
|
||||
|
||||
# if the user hasn't set an ID for this packet (likely and recommended),
|
||||
# we should pick a new unique ID so the message can be tracked.
|
||||
@@ -630,13 +716,17 @@ class MeshInterface: # pylint: disable=R0902
|
||||
and self.localNode.waitForConfig()
|
||||
)
|
||||
if not success:
|
||||
raise MeshInterface.MeshInterfaceError("Timed out waiting for interface config")
|
||||
raise MeshInterface.MeshInterfaceError(
|
||||
"Timed out waiting for interface config"
|
||||
)
|
||||
|
||||
def waitForAckNak(self):
|
||||
"""Wait for the ack/nak"""
|
||||
success = self._timeout.waitForAckNak(self._acknowledgment)
|
||||
if not success:
|
||||
raise MeshInterface.MeshInterfaceError("Timed out waiting for an acknowledgment")
|
||||
raise MeshInterface.MeshInterfaceError(
|
||||
"Timed out waiting for an acknowledgment"
|
||||
)
|
||||
|
||||
def waitForTraceRoute(self, waitFactor):
|
||||
"""Wait for trace route"""
|
||||
@@ -689,7 +779,9 @@ class MeshInterface: # pylint: disable=R0902
|
||||
and raise an exception"""
|
||||
if not self.noProto:
|
||||
if not self.isConnected.wait(timeout): # timeout after x seconds
|
||||
raise MeshInterface.MeshInterfaceError("Timed out waiting for connection completion")
|
||||
raise MeshInterface.MeshInterfaceError(
|
||||
"Timed out waiting for connection completion"
|
||||
)
|
||||
|
||||
# If we failed while connecting, raise the connection to the client
|
||||
if self.failure:
|
||||
@@ -698,7 +790,9 @@ class MeshInterface: # pylint: disable=R0902
|
||||
def _generatePacketId(self) -> int:
|
||||
"""Get a new unique packet ID"""
|
||||
if self.currentPacketId is None:
|
||||
raise MeshInterface.MeshInterfaceError("Not connected yet, can not generate packet")
|
||||
raise MeshInterface.MeshInterfaceError(
|
||||
"Not connected yet, can not generate packet"
|
||||
)
|
||||
else:
|
||||
self.currentPacketId = (self.currentPacketId + 1) & 0xFFFFFFFF
|
||||
return self.currentPacketId
|
||||
@@ -710,20 +804,22 @@ class MeshInterface: # pylint: disable=R0902
|
||||
lambda: pub.sendMessage("meshtastic.connection.lost", interface=self)
|
||||
)
|
||||
|
||||
def sendHeartbeat(self):
|
||||
"""Sends a heartbeat to the radio. Can be used to verify the connection is healthy."""
|
||||
p = mesh_pb2.ToRadio()
|
||||
p.heartbeat.CopyFrom(mesh_pb2.Heartbeat())
|
||||
self._sendToRadio(p)
|
||||
|
||||
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
|
||||
logging.debug(f"Sending heartbeat, interval {i}")
|
||||
if i != 0:
|
||||
self.heartbeatTimer = threading.Timer(i, callback)
|
||||
self.heartbeatTimer.start()
|
||||
p = mesh_pb2.ToRadio()
|
||||
p.heartbeat.CopyFrom(mesh_pb2.Heartbeat())
|
||||
self._sendToRadio(p)
|
||||
interval = 300
|
||||
logging.debug(f"Sending heartbeat, interval {interval} seconds")
|
||||
self.heartbeatTimer = threading.Timer(interval, callback)
|
||||
self.heartbeatTimer.start()
|
||||
self.sendHeartbeat()
|
||||
|
||||
callback() # run our periodic callback now, it will make another timer if necessary
|
||||
|
||||
@@ -746,7 +842,9 @@ class MeshInterface: # pylint: disable=R0902
|
||||
self.myInfo = None
|
||||
self.nodes = {} # nodes keyed by ID
|
||||
self.nodesByNum = {} # nodes keyed by nodenum
|
||||
self._localChannels = [] # empty until we start getting channels pushed from the device (during config)
|
||||
self._localChannels = (
|
||||
[]
|
||||
) # empty until we start getting channels pushed from the device (during config)
|
||||
|
||||
startConfig = mesh_pb2.ToRadio()
|
||||
if self.configId is None or not self.noNodes:
|
||||
@@ -872,13 +970,6 @@ class MeshInterface: # pylint: disable=R0902
|
||||
self.localNode.nodeNum = self.myInfo.my_node_num
|
||||
logging.debug(f"Received myinfo: {stripnl(fromRadio.my_info)}")
|
||||
|
||||
failmsg = None
|
||||
|
||||
if failmsg:
|
||||
self.failure = MeshInterface.MeshInterfaceError(failmsg)
|
||||
self.isConnected.set() # let waitConnected return this exception
|
||||
self.close()
|
||||
|
||||
elif fromRadio.HasField("metadata"):
|
||||
self.metadata = fromRadio.metadata
|
||||
logging.debug(f"Received device metadata: {stripnl(fromRadio.metadata)}")
|
||||
@@ -895,7 +986,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
logging.debug("Node without position")
|
||||
|
||||
# no longer necessary since we're mutating directly in nodesByNum via _getOrCreateByNum
|
||||
#self.nodesByNum[node["num"]] = node
|
||||
# self.nodesByNum[node["num"]] = node
|
||||
if "user" in node: # Some nodes might not have user/ids assigned yet
|
||||
if "id" in node["user"]:
|
||||
self.nodes[node["user"]["id"]] = node
|
||||
@@ -913,21 +1004,26 @@ class MeshInterface: # pylint: disable=R0902
|
||||
self._handleChannel(fromRadio.channel)
|
||||
elif fromRadio.HasField("packet"):
|
||||
self._handlePacketFromRadio(fromRadio.packet)
|
||||
|
||||
elif fromRadio.HasField("log_record"):
|
||||
self._handleLogRecord(fromRadio.log_record)
|
||||
elif fromRadio.HasField("queueStatus"):
|
||||
self._handleQueueStatusFromRadio(fromRadio.queueStatus)
|
||||
|
||||
elif fromRadio.HasField("mqttClientProxyMessage"):
|
||||
publishingThread.queueWork(
|
||||
lambda: pub.sendMessage(
|
||||
"meshtastic.mqttclientproxymessage", proxymessage=fromRadio.mqttClientProxyMessage, interface=self
|
||||
"meshtastic.mqttclientproxymessage",
|
||||
proxymessage=fromRadio.mqttClientProxyMessage,
|
||||
interface=self,
|
||||
)
|
||||
)
|
||||
|
||||
elif fromRadio.HasField("xmodemPacket"):
|
||||
publishingThread.queueWork(
|
||||
lambda: pub.sendMessage(
|
||||
"meshtastic.xmodempacket", packet=fromRadio.xmodemPacket, interface=self
|
||||
"meshtastic.xmodempacket",
|
||||
packet=fromRadio.xmodemPacket,
|
||||
interface=self,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1034,7 +1130,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
return BROADCAST_ADDR
|
||||
|
||||
try:
|
||||
return self.nodesByNum[num]["user"]["id"] #type: ignore[index]
|
||||
return self.nodesByNum[num]["user"]["id"] # type: ignore[index]
|
||||
except:
|
||||
logging.debug(f"Node {num} not found for fromId")
|
||||
return None
|
||||
@@ -1042,7 +1138,9 @@ class MeshInterface: # pylint: disable=R0902
|
||||
def _getOrCreateByNum(self, nodeNum):
|
||||
"""Given a nodenum find the NodeInfo in the DB (or create if necessary)"""
|
||||
if nodeNum == BROADCAST_NUM:
|
||||
raise MeshInterface.MeshInterfaceError("Can not create/find nodenum by the broadcast num")
|
||||
raise MeshInterface.MeshInterfaceError(
|
||||
"Can not create/find nodenum by the broadcast num"
|
||||
)
|
||||
|
||||
if nodeNum in self.nodesByNum:
|
||||
return self.nodesByNum[nodeNum]
|
||||
@@ -1054,9 +1152,9 @@ class MeshInterface: # pylint: disable=R0902
|
||||
"id": presumptive_id,
|
||||
"longName": f"Meshtastic {presumptive_id[-4:]}",
|
||||
"shortName": f"{presumptive_id[-4:]}",
|
||||
"hwModel": "UNSET"
|
||||
}
|
||||
} # Create a minimal node db entry
|
||||
"hwModel": "UNSET",
|
||||
},
|
||||
} # Create a minimal node db entry
|
||||
self.nodesByNum[nodeNum] = n
|
||||
return n
|
||||
|
||||
@@ -1165,13 +1263,21 @@ class MeshInterface: # pylint: disable=R0902
|
||||
# or the handler is set as ackPermitted, but send NAKs and
|
||||
# other, data-containing responses to the handlers
|
||||
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"
|
||||
)
|
||||
# we keep the responseHandler in dict until we actually call it
|
||||
handler = self.responseHandlers.get(requestId, None)
|
||||
if handler is not None:
|
||||
if (not isAck) or handler.callback.__name__ == "onAckNak" or handler.ackPermitted:
|
||||
if (
|
||||
(not isAck)
|
||||
or handler.callback.__name__ == "onAckNak"
|
||||
or handler.ackPermitted
|
||||
):
|
||||
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)
|
||||
|
||||
logging.debug(f"Publishing {topic}: packet={stripnl(asDict)} ")
|
||||
|
||||
@@ -5,7 +5,7 @@ import base64
|
||||
import logging
|
||||
import time
|
||||
|
||||
from typing import Union
|
||||
from typing import Optional, Union
|
||||
|
||||
from meshtastic.protobuf import admin_pb2, apponly_pb2, channel_pb2, localonly_pb2, mesh_pb2, portnums_pb2
|
||||
from meshtastic.util import (
|
||||
@@ -128,7 +128,7 @@ class Node:
|
||||
print("Requesting current config from remote node (this can take a while).")
|
||||
|
||||
msgIndex = configType.index
|
||||
if configType.containing_type.full_name in ("meshtastic.LocalConfig", "LocalConfig"):
|
||||
if configType.containing_type.name == "LocalConfig":
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.get_config_request = msgIndex
|
||||
self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
|
||||
@@ -279,7 +279,7 @@ class Node:
|
||||
return c.index
|
||||
return 0
|
||||
|
||||
def setOwner(self, long_name=None, short_name=None, is_licensed=False):
|
||||
def setOwner(self, long_name: Optional[str]=None, short_name: Optional[str]=None, is_licensed: bool=False):
|
||||
"""Set device owner name"""
|
||||
logging.debug(f"in setOwner nodeNum:{self.nodeNum}")
|
||||
p = admin_pb2.AdminMessage()
|
||||
|
||||
7
meshtastic/powermon/__init__.py
Normal file
7
meshtastic/powermon/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Support for logging from power meters/supplies."""
|
||||
|
||||
from .power_supply import PowerError, PowerMeter, PowerSupply
|
||||
from .ppk2 import PPK2PowerSupply
|
||||
from .riden import RidenPowerSupply
|
||||
from .sim import SimPowerSupply
|
||||
from .stress import PowerStress
|
||||
52
meshtastic/powermon/power_supply.py
Normal file
52
meshtastic/powermon/power_supply.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""code logging power consumption of meshtastic devices."""
|
||||
|
||||
import math
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class PowerError(Exception):
|
||||
"""An exception class for powermon errors"""
|
||||
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
super().__init__(self.message)
|
||||
|
||||
|
||||
class PowerMeter:
|
||||
"""Abstract class for power meters."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the PowerMeter object."""
|
||||
self.prevPowerTime = datetime.now()
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the power meter."""
|
||||
|
||||
def get_average_current_mA(self) -> float:
|
||||
"""Returns average current of last measurement in mA (since last call to this method)"""
|
||||
return math.nan
|
||||
|
||||
def get_min_current_mA(self):
|
||||
"""Returns max current in mA (since last call to this method)."""
|
||||
# Subclasses must override for a better implementation
|
||||
return self.get_average_current_mA()
|
||||
|
||||
def get_max_current_mA(self):
|
||||
"""Returns max current in mA (since last call to this method)."""
|
||||
# Subclasses must override for a better implementation
|
||||
return self.get_average_current_mA()
|
||||
|
||||
def reset_measurements(self):
|
||||
"""Reset current measurements."""
|
||||
|
||||
|
||||
class PowerSupply(PowerMeter):
|
||||
"""Abstract class for power supplies."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the PowerSupply object."""
|
||||
super().__init__()
|
||||
self.v = 0.0
|
||||
|
||||
def powerOn(self):
|
||||
"""Turn on the power supply (using the voltage set in self.v)."""
|
||||
182
meshtastic/powermon/ppk2.py
Normal file
182
meshtastic/powermon/ppk2.py
Normal file
@@ -0,0 +1,182 @@
|
||||
"""Classes for logging power consumption of meshtastic devices."""
|
||||
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from ppk2_api import ppk2_api # type: ignore[import-untyped]
|
||||
|
||||
from .power_supply import PowerError, PowerSupply
|
||||
|
||||
|
||||
class PPK2PowerSupply(PowerSupply):
|
||||
"""Interface for talking with the NRF PPK2 high-resolution micro-power supply.
|
||||
Power Profiler Kit II is what you should google to find it for purchase.
|
||||
"""
|
||||
|
||||
def __init__(self, portName: Optional[str] = None):
|
||||
"""Initialize the PowerSupply object.
|
||||
|
||||
portName (str, optional): The port name of the power supply. Defaults to "/dev/ttyACM0".
|
||||
"""
|
||||
if not portName:
|
||||
devs = ppk2_api.PPK2_API.list_devices()
|
||||
if not devs or len(devs) == 0:
|
||||
raise PowerError("No PPK2 devices found")
|
||||
elif len(devs) > 1:
|
||||
raise PowerError(
|
||||
"Multiple PPK2 devices found, please specify the portName"
|
||||
)
|
||||
else:
|
||||
portName = devs[0]
|
||||
|
||||
self.measuring = False
|
||||
self.current_max = 0
|
||||
self.current_min = 0
|
||||
self.current_sum = 0
|
||||
self.current_num_samples = 0
|
||||
self.current_average = 0
|
||||
|
||||
# for tracking avera data read length (to determine if we are sleeping efficiently in measurement_loop)
|
||||
self.total_data_len = 0
|
||||
self.num_data_reads = 0
|
||||
self.max_data_len = 0
|
||||
|
||||
# Normally we just sleep with a timeout on this condition (polling the power measurement data repeatedly)
|
||||
# but any time our measurements have been fully consumed (via reset_measurements) we notify() this condition
|
||||
# to trigger a new reading ASAP.
|
||||
self._want_measurement = threading.Condition()
|
||||
|
||||
# To guard against a brief window while updating measured values
|
||||
self._result_lock = threading.Condition()
|
||||
|
||||
self.r = r = ppk2_api.PPK2_API(
|
||||
portName
|
||||
) # serial port will be different for you
|
||||
r.get_modifiers()
|
||||
|
||||
self.measurement_thread = threading.Thread(
|
||||
target=self.measurement_loop, daemon=True, name="ppk2 measurement"
|
||||
)
|
||||
logging.info("Connected to Power Profiler Kit II (PPK2)")
|
||||
super().__init__() # we call this late so that the port is already open and _getRawWattHour callback works
|
||||
|
||||
def measurement_loop(self):
|
||||
"""Endless measurement loop will run in a thread."""
|
||||
while self.measuring:
|
||||
with self._want_measurement:
|
||||
self._want_measurement.wait(
|
||||
0.0001 if self.num_data_reads == 0 else 0.001
|
||||
)
|
||||
# normally we poll using this timeout, but sometimes
|
||||
# reset_measurement() will notify us to read immediately
|
||||
|
||||
# always reads 4096 bytes, even if there is no new samples - or possibly the python single thread (because of global interpreter lock)
|
||||
# is always behind and thefore we are inherently dropping samples semi randomly!!!
|
||||
read_data = self.r.get_data()
|
||||
if read_data != b"":
|
||||
samples, _ = self.r.get_samples(read_data)
|
||||
|
||||
# update invariants
|
||||
if len(samples) > 0:
|
||||
if self.current_num_samples == 0:
|
||||
# First set of new reads, reset min/max
|
||||
self.current_max = 0
|
||||
self.current_min = samples[0]
|
||||
# we need at least one sample to get an initial min
|
||||
|
||||
# The following operations could be expensive, so do outside of the lock
|
||||
# FIXME - change all these lists into numpy arrays to use lots less CPU
|
||||
self.current_max = max(self.current_max, max(samples))
|
||||
self.current_min = min(self.current_min, min(samples))
|
||||
latest_sum = sum(samples)
|
||||
with self._result_lock:
|
||||
self.current_sum += latest_sum
|
||||
self.current_num_samples += len(samples)
|
||||
# logging.debug(f"PPK2 data_len={len(read_data)}, sample_len={len(samples)}")
|
||||
|
||||
self.num_data_reads += 1
|
||||
self.total_data_len += len(read_data)
|
||||
self.max_data_len = max(self.max_data_len, len(read_data))
|
||||
|
||||
def get_min_current_mA(self):
|
||||
"""Return the min current in mA."""
|
||||
return self.current_min / 1000
|
||||
|
||||
def get_max_current_mA(self):
|
||||
"""Return the max current in mA."""
|
||||
return self.current_max / 1000
|
||||
|
||||
def get_average_current_mA(self):
|
||||
"""Return the average current in mA."""
|
||||
with self._result_lock:
|
||||
if self.current_num_samples != 0:
|
||||
# If we have new samples, calculate a new average
|
||||
self.current_average = self.current_sum / self.current_num_samples
|
||||
|
||||
# Even if we don't have new samples, return the last calculated average
|
||||
# measurements are in microamperes, divide by 1000
|
||||
return self.current_average / 1000
|
||||
|
||||
def reset_measurements(self):
|
||||
"""Reset current measurements."""
|
||||
# Use the last reading as the new only reading (to ensure we always have a valid current reading)
|
||||
self.current_sum = 0
|
||||
self.current_num_samples = 0
|
||||
|
||||
# if self.num_data_reads:
|
||||
# logging.debug(f"max data len = {self.max_data_len},avg {self.total_data_len/self.num_data_reads}, num reads={self.num_data_reads}")
|
||||
# Summary stats for performance monitoring
|
||||
self.num_data_reads = 0
|
||||
self.total_data_len = 0
|
||||
self.max_data_len = 0
|
||||
|
||||
with self._want_measurement:
|
||||
self._want_measurement.notify() # notify the measurement loop to read immediately
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the power meter."""
|
||||
self.measuring = False
|
||||
self.r.stop_measuring() # send command to ppk2
|
||||
self.measurement_thread.join() # wait for our thread to finish
|
||||
super().close()
|
||||
|
||||
def setIsSupply(self, is_supply: bool):
|
||||
"""If in supply mode we will provide power ourself, otherwise we are just an amp meter."""
|
||||
|
||||
assert self.v > 0.8 # We must set a valid voltage before calling this method
|
||||
|
||||
self.r.set_source_voltage(
|
||||
int(self.v * 1000)
|
||||
) # set source voltage in mV BEFORE setting source mode
|
||||
# Note: source voltage must be set even if we are using the amp meter mode
|
||||
|
||||
# must be after setting source voltage and before setting mode
|
||||
self.r.start_measuring() # send command to ppk2
|
||||
|
||||
if (
|
||||
not is_supply
|
||||
): # min power outpuf of PPK2. If less than this assume we want just meter mode.
|
||||
self.r.use_ampere_meter()
|
||||
else:
|
||||
self.r.use_source_meter() # set source meter mode
|
||||
|
||||
if not self.measurement_thread.is_alive():
|
||||
self.measuring = True
|
||||
self.reset_measurements()
|
||||
|
||||
# We can't start reading from the thread until vdd is set, so start running the thread now
|
||||
self.measurement_thread.start()
|
||||
time.sleep(
|
||||
0.2
|
||||
) # FIXME - crufty way to ensure we do one set of reads to discard bogus fake power readings in the FIFO
|
||||
self.reset_measurements()
|
||||
|
||||
def powerOn(self):
|
||||
"""Power on the supply."""
|
||||
self.r.toggle_DUT_power("ON")
|
||||
|
||||
def powerOff(self):
|
||||
"""Power off the supply."""
|
||||
self.r.toggle_DUT_power("OFF")
|
||||
57
meshtastic/powermon/riden.py
Normal file
57
meshtastic/powermon/riden.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""code logging power consumption of meshtastic devices."""
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from riden import Riden
|
||||
|
||||
from .power_supply import PowerSupply
|
||||
|
||||
|
||||
class RidenPowerSupply(PowerSupply):
|
||||
"""Interface for talking to Riden programmable bench-top power supplies.
|
||||
Only RD6006 tested but others should be similar.
|
||||
"""
|
||||
|
||||
def __init__(self, portName: str = "/dev/ttyUSB0"):
|
||||
"""Initialize the RidenPowerSupply object.
|
||||
|
||||
portName (str, optional): The port name of the power supply. Defaults to "/dev/ttyUSB0".
|
||||
"""
|
||||
self.r = r = Riden(port=portName, baudrate=115200, address=1)
|
||||
logging.info(
|
||||
f"Connected to Riden power supply: model {r.type}, sn {r.sn}, firmware {r.fw}. Date/time updated."
|
||||
)
|
||||
r.set_date_time(datetime.now())
|
||||
self.prevWattHour = self._getRawWattHour()
|
||||
self.nowWattHour = self.prevWattHour
|
||||
super().__init__() # we call this late so that the port is already open and _getRawWattHour callback works
|
||||
|
||||
def setMaxCurrent(self, i: float):
|
||||
"""Set the maximum current the supply will provide."""
|
||||
self.r.set_i_set(i)
|
||||
|
||||
def powerOn(self):
|
||||
"""Power on the supply, with reasonable defaults for meshtastic devices."""
|
||||
self.r.set_v_set(
|
||||
self.v
|
||||
) # my WM1110 devboard header is directly connected to the 3.3V rail
|
||||
self.r.set_output(1)
|
||||
|
||||
def get_average_current_mA(self) -> float:
|
||||
"""Returns average current of last measurement in mA (since last call to this method)"""
|
||||
now = datetime.now()
|
||||
nowWattHour = self._getRawWattHour()
|
||||
watts = (
|
||||
(nowWattHour - self.prevWattHour)
|
||||
/ (now - self.prevPowerTime).total_seconds()
|
||||
* 3600
|
||||
)
|
||||
self.prevPowerTime = now
|
||||
self.prevWattHour = nowWattHour
|
||||
return watts / 1000
|
||||
|
||||
def _getRawWattHour(self) -> float:
|
||||
"""Get the current watt-hour reading."""
|
||||
self.r.update()
|
||||
return self.r.wh
|
||||
16
meshtastic/powermon/sim.py
Normal file
16
meshtastic/powermon/sim.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""code logging power consumption of meshtastic devices."""
|
||||
|
||||
import math
|
||||
import time
|
||||
|
||||
from .power_supply import PowerSupply
|
||||
|
||||
|
||||
class SimPowerSupply(PowerSupply):
|
||||
"""A simulated power supply for testing."""
|
||||
|
||||
def get_average_current_mA(self) -> float:
|
||||
"""Returns average current of last measurement in mA (since last call to this method)"""
|
||||
|
||||
# Sim a 20mW load that varies sinusoidally
|
||||
return (20.0 + 5 * math.sin(time.time()))
|
||||
117
meshtastic/powermon/stress.py
Normal file
117
meshtastic/powermon/stress.py
Normal file
@@ -0,0 +1,117 @@
|
||||
"""Power stress testing support.
|
||||
"""
|
||||
import logging
|
||||
import time
|
||||
|
||||
from ..protobuf import portnums_pb2, powermon_pb2
|
||||
|
||||
|
||||
def onPowerStressResponse(packet, interface):
|
||||
"""Delete me? FIXME"""
|
||||
logging.debug(f"packet:{packet} interface:{interface}")
|
||||
# interface.gotResponse = True
|
||||
|
||||
|
||||
class PowerStressClient:
|
||||
"""
|
||||
The client stub for talking to the firmware PowerStress module.
|
||||
"""
|
||||
|
||||
def __init__(self, iface, node_id=None):
|
||||
"""
|
||||
Create a new PowerStressClient instance.
|
||||
|
||||
iface is the already open MeshInterface instance
|
||||
"""
|
||||
self.iface = iface
|
||||
|
||||
if not node_id:
|
||||
node_id = iface.myInfo.my_node_num
|
||||
|
||||
self.node_id = node_id
|
||||
# No need to subscribe - because we
|
||||
# pub.subscribe(onGPIOreceive, "meshtastic.receive.powerstress")
|
||||
|
||||
def sendPowerStress(
|
||||
self,
|
||||
cmd: powermon_pb2.PowerStressMessage.Opcode.ValueType,
|
||||
num_seconds: float = 0.0,
|
||||
onResponse=None,
|
||||
):
|
||||
"""Client goo for talking with the device side agent."""
|
||||
r = powermon_pb2.PowerStressMessage()
|
||||
r.cmd = cmd
|
||||
r.num_seconds = num_seconds
|
||||
|
||||
return self.iface.sendData(
|
||||
r,
|
||||
self.node_id,
|
||||
portnums_pb2.POWERSTRESS_APP,
|
||||
wantAck=True,
|
||||
wantResponse=True,
|
||||
onResponse=onResponse,
|
||||
onResponseAckPermitted=True,
|
||||
)
|
||||
|
||||
def syncPowerStress(
|
||||
self,
|
||||
cmd: powermon_pb2.PowerStressMessage.Opcode.ValueType,
|
||||
num_seconds: float = 0.0,
|
||||
):
|
||||
"""Send a power stress command and wait for the ack."""
|
||||
gotAck = False
|
||||
|
||||
def onResponse(packet: dict): # pylint: disable=unused-argument
|
||||
nonlocal gotAck
|
||||
gotAck = True
|
||||
|
||||
logging.info(
|
||||
f"Sending power stress command {powermon_pb2.PowerStressMessage.Opcode.Name(cmd)}"
|
||||
)
|
||||
self.sendPowerStress(cmd, onResponse=onResponse, num_seconds=num_seconds)
|
||||
|
||||
if num_seconds == 0.0:
|
||||
# Wait for the response and then continue
|
||||
while not gotAck:
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
# we wait a little bit longer than the time the UUT would be waiting (to make sure all of its messages are handled first)
|
||||
time.sleep(
|
||||
num_seconds + 0.2
|
||||
) # completely block our thread for the duration of the test
|
||||
if not gotAck:
|
||||
logging.error("Did not receive ack for power stress command!")
|
||||
|
||||
|
||||
class PowerStress:
|
||||
"""Walk the UUT through a set of power states so we can capture repeatable power consumption measurements."""
|
||||
|
||||
def __init__(self, iface):
|
||||
self.client = PowerStressClient(iface)
|
||||
|
||||
def run(self):
|
||||
"""Run the power stress test."""
|
||||
try:
|
||||
self.client.syncPowerStress(powermon_pb2.PowerStressMessage.PRINT_INFO)
|
||||
|
||||
num_seconds = 5.0
|
||||
states = [
|
||||
powermon_pb2.PowerStressMessage.LED_ON,
|
||||
powermon_pb2.PowerStressMessage.LED_OFF,
|
||||
powermon_pb2.PowerStressMessage.BT_OFF,
|
||||
powermon_pb2.PowerStressMessage.BT_ON,
|
||||
powermon_pb2.PowerStressMessage.CPU_FULLON,
|
||||
powermon_pb2.PowerStressMessage.CPU_IDLE,
|
||||
# FIXME - can't test deepsleep yet because the ttyACM device disappears. Fix the python code to retry connections
|
||||
# powermon_pb2.PowerStressMessage.CPU_DEEPSLEEP,
|
||||
]
|
||||
for s in states:
|
||||
s_name = powermon_pb2.PowerStressMessage.Opcode.Name(s)
|
||||
logging.info(
|
||||
f"Running power stress test {s_name} for {num_seconds} seconds"
|
||||
)
|
||||
self.client.syncPowerStress(s, num_seconds)
|
||||
|
||||
logging.info("Power stress test complete.")
|
||||
except KeyboardInterrupt as e:
|
||||
logging.warning(f"Power stress interrupted: {e}")
|
||||
File diff suppressed because one or more lines are too long
@@ -56,6 +56,7 @@ class Config(google.protobuf.message.Message):
|
||||
ROUTER_CLIENT: Config.DeviceConfig._Role.ValueType # 3
|
||||
"""
|
||||
Description: Combination of both ROUTER and CLIENT. Not for mobile devices.
|
||||
Deprecated in v2.3.15 because improper usage is impacting public meshes: Use ROUTER or CLIENT instead.
|
||||
"""
|
||||
REPEATER: Config.DeviceConfig._Role.ValueType # 4
|
||||
"""
|
||||
@@ -131,6 +132,7 @@ class Config(google.protobuf.message.Message):
|
||||
ROUTER_CLIENT: Config.DeviceConfig.Role.ValueType # 3
|
||||
"""
|
||||
Description: Combination of both ROUTER and CLIENT. Not for mobile devices.
|
||||
Deprecated in v2.3.15 because improper usage is impacting public meshes: Use ROUTER or CLIENT instead.
|
||||
"""
|
||||
REPEATER: Config.DeviceConfig.Role.ValueType # 4
|
||||
"""
|
||||
@@ -580,6 +582,7 @@ class Config(google.protobuf.message.Message):
|
||||
LS_SECS_FIELD_NUMBER: builtins.int
|
||||
MIN_WAKE_SECS_FIELD_NUMBER: builtins.int
|
||||
DEVICE_BATTERY_INA_ADDRESS_FIELD_NUMBER: builtins.int
|
||||
POWERMON_ENABLES_FIELD_NUMBER: builtins.int
|
||||
is_power_saving: builtins.bool
|
||||
"""
|
||||
Description: Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio.
|
||||
@@ -623,6 +626,11 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
I2C address of INA_2XX to use for reading device battery voltage
|
||||
"""
|
||||
powermon_enables: builtins.int
|
||||
"""
|
||||
If non-zero, we want powermon log outputs. With the particular (bitfield) sources enabled.
|
||||
Note: we picked an ID of 32 so that lower more efficient IDs can be used for more frequently used options.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -634,8 +642,9 @@ class Config(google.protobuf.message.Message):
|
||||
ls_secs: builtins.int = ...,
|
||||
min_wake_secs: builtins.int = ...,
|
||||
device_battery_ina_address: builtins.int = ...,
|
||||
powermon_enables: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["adc_multiplier_override", b"adc_multiplier_override", "device_battery_ina_address", b"device_battery_ina_address", "is_power_saving", b"is_power_saving", "ls_secs", b"ls_secs", "min_wake_secs", b"min_wake_secs", "on_battery_shutdown_after_secs", b"on_battery_shutdown_after_secs", "sds_secs", b"sds_secs", "wait_bluetooth_secs", b"wait_bluetooth_secs"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["adc_multiplier_override", b"adc_multiplier_override", "device_battery_ina_address", b"device_battery_ina_address", "is_power_saving", b"is_power_saving", "ls_secs", b"ls_secs", "min_wake_secs", b"min_wake_secs", "on_battery_shutdown_after_secs", b"on_battery_shutdown_after_secs", "powermon_enables", b"powermon_enables", "sds_secs", b"sds_secs", "wait_bluetooth_secs", b"wait_bluetooth_secs"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class NetworkConfig(google.protobuf.message.Message):
|
||||
@@ -1369,6 +1378,7 @@ class Config(google.protobuf.message.Message):
|
||||
OVERRIDE_DUTY_CYCLE_FIELD_NUMBER: builtins.int
|
||||
SX126X_RX_BOOSTED_GAIN_FIELD_NUMBER: builtins.int
|
||||
OVERRIDE_FREQUENCY_FIELD_NUMBER: builtins.int
|
||||
PA_FAN_DISABLED_FIELD_NUMBER: builtins.int
|
||||
IGNORE_INCOMING_FIELD_NUMBER: builtins.int
|
||||
IGNORE_MQTT_FIELD_NUMBER: builtins.int
|
||||
use_preset: builtins.bool
|
||||
@@ -1456,6 +1466,10 @@ class Config(google.protobuf.message.Message):
|
||||
Please respect your local laws and regulations. If you are a HAM, make sure you
|
||||
enable HAM mode and turn off encryption.
|
||||
"""
|
||||
pa_fan_disabled: builtins.bool
|
||||
"""
|
||||
If true, disable the build-in PA FAN using pin define in RF95_FAN_EN.
|
||||
"""
|
||||
ignore_mqtt: builtins.bool
|
||||
"""
|
||||
If true, the device will not process any packets received via LoRa that passed via MQTT anywhere on the path towards it.
|
||||
@@ -1485,10 +1499,11 @@ class Config(google.protobuf.message.Message):
|
||||
override_duty_cycle: builtins.bool = ...,
|
||||
sx126x_rx_boosted_gain: builtins.bool = ...,
|
||||
override_frequency: builtins.float = ...,
|
||||
pa_fan_disabled: builtins.bool = ...,
|
||||
ignore_incoming: collections.abc.Iterable[builtins.int] | None = ...,
|
||||
ignore_mqtt: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["bandwidth", b"bandwidth", "channel_num", b"channel_num", "coding_rate", b"coding_rate", "frequency_offset", b"frequency_offset", "hop_limit", b"hop_limit", "ignore_incoming", b"ignore_incoming", "ignore_mqtt", b"ignore_mqtt", "modem_preset", b"modem_preset", "override_duty_cycle", b"override_duty_cycle", "override_frequency", b"override_frequency", "region", b"region", "spread_factor", b"spread_factor", "sx126x_rx_boosted_gain", b"sx126x_rx_boosted_gain", "tx_enabled", b"tx_enabled", "tx_power", b"tx_power", "use_preset", b"use_preset"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["bandwidth", b"bandwidth", "channel_num", b"channel_num", "coding_rate", b"coding_rate", "frequency_offset", b"frequency_offset", "hop_limit", b"hop_limit", "ignore_incoming", b"ignore_incoming", "ignore_mqtt", b"ignore_mqtt", "modem_preset", b"modem_preset", "override_duty_cycle", b"override_duty_cycle", "override_frequency", b"override_frequency", "pa_fan_disabled", b"pa_fan_disabled", "region", b"region", "spread_factor", b"spread_factor", "sx126x_rx_boosted_gain", b"sx126x_rx_boosted_gain", "tx_enabled", b"tx_enabled", "tx_power", b"tx_power", "use_preset", b"use_preset"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class BluetoothConfig(google.protobuf.message.Message):
|
||||
@@ -1530,6 +1545,7 @@ class Config(google.protobuf.message.Message):
|
||||
ENABLED_FIELD_NUMBER: builtins.int
|
||||
MODE_FIELD_NUMBER: builtins.int
|
||||
FIXED_PIN_FIELD_NUMBER: builtins.int
|
||||
DEVICE_LOGGING_ENABLED_FIELD_NUMBER: builtins.int
|
||||
enabled: builtins.bool
|
||||
"""
|
||||
Enable Bluetooth on the device
|
||||
@@ -1542,14 +1558,19 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
Specified PIN for PairingMode.FixedPin
|
||||
"""
|
||||
device_logging_enabled: builtins.bool
|
||||
"""
|
||||
Enables device (serial style logs) over Bluetooth
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
enabled: builtins.bool = ...,
|
||||
mode: global___Config.BluetoothConfig.PairingMode.ValueType = ...,
|
||||
fixed_pin: builtins.int = ...,
|
||||
device_logging_enabled: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["enabled", b"enabled", "fixed_pin", b"fixed_pin", "mode", b"mode"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["device_logging_enabled", b"device_logging_enabled", "enabled", b"enabled", "fixed_pin", b"fixed_pin", "mode", b"mode"]) -> None: ...
|
||||
|
||||
DEVICE_FIELD_NUMBER: builtins.int
|
||||
POSITION_FIELD_NUMBER: builtins.int
|
||||
|
||||
@@ -14,12 +14,11 @@ _sym_db = _symbol_database.Default()
|
||||
from meshtastic.protobuf import channel_pb2 as meshtastic_dot_protobuf_dot_channel__pb2
|
||||
from meshtastic.protobuf import localonly_pb2 as meshtastic_dot_protobuf_dot_localonly__pb2
|
||||
from meshtastic.protobuf import mesh_pb2 as meshtastic_dot_protobuf_dot_mesh__pb2
|
||||
from meshtastic.protobuf import module_config_pb2 as meshtastic_dot_protobuf_dot_module__config__pb2
|
||||
from meshtastic.protobuf import telemetry_pb2 as meshtastic_dot_protobuf_dot_telemetry__pb2
|
||||
import nanopb_pb2 as nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/deviceonly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a\'meshtastic/protobuf/module_config.proto\x1a#meshtastic/protobuf/telemetry.proto\x1a\x0cnanopb.proto\"\x99\x01\n\x0cPositionLite\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\x0c\n\x04time\x18\x04 \x01(\x07\x12@\n\x0flocation_source\x18\x05 \x01(\x0e\x32\'.meshtastic.protobuf.Position.LocSource\"\xa1\x02\n\x0cNodeInfoLite\x12\x0b\n\x03num\x18\x01 \x01(\r\x12\'\n\x04user\x18\x02 \x01(\x0b\x32\x19.meshtastic.protobuf.User\x12\x33\n\x08position\x18\x03 \x01(\x0b\x32!.meshtastic.protobuf.PositionLite\x12\x0b\n\x03snr\x18\x04 \x01(\x02\x12\x12\n\nlast_heard\x18\x05 \x01(\x07\x12:\n\x0e\x64\x65vice_metrics\x18\x06 \x01(\x0b\x32\".meshtastic.protobuf.DeviceMetrics\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\r\x12\x10\n\x08via_mqtt\x18\x08 \x01(\x08\x12\x11\n\thops_away\x18\t \x01(\r\x12\x13\n\x0bis_favorite\x18\n \x01(\x08\"\x82\x04\n\x0b\x44\x65viceState\x12\x30\n\x07my_node\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.MyNodeInfo\x12(\n\x05owner\x18\x03 \x01(\x0b\x32\x19.meshtastic.protobuf.User\x12\x36\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12\x38\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x13\n\x07no_save\x18\t \x01(\x08\x42\x02\x18\x01\x12\x15\n\rdid_gps_reset\x18\x0b \x01(\x08\x12\x34\n\x0brx_waypoint\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12M\n\x19node_remote_hardware_pins\x18\r \x03(\x0b\x32*.meshtastic.protobuf.NodeRemoteHardwarePin\x12\x63\n\x0cnode_db_lite\x18\x0e \x03(\x0b\x32!.meshtastic.protobuf.NodeInfoLiteB*\x92?\'\x92\x01$std::vector<meshtastic_NodeInfoLite>\"N\n\x0b\x43hannelFile\x12.\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x1c.meshtastic.protobuf.Channel\x12\x0f\n\x07version\x18\x02 \x01(\r\"\xb2\x02\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\x32\n\x08oem_font\x18\x04 \x01(\x0e\x32 .meshtastic.protobuf.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 .meshtastic.protobuf.LocalConfig\x12G\n\x17oem_local_module_config\x18\x08 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfig*>\n\x0bScreenFonts\x12\x0e\n\nFONT_SMALL\x10\x00\x12\x0f\n\x0b\x46ONT_MEDIUM\x10\x01\x12\x0e\n\nFONT_LARGE\x10\x02\x42m\n\x13\x63om.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x92?\x0b\xc2\x01\x08<vector>b\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/deviceonly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a#meshtastic/protobuf/telemetry.proto\x1a\x0cnanopb.proto\"\x99\x01\n\x0cPositionLite\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\x0c\n\x04time\x18\x04 \x01(\x07\x12@\n\x0flocation_source\x18\x05 \x01(\x0e\x32\'.meshtastic.protobuf.Position.LocSource\"\xa1\x02\n\x0cNodeInfoLite\x12\x0b\n\x03num\x18\x01 \x01(\r\x12\'\n\x04user\x18\x02 \x01(\x0b\x32\x19.meshtastic.protobuf.User\x12\x33\n\x08position\x18\x03 \x01(\x0b\x32!.meshtastic.protobuf.PositionLite\x12\x0b\n\x03snr\x18\x04 \x01(\x02\x12\x12\n\nlast_heard\x18\x05 \x01(\x07\x12:\n\x0e\x64\x65vice_metrics\x18\x06 \x01(\x0b\x32\".meshtastic.protobuf.DeviceMetrics\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\r\x12\x10\n\x08via_mqtt\x18\x08 \x01(\x08\x12\x11\n\thops_away\x18\t \x01(\r\x12\x13\n\x0bis_favorite\x18\n \x01(\x08\"\x82\x04\n\x0b\x44\x65viceState\x12\x30\n\x07my_node\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.MyNodeInfo\x12(\n\x05owner\x18\x03 \x01(\x0b\x32\x19.meshtastic.protobuf.User\x12\x36\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12\x38\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x13\n\x07no_save\x18\t \x01(\x08\x42\x02\x18\x01\x12\x15\n\rdid_gps_reset\x18\x0b \x01(\x08\x12\x34\n\x0brx_waypoint\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12M\n\x19node_remote_hardware_pins\x18\r \x03(\x0b\x32*.meshtastic.protobuf.NodeRemoteHardwarePin\x12\x63\n\x0cnode_db_lite\x18\x0e \x03(\x0b\x32!.meshtastic.protobuf.NodeInfoLiteB*\x92?\'\x92\x01$std::vector<meshtastic_NodeInfoLite>\"N\n\x0b\x43hannelFile\x12.\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x1c.meshtastic.protobuf.Channel\x12\x0f\n\x07version\x18\x02 \x01(\r\"\xb2\x02\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\x32\n\x08oem_font\x18\x04 \x01(\x0e\x32 .meshtastic.protobuf.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 .meshtastic.protobuf.LocalConfig\x12G\n\x17oem_local_module_config\x18\x08 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfig*>\n\x0bScreenFonts\x12\x0e\n\nFONT_SMALL\x10\x00\x12\x0f\n\x0b\x46ONT_MEDIUM\x10\x01\x12\x0e\n\nFONT_LARGE\x10\x02\x42m\n\x13\x63om.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x92?\x0b\xc2\x01\x08<vector>b\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
@@ -31,16 +30,16 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
_DEVICESTATE.fields_by_name['no_save']._serialized_options = b'\030\001'
|
||||
_DEVICESTATE.fields_by_name['node_db_lite']._options = None
|
||||
_DEVICESTATE.fields_by_name['node_db_lite']._serialized_options = b'\222?\'\222\001$std::vector<meshtastic_NodeInfoLite>'
|
||||
_globals['_SCREENFONTS']._serialized_start=1611
|
||||
_globals['_SCREENFONTS']._serialized_end=1673
|
||||
_globals['_POSITIONLITE']._serialized_start=258
|
||||
_globals['_POSITIONLITE']._serialized_end=411
|
||||
_globals['_NODEINFOLITE']._serialized_start=414
|
||||
_globals['_NODEINFOLITE']._serialized_end=703
|
||||
_globals['_DEVICESTATE']._serialized_start=706
|
||||
_globals['_DEVICESTATE']._serialized_end=1220
|
||||
_globals['_CHANNELFILE']._serialized_start=1222
|
||||
_globals['_CHANNELFILE']._serialized_end=1300
|
||||
_globals['_OEMSTORE']._serialized_start=1303
|
||||
_globals['_OEMSTORE']._serialized_end=1609
|
||||
_globals['_SCREENFONTS']._serialized_start=1570
|
||||
_globals['_SCREENFONTS']._serialized_end=1632
|
||||
_globals['_POSITIONLITE']._serialized_start=217
|
||||
_globals['_POSITIONLITE']._serialized_end=370
|
||||
_globals['_NODEINFOLITE']._serialized_start=373
|
||||
_globals['_NODEINFOLITE']._serialized_end=662
|
||||
_globals['_DEVICESTATE']._serialized_start=665
|
||||
_globals['_DEVICESTATE']._serialized_end=1179
|
||||
_globals['_CHANNELFILE']._serialized_start=1181
|
||||
_globals['_CHANNELFILE']._serialized_end=1259
|
||||
_globals['_OEMSTORE']._serialized_start=1262
|
||||
_globals['_OEMSTORE']._serialized_end=1568
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -125,6 +125,10 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
||||
"""
|
||||
RAK2560 Solar base station based on RAK4630
|
||||
"""
|
||||
HELTEC_HRU_3601: _HardwareModel.ValueType # 23
|
||||
"""
|
||||
Heltec HRU-3601: https://heltec.org/project/hru-3601/
|
||||
"""
|
||||
STATION_G1: _HardwareModel.ValueType # 25
|
||||
"""
|
||||
B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
|
||||
@@ -303,6 +307,31 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
||||
"""
|
||||
Heltec Capsule Sensor V3 with ESP32-S3 CPU, Portable LoRa device that can replace GNSS modules or sensors
|
||||
"""
|
||||
HELTEC_VISION_MASTER_T190: _HardwareModel.ValueType # 66
|
||||
"""
|
||||
Heltec Vision Master T190 with ESP32-S3 CPU, and a 1.90 inch TFT display
|
||||
"""
|
||||
HELTEC_VISION_MASTER_E213: _HardwareModel.ValueType # 67
|
||||
"""
|
||||
Heltec Vision Master E213 with ESP32-S3 CPU, and a 2.13 inch E-Ink display
|
||||
"""
|
||||
HELTEC_VISION_MASTER_E290: _HardwareModel.ValueType # 68
|
||||
"""
|
||||
Heltec Vision Master E290 with ESP32-S3 CPU, and a 2.9 inch E-Ink display
|
||||
"""
|
||||
HELTEC_MESH_NODE_T114: _HardwareModel.ValueType # 69
|
||||
"""
|
||||
Heltec Mesh Node T114 board with nRF52840 CPU, and a 1.14 inch TFT display, Ultimate low-power design,
|
||||
specifically adapted for the Meshtatic project
|
||||
"""
|
||||
SENSECAP_INDICATOR: _HardwareModel.ValueType # 70
|
||||
"""
|
||||
Sensecap Indicator from Seeed Studio. ESP32-S3 device with TFT and RP2040 coprocessor
|
||||
"""
|
||||
TRACKER_T1000_E: _HardwareModel.ValueType # 71
|
||||
"""
|
||||
Seeed studio T1000-E tracker card. NRF52840 w/ LR1110 radio, GPS, button, buzzer, and sensors.
|
||||
"""
|
||||
PRIVATE_HW: _HardwareModel.ValueType # 255
|
||||
"""
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -412,6 +441,10 @@ RAK2560: HardwareModel.ValueType # 22
|
||||
"""
|
||||
RAK2560 Solar base station based on RAK4630
|
||||
"""
|
||||
HELTEC_HRU_3601: HardwareModel.ValueType # 23
|
||||
"""
|
||||
Heltec HRU-3601: https://heltec.org/project/hru-3601/
|
||||
"""
|
||||
STATION_G1: HardwareModel.ValueType # 25
|
||||
"""
|
||||
B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
|
||||
@@ -590,6 +623,31 @@ HELTEC_CAPSULE_SENSOR_V3: HardwareModel.ValueType # 65
|
||||
"""
|
||||
Heltec Capsule Sensor V3 with ESP32-S3 CPU, Portable LoRa device that can replace GNSS modules or sensors
|
||||
"""
|
||||
HELTEC_VISION_MASTER_T190: HardwareModel.ValueType # 66
|
||||
"""
|
||||
Heltec Vision Master T190 with ESP32-S3 CPU, and a 1.90 inch TFT display
|
||||
"""
|
||||
HELTEC_VISION_MASTER_E213: HardwareModel.ValueType # 67
|
||||
"""
|
||||
Heltec Vision Master E213 with ESP32-S3 CPU, and a 2.13 inch E-Ink display
|
||||
"""
|
||||
HELTEC_VISION_MASTER_E290: HardwareModel.ValueType # 68
|
||||
"""
|
||||
Heltec Vision Master E290 with ESP32-S3 CPU, and a 2.9 inch E-Ink display
|
||||
"""
|
||||
HELTEC_MESH_NODE_T114: HardwareModel.ValueType # 69
|
||||
"""
|
||||
Heltec Mesh Node T114 board with nRF52840 CPU, and a 1.14 inch TFT display, Ultimate low-power design,
|
||||
specifically adapted for the Meshtatic project
|
||||
"""
|
||||
SENSECAP_INDICATOR: HardwareModel.ValueType # 70
|
||||
"""
|
||||
Sensecap Indicator from Seeed Studio. ESP32-S3 device with TFT and RP2040 coprocessor
|
||||
"""
|
||||
TRACKER_T1000_E: HardwareModel.ValueType # 71
|
||||
"""
|
||||
Seeed studio T1000-E tracker card. NRF52840 w/ LR1110 radio, GPS, button, buzzer, and sensors.
|
||||
"""
|
||||
PRIVATE_HW: HardwareModel.ValueType # 255
|
||||
"""
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -689,6 +747,17 @@ class _CriticalErrorCodeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapp
|
||||
A (likely software but possibly hardware) failure was detected while trying to send packets.
|
||||
If this occurs on your board, please post in the forum so that we can ask you to collect some information to allow fixing this bug
|
||||
"""
|
||||
FLASH_CORRUPTION_RECOVERABLE: _CriticalErrorCode.ValueType # 12
|
||||
"""
|
||||
Corruption was detected on the flash filesystem but we were able to repair things.
|
||||
If you see this failure in the field please post in the forum because we are interested in seeing if this is occurring in the field.
|
||||
"""
|
||||
FLASH_CORRUPTION_UNRECOVERABLE: _CriticalErrorCode.ValueType # 13
|
||||
"""
|
||||
Corruption was detected on the flash filesystem but we were unable to repair things.
|
||||
NOTE: Your node will probably need to be reconfigured the next time it reboots (it will lose the region code etc...)
|
||||
If you see this failure in the field please post in the forum because we are interested in seeing if this is occurring in the field.
|
||||
"""
|
||||
|
||||
class CriticalErrorCode(_CriticalErrorCode, metaclass=_CriticalErrorCodeEnumTypeWrapper):
|
||||
"""
|
||||
@@ -747,6 +816,17 @@ RADIO_SPI_BUG: CriticalErrorCode.ValueType # 11
|
||||
A (likely software but possibly hardware) failure was detected while trying to send packets.
|
||||
If this occurs on your board, please post in the forum so that we can ask you to collect some information to allow fixing this bug
|
||||
"""
|
||||
FLASH_CORRUPTION_RECOVERABLE: CriticalErrorCode.ValueType # 12
|
||||
"""
|
||||
Corruption was detected on the flash filesystem but we were able to repair things.
|
||||
If you see this failure in the field please post in the forum because we are interested in seeing if this is occurring in the field.
|
||||
"""
|
||||
FLASH_CORRUPTION_UNRECOVERABLE: CriticalErrorCode.ValueType # 13
|
||||
"""
|
||||
Corruption was detected on the flash filesystem but we were unable to repair things.
|
||||
NOTE: Your node will probably need to be reconfigured the next time it reboots (it will lose the region code etc...)
|
||||
If you see this failure in the field please post in the forum because we are interested in seeing if this is occurring in the field.
|
||||
"""
|
||||
global___CriticalErrorCode = CriticalErrorCode
|
||||
|
||||
@typing.final
|
||||
@@ -2050,6 +2130,7 @@ class FromRadio(google.protobuf.message.Message):
|
||||
XMODEMPACKET_FIELD_NUMBER: builtins.int
|
||||
METADATA_FIELD_NUMBER: builtins.int
|
||||
MQTTCLIENTPROXYMESSAGE_FIELD_NUMBER: builtins.int
|
||||
FILEINFO_FIELD_NUMBER: builtins.int
|
||||
id: builtins.int
|
||||
"""
|
||||
The packet id, used to allow the phone to request missing read packets from the FIFO,
|
||||
@@ -2137,6 +2218,12 @@ class FromRadio(google.protobuf.message.Message):
|
||||
MQTT Client Proxy Message (device sending to client / phone for publishing to MQTT)
|
||||
"""
|
||||
|
||||
@property
|
||||
def fileInfo(self) -> global___FileInfo:
|
||||
"""
|
||||
File system manifest messages
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -2154,13 +2241,42 @@ class FromRadio(google.protobuf.message.Message):
|
||||
xmodemPacket: meshtastic.protobuf.xmodem_pb2.XModem | None = ...,
|
||||
metadata: global___DeviceMetadata | None = ...,
|
||||
mqttClientProxyMessage: global___MqttClientProxyMessage | None = ...,
|
||||
fileInfo: global___FileInfo | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["channel", b"channel", "config", b"config", "config_complete_id", b"config_complete_id", "log_record", b"log_record", "metadata", b"metadata", "moduleConfig", b"moduleConfig", "mqttClientProxyMessage", b"mqttClientProxyMessage", "my_info", b"my_info", "node_info", b"node_info", "packet", b"packet", "payload_variant", b"payload_variant", "queueStatus", b"queueStatus", "rebooted", b"rebooted", "xmodemPacket", b"xmodemPacket"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["channel", b"channel", "config", b"config", "config_complete_id", b"config_complete_id", "id", b"id", "log_record", b"log_record", "metadata", b"metadata", "moduleConfig", b"moduleConfig", "mqttClientProxyMessage", b"mqttClientProxyMessage", "my_info", b"my_info", "node_info", b"node_info", "packet", b"packet", "payload_variant", b"payload_variant", "queueStatus", b"queueStatus", "rebooted", b"rebooted", "xmodemPacket", b"xmodemPacket"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["packet", "my_info", "node_info", "config", "log_record", "config_complete_id", "rebooted", "moduleConfig", "channel", "queueStatus", "xmodemPacket", "metadata", "mqttClientProxyMessage"] | None: ...
|
||||
def HasField(self, field_name: typing.Literal["channel", b"channel", "config", b"config", "config_complete_id", b"config_complete_id", "fileInfo", b"fileInfo", "log_record", b"log_record", "metadata", b"metadata", "moduleConfig", b"moduleConfig", "mqttClientProxyMessage", b"mqttClientProxyMessage", "my_info", b"my_info", "node_info", b"node_info", "packet", b"packet", "payload_variant", b"payload_variant", "queueStatus", b"queueStatus", "rebooted", b"rebooted", "xmodemPacket", b"xmodemPacket"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["channel", b"channel", "config", b"config", "config_complete_id", b"config_complete_id", "fileInfo", b"fileInfo", "id", b"id", "log_record", b"log_record", "metadata", b"metadata", "moduleConfig", b"moduleConfig", "mqttClientProxyMessage", b"mqttClientProxyMessage", "my_info", b"my_info", "node_info", b"node_info", "packet", b"packet", "payload_variant", b"payload_variant", "queueStatus", b"queueStatus", "rebooted", b"rebooted", "xmodemPacket", b"xmodemPacket"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["packet", "my_info", "node_info", "config", "log_record", "config_complete_id", "rebooted", "moduleConfig", "channel", "queueStatus", "xmodemPacket", "metadata", "mqttClientProxyMessage", "fileInfo"] | None: ...
|
||||
|
||||
global___FromRadio = FromRadio
|
||||
|
||||
@typing.final
|
||||
class FileInfo(google.protobuf.message.Message):
|
||||
"""
|
||||
Individual File info for the device
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
FILE_NAME_FIELD_NUMBER: builtins.int
|
||||
SIZE_BYTES_FIELD_NUMBER: builtins.int
|
||||
file_name: builtins.str
|
||||
"""
|
||||
The fully qualified path of the file
|
||||
"""
|
||||
size_bytes: builtins.int
|
||||
"""
|
||||
The size of the file in bytes
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
file_name: builtins.str = ...,
|
||||
size_bytes: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["file_name", b"file_name", "size_bytes", b"size_bytes"]) -> None: ...
|
||||
|
||||
global___FileInfo = FileInfo
|
||||
|
||||
@typing.final
|
||||
class ToRadio(google.protobuf.message.Message):
|
||||
"""
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -503,6 +503,8 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
NMEA: ModuleConfig.SerialConfig._Serial_Mode.ValueType # 4
|
||||
CALTOPO: ModuleConfig.SerialConfig._Serial_Mode.ValueType # 5
|
||||
"""NMEA messages specifically tailored for CalTopo"""
|
||||
WS85: ModuleConfig.SerialConfig._Serial_Mode.ValueType # 6
|
||||
"""Ecowitt WS85 weather station"""
|
||||
|
||||
class Serial_Mode(_Serial_Mode, metaclass=_Serial_ModeEnumTypeWrapper):
|
||||
"""
|
||||
@@ -516,6 +518,8 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
NMEA: ModuleConfig.SerialConfig.Serial_Mode.ValueType # 4
|
||||
CALTOPO: ModuleConfig.SerialConfig.Serial_Mode.ValueType # 5
|
||||
"""NMEA messages specifically tailored for CalTopo"""
|
||||
WS85: ModuleConfig.SerialConfig.Serial_Mode.ValueType # 6
|
||||
"""Ecowitt WS85 weather station"""
|
||||
|
||||
ENABLED_FIELD_NUMBER: builtins.int
|
||||
ECHO_FIELD_NUMBER: builtins.int
|
||||
@@ -701,6 +705,7 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
RECORDS_FIELD_NUMBER: builtins.int
|
||||
HISTORY_RETURN_MAX_FIELD_NUMBER: builtins.int
|
||||
HISTORY_RETURN_WINDOW_FIELD_NUMBER: builtins.int
|
||||
IS_SERVER_FIELD_NUMBER: builtins.int
|
||||
enabled: builtins.bool
|
||||
"""
|
||||
Enable the Store and Forward Module
|
||||
@@ -721,6 +726,10 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
TODO: REPLACE
|
||||
"""
|
||||
is_server: builtins.bool
|
||||
"""
|
||||
Set to true to let this node act as a server that stores received messages and resends them upon request.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -729,8 +738,9 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
records: builtins.int = ...,
|
||||
history_return_max: builtins.int = ...,
|
||||
history_return_window: builtins.int = ...,
|
||||
is_server: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["enabled", b"enabled", "heartbeat", b"heartbeat", "history_return_max", b"history_return_max", "history_return_window", b"history_return_window", "records", b"records"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["enabled", b"enabled", "heartbeat", b"heartbeat", "history_return_max", b"history_return_max", "history_return_window", b"history_return_window", "is_server", b"is_server", "records", b"records"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class RangeTestConfig(google.protobuf.message.Message):
|
||||
|
||||
@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/portnums.proto\x12\x13meshtastic.protobuf*\x8d\x04\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\x18\n\x14\x44\x45TECTION_SENSOR_APP\x10\n\x12\r\n\tREPLY_APP\x10 \x12\x11\n\rIP_TUNNEL_APP\x10!\x12\x12\n\x0ePAXCOUNTER_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\x0f\n\x0b\x41TAK_PLUGIN\x10H\x12\x12\n\x0eMAP_REPORT_APP\x10I\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_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/portnums.proto\x12\x13meshtastic.protobuf*\xa2\x04\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\x18\n\x14\x44\x45TECTION_SENSOR_APP\x10\n\x12\r\n\tREPLY_APP\x10 \x12\x11\n\rIP_TUNNEL_APP\x10!\x12\x12\n\x0ePAXCOUNTER_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\x0f\n\x0b\x41TAK_PLUGIN\x10H\x12\x12\n\x0eMAP_REPORT_APP\x10I\x12\x13\n\x0fPOWERSTRESS_APP\x10J\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')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
@@ -22,5 +22,5 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\010PortnumsZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_globals['_PORTNUM']._serialized_start=60
|
||||
_globals['_PORTNUM']._serialized_end=585
|
||||
_globals['_PORTNUM']._serialized_end=606
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
@@ -171,6 +171,10 @@ class _PortNumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTy
|
||||
"""
|
||||
Provides unencrypted information about a node for consumption by a map via MQTT
|
||||
"""
|
||||
POWERSTRESS_APP: _PortNum.ValueType # 74
|
||||
"""
|
||||
PowerStress based monitoring support (for automated power consumption testing)
|
||||
"""
|
||||
PRIVATE_APP: _PortNum.ValueType # 256
|
||||
"""
|
||||
Private applications should use portnums >= 256.
|
||||
@@ -352,6 +356,10 @@ MAP_REPORT_APP: PortNum.ValueType # 73
|
||||
"""
|
||||
Provides unencrypted information about a node for consumption by a map via MQTT
|
||||
"""
|
||||
POWERSTRESS_APP: PortNum.ValueType # 74
|
||||
"""
|
||||
PowerStress based monitoring support (for automated power consumption testing)
|
||||
"""
|
||||
PRIVATE_APP: PortNum.ValueType # 256
|
||||
"""
|
||||
Private applications should use portnums >= 256.
|
||||
|
||||
32
meshtastic/protobuf/powermon_pb2.py
Normal file
32
meshtastic/protobuf/powermon_pb2.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: meshtastic/protobuf/powermon.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 symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/powermon.proto\x12\x13meshtastic.protobuf\"\xe0\x01\n\x08PowerMon\"\xd3\x01\n\x05State\x12\x08\n\x04None\x10\x00\x12\x11\n\rCPU_DeepSleep\x10\x01\x12\x12\n\x0e\x43PU_LightSleep\x10\x02\x12\x0c\n\x08Vext1_On\x10\x04\x12\r\n\tLora_RXOn\x10\x08\x12\r\n\tLora_TXOn\x10\x10\x12\x11\n\rLora_RXActive\x10 \x12\t\n\x05\x42T_On\x10@\x12\x0b\n\x06LED_On\x10\x80\x01\x12\x0e\n\tScreen_On\x10\x80\x02\x12\x13\n\x0eScreen_Drawing\x10\x80\x04\x12\x0c\n\x07Wifi_On\x10\x80\x08\x12\x0f\n\nGPS_Active\x10\x80\x10\"\x88\x03\n\x12PowerStressMessage\x12;\n\x03\x63md\x18\x01 \x01(\x0e\x32..meshtastic.protobuf.PowerStressMessage.Opcode\x12\x13\n\x0bnum_seconds\x18\x02 \x01(\x02\"\x9f\x02\n\x06Opcode\x12\t\n\x05UNSET\x10\x00\x12\x0e\n\nPRINT_INFO\x10\x01\x12\x0f\n\x0b\x46ORCE_QUIET\x10\x02\x12\r\n\tEND_QUIET\x10\x03\x12\r\n\tSCREEN_ON\x10\x10\x12\x0e\n\nSCREEN_OFF\x10\x11\x12\x0c\n\x08\x43PU_IDLE\x10 \x12\x11\n\rCPU_DEEPSLEEP\x10!\x12\x0e\n\nCPU_FULLON\x10\"\x12\n\n\x06LED_ON\x10\x30\x12\x0b\n\x07LED_OFF\x10\x31\x12\x0c\n\x08LORA_OFF\x10@\x12\x0b\n\x07LORA_TX\x10\x41\x12\x0b\n\x07LORA_RX\x10\x42\x12\n\n\x06\x42T_OFF\x10P\x12\t\n\x05\x42T_ON\x10Q\x12\x0c\n\x08WIFI_OFF\x10`\x12\x0b\n\x07WIFI_ON\x10\x61\x12\x0b\n\x07GPS_OFF\x10p\x12\n\n\x06GPS_ON\x10qBc\n\x13\x63om.geeksville.meshB\x0ePowerMonProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.powermon_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\016PowerMonProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_globals['_POWERMON']._serialized_start=60
|
||||
_globals['_POWERMON']._serialized_end=284
|
||||
_globals['_POWERMON_STATE']._serialized_start=73
|
||||
_globals['_POWERMON_STATE']._serialized_end=284
|
||||
_globals['_POWERSTRESSMESSAGE']._serialized_start=287
|
||||
_globals['_POWERSTRESSMESSAGE']._serialized_end=679
|
||||
_globals['_POWERSTRESSMESSAGE_OPCODE']._serialized_start=392
|
||||
_globals['_POWERSTRESSMESSAGE_OPCODE']._serialized_end=679
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
221
meshtastic/protobuf/powermon_pb2.pyi
Normal file
221
meshtastic/protobuf/powermon_pb2.pyi
Normal file
@@ -0,0 +1,221 @@
|
||||
"""
|
||||
@generated by mypy-protobuf. Do not edit manually!
|
||||
isort:skip_file
|
||||
"""
|
||||
|
||||
import builtins
|
||||
import google.protobuf.descriptor
|
||||
import google.protobuf.internal.enum_type_wrapper
|
||||
import google.protobuf.message
|
||||
import sys
|
||||
import typing
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
import typing as typing_extensions
|
||||
else:
|
||||
import typing_extensions
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
|
||||
|
||||
@typing.final
|
||||
class PowerMon(google.protobuf.message.Message):
|
||||
"""Note: There are no 'PowerMon' messages normally in use (PowerMons are sent only as structured logs - slogs).
|
||||
But we wrap our State enum in this message to effectively nest a namespace (without our linter yelling at us)
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
class _State:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _StateEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[PowerMon._State.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
CPU_DeepSleep: PowerMon._State.ValueType # 1
|
||||
CPU_LightSleep: PowerMon._State.ValueType # 2
|
||||
Vext1_On: PowerMon._State.ValueType # 4
|
||||
"""
|
||||
The external Vext1 power is on. Many boards have auxillary power rails that the CPU turns on only
|
||||
occasionally. In cases where that rail has multiple devices on it we usually want to have logging on
|
||||
the state of that rail as an independent record.
|
||||
For instance on the Heltec Tracker 1.1 board, this rail is the power source for the GPS and screen.
|
||||
|
||||
The log messages will be short and complete (see PowerMon.Event in the protobufs for details).
|
||||
something like "S:PM:C,0x00001234,REASON" where the hex number is the bitmask of all current states.
|
||||
(We use a bitmask for states so that if a log message gets lost it won't be fatal)
|
||||
"""
|
||||
Lora_RXOn: PowerMon._State.ValueType # 8
|
||||
Lora_TXOn: PowerMon._State.ValueType # 16
|
||||
Lora_RXActive: PowerMon._State.ValueType # 32
|
||||
BT_On: PowerMon._State.ValueType # 64
|
||||
LED_On: PowerMon._State.ValueType # 128
|
||||
Screen_On: PowerMon._State.ValueType # 256
|
||||
Screen_Drawing: PowerMon._State.ValueType # 512
|
||||
Wifi_On: PowerMon._State.ValueType # 1024
|
||||
GPS_Active: PowerMon._State.ValueType # 2048
|
||||
"""
|
||||
GPS is actively trying to find our location
|
||||
See GPSPowerState for more details
|
||||
"""
|
||||
|
||||
class State(_State, metaclass=_StateEnumTypeWrapper):
|
||||
"""Any significant power changing event in meshtastic should be tagged with a powermon state transition.
|
||||
If you are making new meshtastic features feel free to add new entries at the end of this definition.
|
||||
"""
|
||||
|
||||
CPU_DeepSleep: PowerMon.State.ValueType # 1
|
||||
CPU_LightSleep: PowerMon.State.ValueType # 2
|
||||
Vext1_On: PowerMon.State.ValueType # 4
|
||||
"""
|
||||
The external Vext1 power is on. Many boards have auxillary power rails that the CPU turns on only
|
||||
occasionally. In cases where that rail has multiple devices on it we usually want to have logging on
|
||||
the state of that rail as an independent record.
|
||||
For instance on the Heltec Tracker 1.1 board, this rail is the power source for the GPS and screen.
|
||||
|
||||
The log messages will be short and complete (see PowerMon.Event in the protobufs for details).
|
||||
something like "S:PM:C,0x00001234,REASON" where the hex number is the bitmask of all current states.
|
||||
(We use a bitmask for states so that if a log message gets lost it won't be fatal)
|
||||
"""
|
||||
Lora_RXOn: PowerMon.State.ValueType # 8
|
||||
Lora_TXOn: PowerMon.State.ValueType # 16
|
||||
Lora_RXActive: PowerMon.State.ValueType # 32
|
||||
BT_On: PowerMon.State.ValueType # 64
|
||||
LED_On: PowerMon.State.ValueType # 128
|
||||
Screen_On: PowerMon.State.ValueType # 256
|
||||
Screen_Drawing: PowerMon.State.ValueType # 512
|
||||
Wifi_On: PowerMon.State.ValueType # 1024
|
||||
GPS_Active: PowerMon.State.ValueType # 2048
|
||||
"""
|
||||
GPS is actively trying to find our location
|
||||
See GPSPowerState for more details
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
) -> None: ...
|
||||
|
||||
global___PowerMon = PowerMon
|
||||
|
||||
@typing.final
|
||||
class PowerStressMessage(google.protobuf.message.Message):
|
||||
"""
|
||||
PowerStress testing support via the C++ PowerStress module
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
class _Opcode:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _OpcodeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[PowerStressMessage._Opcode.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
UNSET: PowerStressMessage._Opcode.ValueType # 0
|
||||
"""
|
||||
Unset/unused
|
||||
"""
|
||||
PRINT_INFO: PowerStressMessage._Opcode.ValueType # 1
|
||||
"""Print board version slog and send an ack that we are alive and ready to process commands"""
|
||||
FORCE_QUIET: PowerStressMessage._Opcode.ValueType # 2
|
||||
"""Try to turn off all automatic processing of packets, screen, sleeping, etc (to make it easier to measure in isolation)"""
|
||||
END_QUIET: PowerStressMessage._Opcode.ValueType # 3
|
||||
"""Stop powerstress processing - probably by just rebooting the board"""
|
||||
SCREEN_ON: PowerStressMessage._Opcode.ValueType # 16
|
||||
"""Turn the screen on"""
|
||||
SCREEN_OFF: PowerStressMessage._Opcode.ValueType # 17
|
||||
"""Turn the screen off"""
|
||||
CPU_IDLE: PowerStressMessage._Opcode.ValueType # 32
|
||||
"""Let the CPU run but we assume mostly idling for num_seconds"""
|
||||
CPU_DEEPSLEEP: PowerStressMessage._Opcode.ValueType # 33
|
||||
"""Force deep sleep for FIXME seconds"""
|
||||
CPU_FULLON: PowerStressMessage._Opcode.ValueType # 34
|
||||
"""Spin the CPU as fast as possible for num_seconds"""
|
||||
LED_ON: PowerStressMessage._Opcode.ValueType # 48
|
||||
"""Turn the LED on for num_seconds (and leave it on - for baseline power measurement purposes)"""
|
||||
LED_OFF: PowerStressMessage._Opcode.ValueType # 49
|
||||
"""Force the LED off for num_seconds"""
|
||||
LORA_OFF: PowerStressMessage._Opcode.ValueType # 64
|
||||
"""Completely turn off the LORA radio for num_seconds"""
|
||||
LORA_TX: PowerStressMessage._Opcode.ValueType # 65
|
||||
"""Send Lora packets for num_seconds"""
|
||||
LORA_RX: PowerStressMessage._Opcode.ValueType # 66
|
||||
"""Receive Lora packets for num_seconds (node will be mostly just listening, unless an external agent is helping stress this by sending packets on the current channel)"""
|
||||
BT_OFF: PowerStressMessage._Opcode.ValueType # 80
|
||||
"""Turn off the BT radio for num_seconds"""
|
||||
BT_ON: PowerStressMessage._Opcode.ValueType # 81
|
||||
"""Turn on the BT radio for num_seconds"""
|
||||
WIFI_OFF: PowerStressMessage._Opcode.ValueType # 96
|
||||
"""Turn off the WIFI radio for num_seconds"""
|
||||
WIFI_ON: PowerStressMessage._Opcode.ValueType # 97
|
||||
"""Turn on the WIFI radio for num_seconds"""
|
||||
GPS_OFF: PowerStressMessage._Opcode.ValueType # 112
|
||||
"""Turn off the GPS radio for num_seconds"""
|
||||
GPS_ON: PowerStressMessage._Opcode.ValueType # 113
|
||||
"""Turn on the GPS radio for num_seconds"""
|
||||
|
||||
class Opcode(_Opcode, metaclass=_OpcodeEnumTypeWrapper):
|
||||
"""
|
||||
What operation would we like the UUT to perform.
|
||||
note: senders should probably set want_response in their request packets, so that they can know when the state
|
||||
machine has started processing their request
|
||||
"""
|
||||
|
||||
UNSET: PowerStressMessage.Opcode.ValueType # 0
|
||||
"""
|
||||
Unset/unused
|
||||
"""
|
||||
PRINT_INFO: PowerStressMessage.Opcode.ValueType # 1
|
||||
"""Print board version slog and send an ack that we are alive and ready to process commands"""
|
||||
FORCE_QUIET: PowerStressMessage.Opcode.ValueType # 2
|
||||
"""Try to turn off all automatic processing of packets, screen, sleeping, etc (to make it easier to measure in isolation)"""
|
||||
END_QUIET: PowerStressMessage.Opcode.ValueType # 3
|
||||
"""Stop powerstress processing - probably by just rebooting the board"""
|
||||
SCREEN_ON: PowerStressMessage.Opcode.ValueType # 16
|
||||
"""Turn the screen on"""
|
||||
SCREEN_OFF: PowerStressMessage.Opcode.ValueType # 17
|
||||
"""Turn the screen off"""
|
||||
CPU_IDLE: PowerStressMessage.Opcode.ValueType # 32
|
||||
"""Let the CPU run but we assume mostly idling for num_seconds"""
|
||||
CPU_DEEPSLEEP: PowerStressMessage.Opcode.ValueType # 33
|
||||
"""Force deep sleep for FIXME seconds"""
|
||||
CPU_FULLON: PowerStressMessage.Opcode.ValueType # 34
|
||||
"""Spin the CPU as fast as possible for num_seconds"""
|
||||
LED_ON: PowerStressMessage.Opcode.ValueType # 48
|
||||
"""Turn the LED on for num_seconds (and leave it on - for baseline power measurement purposes)"""
|
||||
LED_OFF: PowerStressMessage.Opcode.ValueType # 49
|
||||
"""Force the LED off for num_seconds"""
|
||||
LORA_OFF: PowerStressMessage.Opcode.ValueType # 64
|
||||
"""Completely turn off the LORA radio for num_seconds"""
|
||||
LORA_TX: PowerStressMessage.Opcode.ValueType # 65
|
||||
"""Send Lora packets for num_seconds"""
|
||||
LORA_RX: PowerStressMessage.Opcode.ValueType # 66
|
||||
"""Receive Lora packets for num_seconds (node will be mostly just listening, unless an external agent is helping stress this by sending packets on the current channel)"""
|
||||
BT_OFF: PowerStressMessage.Opcode.ValueType # 80
|
||||
"""Turn off the BT radio for num_seconds"""
|
||||
BT_ON: PowerStressMessage.Opcode.ValueType # 81
|
||||
"""Turn on the BT radio for num_seconds"""
|
||||
WIFI_OFF: PowerStressMessage.Opcode.ValueType # 96
|
||||
"""Turn off the WIFI radio for num_seconds"""
|
||||
WIFI_ON: PowerStressMessage.Opcode.ValueType # 97
|
||||
"""Turn on the WIFI radio for num_seconds"""
|
||||
GPS_OFF: PowerStressMessage.Opcode.ValueType # 112
|
||||
"""Turn off the GPS radio for num_seconds"""
|
||||
GPS_ON: PowerStressMessage.Opcode.ValueType # 113
|
||||
"""Turn on the GPS radio for num_seconds"""
|
||||
|
||||
CMD_FIELD_NUMBER: builtins.int
|
||||
NUM_SECONDS_FIELD_NUMBER: builtins.int
|
||||
cmd: global___PowerStressMessage.Opcode.ValueType
|
||||
"""
|
||||
What type of HardwareMessage is this?
|
||||
"""
|
||||
num_seconds: builtins.float
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
cmd: global___PowerStressMessage.Opcode.ValueType = ...,
|
||||
num_seconds: builtins.float = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["cmd", b"cmd", "num_seconds", b"num_seconds"]) -> None: ...
|
||||
|
||||
global___PowerStressMessage = PowerStressMessage
|
||||
@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/telemetry.proto\x12\x13meshtastic.protobuf\"\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\"\xb6\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\x12\x0e\n\x06weight\x18\x0f \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\"\xad\x02\n\tTelemetry\x12\x0c\n\x04time\x18\x01 \x01(\x07\x12<\n\x0e\x64\x65vice_metrics\x18\x02 \x01(\x0b\x32\".meshtastic.protobuf.DeviceMetricsH\x00\x12\x46\n\x13\x65nvironment_metrics\x18\x03 \x01(\x0b\x32\'.meshtastic.protobuf.EnvironmentMetricsH\x00\x12\x45\n\x13\x61ir_quality_metrics\x18\x04 \x01(\x0b\x32&.meshtastic.protobuf.AirQualityMetricsH\x00\x12:\n\rpower_metrics\x18\x05 \x01(\x0b\x32!.meshtastic.protobuf.PowerMetricsH\x00\x42\t\n\x07variant\">\n\rNau7802Config\x12\x12\n\nzeroOffset\x18\x01 \x01(\x05\x12\x19\n\x11\x63\x61librationFactor\x18\x02 \x01(\x02*\xea\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\x12\x0b\n\x07NAU7802\x10\x19\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#meshtastic/protobuf/telemetry.proto\x12\x13meshtastic.protobuf\"\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\"\xdc\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\x12\x0e\n\x06weight\x18\x0f \x01(\x02\x12\x11\n\twind_gust\x18\x10 \x01(\x02\x12\x11\n\twind_lull\x18\x11 \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\"\xad\x02\n\tTelemetry\x12\x0c\n\x04time\x18\x01 \x01(\x07\x12<\n\x0e\x64\x65vice_metrics\x18\x02 \x01(\x0b\x32\".meshtastic.protobuf.DeviceMetricsH\x00\x12\x46\n\x13\x65nvironment_metrics\x18\x03 \x01(\x0b\x32\'.meshtastic.protobuf.EnvironmentMetricsH\x00\x12\x45\n\x13\x61ir_quality_metrics\x18\x04 \x01(\x0b\x32&.meshtastic.protobuf.AirQualityMetricsH\x00\x12:\n\rpower_metrics\x18\x05 \x01(\x0b\x32!.meshtastic.protobuf.PowerMetricsH\x00\x42\t\n\x07variant\">\n\rNau7802Config\x12\x12\n\nzeroOffset\x18\x01 \x01(\x05\x12\x19\n\x11\x63\x61librationFactor\x18\x02 \x01(\x02*\xea\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\x12\x0b\n\x07NAU7802\x10\x19\x42\x64\n\x13\x63om.geeksville.meshB\x0fTelemetryProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
@@ -21,18 +21,18 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.telemet
|
||||
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'
|
||||
_globals['_TELEMETRYSENSORTYPE']._serialized_start=1339
|
||||
_globals['_TELEMETRYSENSORTYPE']._serialized_end=1701
|
||||
_globals['_TELEMETRYSENSORTYPE']._serialized_start=1377
|
||||
_globals['_TELEMETRYSENSORTYPE']._serialized_end=1739
|
||||
_globals['_DEVICEMETRICS']._serialized_start=61
|
||||
_globals['_DEVICEMETRICS']._serialized_end=190
|
||||
_globals['_ENVIRONMENTMETRICS']._serialized_start=193
|
||||
_globals['_ENVIRONMENTMETRICS']._serialized_end=503
|
||||
_globals['_POWERMETRICS']._serialized_start=506
|
||||
_globals['_POWERMETRICS']._serialized_end=646
|
||||
_globals['_AIRQUALITYMETRICS']._serialized_start=649
|
||||
_globals['_AIRQUALITYMETRICS']._serialized_end=968
|
||||
_globals['_TELEMETRY']._serialized_start=971
|
||||
_globals['_TELEMETRY']._serialized_end=1272
|
||||
_globals['_NAU7802CONFIG']._serialized_start=1274
|
||||
_globals['_NAU7802CONFIG']._serialized_end=1336
|
||||
_globals['_ENVIRONMENTMETRICS']._serialized_end=541
|
||||
_globals['_POWERMETRICS']._serialized_start=544
|
||||
_globals['_POWERMETRICS']._serialized_end=684
|
||||
_globals['_AIRQUALITYMETRICS']._serialized_start=687
|
||||
_globals['_AIRQUALITYMETRICS']._serialized_end=1006
|
||||
_globals['_TELEMETRY']._serialized_start=1009
|
||||
_globals['_TELEMETRY']._serialized_end=1310
|
||||
_globals['_NAU7802CONFIG']._serialized_start=1312
|
||||
_globals['_NAU7802CONFIG']._serialized_end=1374
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
@@ -308,6 +308,8 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
||||
WIND_DIRECTION_FIELD_NUMBER: builtins.int
|
||||
WIND_SPEED_FIELD_NUMBER: builtins.int
|
||||
WEIGHT_FIELD_NUMBER: builtins.int
|
||||
WIND_GUST_FIELD_NUMBER: builtins.int
|
||||
WIND_LULL_FIELD_NUMBER: builtins.int
|
||||
temperature: builtins.float
|
||||
"""
|
||||
Temperature measured
|
||||
@@ -370,6 +372,14 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
||||
"""
|
||||
Weight in KG
|
||||
"""
|
||||
wind_gust: builtins.float
|
||||
"""
|
||||
Wind gust in m/s
|
||||
"""
|
||||
wind_lull: builtins.float
|
||||
"""
|
||||
Wind lull in m/s
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -388,8 +398,10 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
||||
wind_direction: builtins.int = ...,
|
||||
wind_speed: builtins.float = ...,
|
||||
weight: builtins.float = ...,
|
||||
wind_gust: builtins.float = ...,
|
||||
wind_lull: builtins.float = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.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", "weight", b"weight", "white_lux", b"white_lux", "wind_direction", b"wind_direction", "wind_speed", b"wind_speed"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.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", "weight", b"weight", "white_lux", b"white_lux", "wind_direction", b"wind_direction", "wind_gust", b"wind_gust", "wind_lull", b"wind_lull", "wind_speed", b"wind_speed"]) -> None: ...
|
||||
|
||||
global___EnvironmentMetrics = EnvironmentMetrics
|
||||
|
||||
|
||||
@@ -67,9 +67,10 @@ class SerialInterface(StreamInterface):
|
||||
|
||||
def close(self):
|
||||
"""Close a connection to the device"""
|
||||
self.stream.flush()
|
||||
time.sleep(0.1)
|
||||
self.stream.flush()
|
||||
time.sleep(0.1)
|
||||
if self.stream: # Stream can be null if we were already closed
|
||||
self.stream.flush() # FIXME: why are there these two flushes with 100ms sleeps? This shouldn't be necessary
|
||||
time.sleep(0.1)
|
||||
self.stream.flush()
|
||||
time.sleep(0.1)
|
||||
logging.debug("Closing Serial stream")
|
||||
StreamInterface.close(self)
|
||||
|
||||
3
meshtastic/slog/__init__.py
Normal file
3
meshtastic/slog/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""Structured logging framework (see dev docs for more info)."""
|
||||
|
||||
from .slog import LogSet, root_dir
|
||||
96
meshtastic/slog/arrow.py
Normal file
96
meshtastic/slog/arrow.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""Utilities for Apache Arrow serialization."""
|
||||
|
||||
import logging
|
||||
import threading
|
||||
import os
|
||||
from typing import Optional, List
|
||||
|
||||
import pyarrow as pa
|
||||
from pyarrow import feather
|
||||
|
||||
chunk_size = 1000 # disk writes are batched based on this number of rows
|
||||
|
||||
|
||||
class ArrowWriter:
|
||||
"""Writes an arrow file in a streaming fashion"""
|
||||
|
||||
def __init__(self, file_name: str):
|
||||
"""Create a new ArrowWriter object.
|
||||
|
||||
file_name (str): The name of the file to write to.
|
||||
"""
|
||||
self.sink = pa.OSFile(file_name, "wb") # type: ignore
|
||||
self.new_rows: List[dict] = []
|
||||
self.schema: Optional[pa.Schema] = None # haven't yet learned the schema
|
||||
self.writer: Optional[pa.RecordBatchStreamWriter] = None
|
||||
self._lock = threading.Condition() # Ensure only one thread writes at a time
|
||||
|
||||
def close(self):
|
||||
"""Close the stream and writes the file as needed."""
|
||||
with self._lock:
|
||||
self._write()
|
||||
if self.writer:
|
||||
self.writer.close()
|
||||
self.sink.close()
|
||||
|
||||
def set_schema(self, schema: pa.Schema):
|
||||
"""Set the schema for the file.
|
||||
Only needed for datasets where we can't learn it from the first record written.
|
||||
|
||||
schema (pa.Schema): The schema to use.
|
||||
"""
|
||||
with self._lock:
|
||||
assert self.schema is None
|
||||
self.schema = schema
|
||||
self.writer = pa.ipc.new_stream(self.sink, schema)
|
||||
|
||||
def _write(self):
|
||||
"""Write the new rows to the file."""
|
||||
if len(self.new_rows) > 0:
|
||||
if self.schema is None:
|
||||
# only need to look at the first row to learn the schema
|
||||
self.set_schema(pa.Table.from_pylist([self.new_rows[0]]).schema)
|
||||
|
||||
self.writer.write_batch(
|
||||
pa.RecordBatch.from_pylist(self.new_rows, schema=self.schema)
|
||||
)
|
||||
self.new_rows = []
|
||||
|
||||
def add_row(self, row_dict: dict):
|
||||
"""Add a row to the arrow file.
|
||||
We will automatically learn the schema from the first row. But all rows must use that schema.
|
||||
"""
|
||||
with self._lock:
|
||||
self.new_rows.append(row_dict)
|
||||
if len(self.new_rows) >= chunk_size:
|
||||
self._write()
|
||||
|
||||
|
||||
class FeatherWriter(ArrowWriter):
|
||||
"""A smaller more interoperable version of arrow files.
|
||||
Uses a temporary .arrow file (which could be huge) but converts to a much smaller (but still fast)
|
||||
feather file.
|
||||
"""
|
||||
|
||||
def __init__(self, file_name: str):
|
||||
super().__init__(file_name + ".arrow")
|
||||
self.base_file_name = file_name
|
||||
|
||||
def close(self):
|
||||
super().close()
|
||||
src_name = self.base_file_name + ".arrow"
|
||||
dest_name = self.base_file_name + ".feather"
|
||||
if os.path.getsize(src_name) == 0:
|
||||
logging.warning(f"Discarding empty file: {src_name}")
|
||||
os.remove(src_name)
|
||||
else:
|
||||
logging.info(f"Compressing log data into {dest_name}")
|
||||
|
||||
# note: must use open_stream, not open_file/read_table because the streaming layout is different
|
||||
# data = feather.read_table(src_name)
|
||||
with pa.memory_map(src_name) as source:
|
||||
array = pa.ipc.open_stream(source).read_all()
|
||||
|
||||
# See https://stackoverflow.com/a/72406099 for more info and performance testing measurements
|
||||
feather.write_feather(array, dest_name, compression="zstd")
|
||||
os.remove(src_name)
|
||||
296
meshtastic/slog/slog.py
Normal file
296
meshtastic/slog/slog.py
Normal file
@@ -0,0 +1,296 @@
|
||||
"""code logging power consumption of meshtastic devices."""
|
||||
|
||||
import atexit
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from functools import reduce
|
||||
from typing import Optional, List, Tuple
|
||||
|
||||
import parse # type: ignore[import-untyped]
|
||||
import platformdirs
|
||||
import pyarrow as pa
|
||||
from pubsub import pub # type: ignore[import-untyped]
|
||||
|
||||
from meshtastic.mesh_interface import MeshInterface
|
||||
from meshtastic.powermon import PowerMeter
|
||||
|
||||
from .arrow import FeatherWriter
|
||||
|
||||
|
||||
def root_dir() -> str:
|
||||
"""Return the root directory for slog files."""
|
||||
|
||||
app_name = "meshtastic"
|
||||
app_author = "meshtastic"
|
||||
app_dir = platformdirs.user_data_dir(app_name, app_author)
|
||||
dir_name = f"{app_dir}/slogs"
|
||||
os.makedirs(dir_name, exist_ok=True)
|
||||
return dir_name
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class LogDef:
|
||||
"""Log definition."""
|
||||
|
||||
code: str # i.e. PM or B or whatever... see meshtastic slog documentation
|
||||
fields: List[Tuple[str, pa.DataType]] # A list of field names and their arrow types
|
||||
format: parse.Parser # A format string that can be used to parse the arguments
|
||||
|
||||
def __init__(self, code: str, fields: List[Tuple[str, pa.DataType]]) -> None:
|
||||
"""Initialize the LogDef object.
|
||||
|
||||
code (str): The code.
|
||||
format (str): The format.
|
||||
|
||||
"""
|
||||
self.code = code
|
||||
self.fields = fields
|
||||
|
||||
fmt = ""
|
||||
for idx, f in enumerate(fields):
|
||||
if idx != 0:
|
||||
fmt += ","
|
||||
|
||||
# make the format string
|
||||
suffix = (
|
||||
"" if f[1] == pa.string() else ":d"
|
||||
) # treat as a string or an int (the only types we have so far)
|
||||
fmt += "{" + f[0] + suffix + "}"
|
||||
self.format = parse.compile(
|
||||
fmt
|
||||
) # We include a catchall matcher at the end - to ignore stuff we don't understand
|
||||
|
||||
|
||||
"""A dictionary mapping from logdef code to logdef"""
|
||||
log_defs = {
|
||||
d.code: d
|
||||
for d in [
|
||||
LogDef("B", [("board_id", pa.uint32()), ("sw_version", pa.string())]),
|
||||
LogDef("PM", [("pm_mask", pa.uint64()), ("pm_reason", pa.string())]),
|
||||
LogDef("PS", [("ps_state", pa.uint32())]),
|
||||
]
|
||||
}
|
||||
log_regex = re.compile(".*S:([0-9A-Za-z]+):(.*)")
|
||||
|
||||
|
||||
class PowerLogger:
|
||||
"""Logs current watts reading periodically using PowerMeter and ArrowWriter."""
|
||||
|
||||
def __init__(self, pMeter: PowerMeter, file_path: str, interval=0.002) -> None:
|
||||
"""Initialize the PowerLogger object."""
|
||||
self.pMeter = pMeter
|
||||
self.writer = FeatherWriter(file_path)
|
||||
self.interval = interval
|
||||
self.is_logging = True
|
||||
self.thread = threading.Thread(
|
||||
target=self._logging_thread, name="PowerLogger", daemon=True
|
||||
)
|
||||
self.thread.start()
|
||||
|
||||
def store_current_reading(self, now: Optional[datetime] = None) -> None:
|
||||
"""Store current power measurement."""
|
||||
if now is None:
|
||||
now = datetime.now()
|
||||
d = {
|
||||
"time": now,
|
||||
"average_mW": self.pMeter.get_average_current_mA(),
|
||||
"max_mW": self.pMeter.get_max_current_mA(),
|
||||
"min_mW": self.pMeter.get_min_current_mA(),
|
||||
}
|
||||
self.pMeter.reset_measurements()
|
||||
self.writer.add_row(d)
|
||||
|
||||
def _logging_thread(self) -> None:
|
||||
"""Background thread for logging the current watts reading."""
|
||||
while self.is_logging:
|
||||
self.store_current_reading()
|
||||
time.sleep(self.interval)
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the PowerLogger and stop logging."""
|
||||
if self.is_logging:
|
||||
self.pMeter.close()
|
||||
self.is_logging = False
|
||||
self.thread.join()
|
||||
self.writer.close()
|
||||
|
||||
|
||||
# FIXME move these defs somewhere else
|
||||
TOPIC_MESHTASTIC_LOG_LINE = "meshtastic.log.line"
|
||||
|
||||
|
||||
class StructuredLogger:
|
||||
"""Sniffs device logs for structured log messages, extracts those into apache arrow format.
|
||||
Also writes the raw log messages to raw.txt"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client: MeshInterface,
|
||||
dir_path: str,
|
||||
power_logger: Optional[PowerLogger] = None,
|
||||
include_raw=True,
|
||||
) -> None:
|
||||
"""Initialize the StructuredLogger object.
|
||||
|
||||
client (MeshInterface): The MeshInterface object to monitor.
|
||||
"""
|
||||
self.client = client
|
||||
self.power_logger = power_logger
|
||||
|
||||
# Setup the arrow writer (and its schema)
|
||||
self.writer = FeatherWriter(f"{dir_path}/slog")
|
||||
all_fields = reduce(
|
||||
(lambda x, y: x + y), map(lambda x: x.fields, log_defs.values())
|
||||
)
|
||||
|
||||
self.include_raw = include_raw
|
||||
if self.include_raw:
|
||||
all_fields.append(("raw", pa.string()))
|
||||
|
||||
# Use timestamp as the first column
|
||||
all_fields.insert(0, ("time", pa.timestamp("us")))
|
||||
|
||||
# pass in our name->type tuples a pa.fields
|
||||
self.writer.set_schema(
|
||||
pa.schema(map(lambda x: pa.field(x[0], x[1]), all_fields))
|
||||
)
|
||||
|
||||
self.raw_file: Optional[
|
||||
io.TextIOWrapper
|
||||
] = open( # pylint: disable=consider-using-with
|
||||
f"{dir_path}/raw.txt", "w", encoding="utf8"
|
||||
)
|
||||
|
||||
# We need a closure here because the subscription API is very strict about exact arg matching
|
||||
def listen_glue(line, interface): # pylint: disable=unused-argument
|
||||
self._onLogMessage(line)
|
||||
|
||||
self._listen_glue = (
|
||||
listen_glue # we must save this so it doesn't get garbage collected
|
||||
)
|
||||
self._listener = pub.subscribe(listen_glue, TOPIC_MESHTASTIC_LOG_LINE)
|
||||
|
||||
def close(self) -> None:
|
||||
"""Stop logging."""
|
||||
pub.unsubscribe(self._listener, TOPIC_MESHTASTIC_LOG_LINE)
|
||||
self.writer.close()
|
||||
f = self.raw_file
|
||||
self.raw_file = None # mark that we are shutting down
|
||||
if f:
|
||||
f.close() # Close the raw.txt file
|
||||
|
||||
def _onLogMessage(self, line: str) -> None:
|
||||
"""Handle log messages.
|
||||
|
||||
line (str): the line of log output
|
||||
"""
|
||||
|
||||
di = {} # the dictionary of the fields we found to log
|
||||
|
||||
m = log_regex.match(line)
|
||||
if m:
|
||||
src = m.group(1)
|
||||
args = m.group(2)
|
||||
logging.debug(f"SLog {src}, args: {args}")
|
||||
|
||||
d = log_defs.get(src)
|
||||
if d:
|
||||
last_field = d.fields[-1]
|
||||
last_is_str = last_field[1] == pa.string()
|
||||
if last_is_str:
|
||||
args += " "
|
||||
# append a space so that if the last arg is an empty str
|
||||
# it will still be accepted as a match for a str
|
||||
|
||||
r = d.format.parse(args) # get the values with the correct types
|
||||
if r:
|
||||
di = r.named
|
||||
if last_is_str:
|
||||
di[last_field[0]] = di[
|
||||
last_field[0]
|
||||
].strip() # remove the trailing space we added
|
||||
if di[last_field[0]] == "":
|
||||
# If the last field is an empty string, remove it
|
||||
del di[last_field[0]]
|
||||
else:
|
||||
logging.warning(f"Failed to parse slog {line} with {d.format}")
|
||||
else:
|
||||
logging.warning(f"Unknown Structured Log: {line}")
|
||||
|
||||
# Store our structured log record
|
||||
if di or self.include_raw:
|
||||
now = datetime.now()
|
||||
di["time"] = now
|
||||
if self.include_raw:
|
||||
di["raw"] = line
|
||||
self.writer.add_row(di)
|
||||
|
||||
# If we have a sibling power logger, make sure we have a power measurement with the EXACT same timestamp
|
||||
if self.power_logger:
|
||||
self.power_logger.store_current_reading(now)
|
||||
|
||||
if self.raw_file:
|
||||
self.raw_file.write(line + "\n") # Write the raw log
|
||||
|
||||
|
||||
class LogSet:
|
||||
"""A complete set of meshtastic log/metadata for a particular run."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client: MeshInterface,
|
||||
dir_name: Optional[str] = None,
|
||||
power_meter: Optional[PowerMeter] = None,
|
||||
) -> None:
|
||||
"""Initialize the PowerMonClient object.
|
||||
|
||||
power (PowerSupply): The power supply object.
|
||||
client (MeshInterface): The MeshInterface object to monitor.
|
||||
"""
|
||||
|
||||
if not dir_name:
|
||||
app_dir = root_dir()
|
||||
dir_name = f"{app_dir}/{datetime.now().strftime('%Y%m%d-%H%M%S')}"
|
||||
os.makedirs(dir_name, exist_ok=True)
|
||||
|
||||
# Also make a 'latest' directory that always points to the most recent logs
|
||||
# symlink might fail on some platforms, if it does fail silently
|
||||
if os.path.exists(f"{app_dir}/latest"):
|
||||
os.unlink(f"{app_dir}/latest")
|
||||
os.symlink(dir_name, f"{app_dir}/latest", target_is_directory=True)
|
||||
|
||||
self.dir_name = dir_name
|
||||
|
||||
logging.info(f"Writing slogs to {dir_name}")
|
||||
|
||||
self.power_logger: Optional[PowerLogger] = (
|
||||
None
|
||||
if not power_meter
|
||||
else PowerLogger(power_meter, f"{self.dir_name}/power")
|
||||
)
|
||||
|
||||
self.slog_logger: Optional[StructuredLogger] = StructuredLogger(
|
||||
client, self.dir_name, power_logger=self.power_logger
|
||||
)
|
||||
|
||||
# Store a lambda so we can find it again to unregister
|
||||
self.atexit_handler = lambda: self.close() # pylint: disable=unnecessary-lambda
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the log set."""
|
||||
|
||||
if self.slog_logger:
|
||||
logging.info(f"Closing slogs in {self.dir_name}")
|
||||
atexit.unregister(
|
||||
self.atexit_handler
|
||||
) # docs say it will silently ignore if not found
|
||||
self.slog_logger.close()
|
||||
if self.power_logger:
|
||||
self.power_logger.close()
|
||||
self.slog_logger = None
|
||||
@@ -39,9 +39,10 @@ class StreamInterface(MeshInterface):
|
||||
self._wantExit = False
|
||||
|
||||
self.is_windows11 = is_windows11()
|
||||
self.cur_log_line = ""
|
||||
|
||||
# FIXME, figure out why daemon=True causes reader thread to exit too early
|
||||
self._rxThread = threading.Thread(target=self.__reader, args=(), daemon=True)
|
||||
self._rxThread = threading.Thread(target=self.__reader, args=(), daemon=True, name="stream reader")
|
||||
|
||||
MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto, noNodes=noNodes)
|
||||
|
||||
@@ -124,6 +125,23 @@ class StreamInterface(MeshInterface):
|
||||
if self._rxThread != threading.current_thread():
|
||||
self._rxThread.join() # wait for it to exit
|
||||
|
||||
def _handleLogByte(self, b):
|
||||
"""Handle a byte that is part of a log message from the device."""
|
||||
|
||||
utf = "?" # assume we might fail
|
||||
try:
|
||||
utf = b.decode("utf-8")
|
||||
except:
|
||||
pass
|
||||
|
||||
if utf == "\r":
|
||||
pass # ignore
|
||||
elif utf == "\n":
|
||||
self._handleLogLine(self.cur_log_line)
|
||||
self.cur_log_line = ""
|
||||
else:
|
||||
self.cur_log_line += utf
|
||||
|
||||
def __reader(self):
|
||||
"""The reader thread that reads bytes from our stream"""
|
||||
logging.debug("in __reader()")
|
||||
@@ -146,11 +164,9 @@ class StreamInterface(MeshInterface):
|
||||
if ptr == 0: # looking for START1
|
||||
if c != START1:
|
||||
self._rxBuf = empty # failed to find start
|
||||
if self.debugOut is not None:
|
||||
try:
|
||||
self.debugOut.write(b.decode("utf-8"))
|
||||
except:
|
||||
self.debugOut.write("?")
|
||||
# This must be a log message from the device
|
||||
|
||||
self._handleLogByte(b)
|
||||
|
||||
elif ptr == 1: # looking for START2
|
||||
if c != START2:
|
||||
|
||||
BIN
meshtastic/tests/slog-test-input/power.feather
Normal file
BIN
meshtastic/tests/slog-test-input/power.feather
Normal file
Binary file not shown.
349
meshtastic/tests/slog-test-input/raw.txt
Normal file
349
meshtastic/tests/slog-test-input/raw.txt
Normal file
@@ -0,0 +1,349 @@
|
||||
[RadioIf] getFromRadio=STATE_SEND_PACKETS
|
||||
[RadioIf] Can not send yet, busyRx
|
||||
Telling client we have new packets 3
|
||||
BLE notify fromNum
|
||||
[Blink] S:PM:0x000009c8,
|
||||
[Blink] S:PM:0x00000948,
|
||||
toRadioWriteCb data 0x2001ffea, len 26
|
||||
PACKET FROM PHONE (id=0x8f26f64c fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP)
|
||||
[RadioIf] Ignore false preamble detection.
|
||||
[RadioIf] S:PM:0x00000940,
|
||||
[RadioIf] Starting low level send (id=0x8f26f64b fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x8 encrypted hopStart=3 priority=64)
|
||||
[RadioIf] S:PM:0x00000950,
|
||||
[RadioIf] (bw=250, sf=11, cr=4/5) packet symLen=8 ms, payloadSize=25, time 419 ms
|
||||
[RadioIf] AirTime - Packet transmitted : 419ms
|
||||
Telling client we have new packets 4
|
||||
BLE notify fromNum
|
||||
[Router] Add packet record (id=0x8f26f64c fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725846)
|
||||
[Router] handleReceived(REMOTE) (id=0x8f26f64c fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725846)
|
||||
[Router] Module 'powerstress' wantsPacket=1
|
||||
[Router] Received powerstress from=0x0, id=0x8f26f64c, portnum=74, payloadlen=2
|
||||
[Router] Received PowerStress cmd=1
|
||||
[Router] S:B:9,2.3.15.177d19ac
|
||||
[Router] Asked module 'powerstress' to send a response
|
||||
[Router] Module 'powerstress' handled and skipped other processing
|
||||
[Router] No one responded, send a nak
|
||||
[Router] Alloc an err=8,to=0x67f63246,idFrom=0x8f26f64c,id=0x5fa26660
|
||||
[Router] Enqueued local (id=0x5fa26660 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64c rxtime=1720725847 priority=120)
|
||||
[Router] Rx someone rebroadcasting for us (id=0x5fa26660 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64c rxtime=1720725847 priority=120)
|
||||
[Router] didn't find pending packet
|
||||
[Router] Add packet record (id=0x5fa26660 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64c rxtime=1720725847 priority=120)
|
||||
[Router] handleReceived(REMOTE) (id=0x5fa26660 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64c rxtime=1720725847 priority=120)
|
||||
[Router] Module 'canned' wantsPacket=1
|
||||
[Router] showing standard frames
|
||||
[Router] Showing 0 module frames
|
||||
[Router] Total frame count: 103
|
||||
[Router] Added modules. numframes: 0
|
||||
[Router] Finished building frames. numframes: 7
|
||||
[Router] Module 'canned' considered
|
||||
[Router] Module 'routing' wantsPacket=1
|
||||
[Router] Received routing from=0x67f63246, id=0x5fa26660, portnum=5, payloadlen=2
|
||||
[Router] Routing sniffing (id=0x5fa26660 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64c rxtime=1720725847 priority=120)
|
||||
[Router] Received a nak for 0x8f26f64c, stopping retransmissions
|
||||
[Router] Delivering rx packet (id=0x5fa26660 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64c rxtime=1720725847 priority=120)
|
||||
[Router] Update DB node 0x67f63246, rx_time=1720725847
|
||||
[Router] Forwarding to phone (id=0x5fa26660 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64c rxtime=1720725847 priority=120)
|
||||
[Router] Module 'routing' considered
|
||||
[RadioIf] Completed sending (id=0x8f26f64b fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x8 encrypted hopStart=3 priority=64)
|
||||
[RadioIf] S:PM:0x00000940,
|
||||
[RadioIf] S:PM:0x00000948,
|
||||
Telling client we have new packets 5
|
||||
BLE notify fromNum
|
||||
[RadioIf] S:PM:0x00000940,
|
||||
[RadioIf] Starting low level send (id=0x5fa2665f fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x8 encrypted hopStart=3 priority=10)
|
||||
[RadioIf] S:PM:0x00000950,
|
||||
[RadioIf] (bw=250, sf=11, cr=4/5) packet symLen=8 ms, payloadSize=66, time 722 ms
|
||||
[RadioIf] AirTime - Packet transmitted : 722ms
|
||||
[Blink] S:PM:0x000009d0,
|
||||
[Blink] S:PM:0x00000950,
|
||||
getFromRadio=STATE_SEND_PACKETS
|
||||
getFromRadio=STATE_SEND_PACKETS
|
||||
phone downloaded packet (id=0x5fa26660 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64c rxtime=1720725847 priority=120)
|
||||
toRadioWriteCb data 0x2001ffea, len 31
|
||||
PACKET FROM PHONE (id=0x8f26f64d fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP)
|
||||
Enqueued local (id=0x8f26f64d fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725848)
|
||||
Telling client we have new packets 6
|
||||
BLE notify fromNum
|
||||
[Router] Add packet record (id=0x8f26f64d fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725848)
|
||||
[Router] handleReceived(REMOTE) (id=0x8f26f64d fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725848)
|
||||
[Router] Module 'powerstress' wantsPacket=1
|
||||
[Router] Received powerstress from=0x0, id=0x8f26f64d, portnum=74, payloadlen=7
|
||||
[Router] Received PowerStress cmd=48
|
||||
[Router] Asked module 'powerstress' to send a response
|
||||
[Router] Module 'powerstress' handled and skipped other processing
|
||||
[Router] No one responded, send a nak
|
||||
[Router] Alloc an err=8,to=0x67f63246,idFrom=0x8f26f64d,id=0x5fa26661
|
||||
[Router] Enqueued local (id=0x5fa26661 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64d rxtime=1720725848 priority=120)
|
||||
[Router] Rx someone rebroadcasting for us (id=0x5fa26661 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64d rxtime=1720725848 priority=120)
|
||||
[Router] didn't find pending packet
|
||||
[Router] Add packet record (id=0x5fa26661 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64d rxtime=1720725848 priority=120)
|
||||
[Router] handleReceived(REMOTE) (id=0x5fa26661 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64d rxtime=1720725848 priority=120)
|
||||
[Router] Module 'canned' wantsPacket=1
|
||||
[Router] Showing 0 module frames
|
||||
[Router] Total frame count: 103
|
||||
[Router] Added modules. numframes: 0
|
||||
[Router] Finished building frames. numframes: 7
|
||||
[Router] Module 'canned' considered
|
||||
[Router] Module 'routing' wantsPacket=1
|
||||
[Router] Received routing from=0x67f63246, id=0x5fa26661, portnum=5, payloadlen=2
|
||||
[Router] Routing sniffing (id=0x5fa26661 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64d rxtime=1720725848 priority=120)
|
||||
[Router] Received a nak for 0x8f26f64d, stopping retransmissions
|
||||
[Router] Delivering rx packet (id=0x5fa26661 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64d rxtime=1720725848 priority=120)
|
||||
[Router] Update DB node 0x67f63246, rx_time=1720725848
|
||||
[Router] Forwarding to phone (id=0x5fa26661 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64d rxtime=1720725848 priority=120)
|
||||
[Router] Module 'routing' considered
|
||||
[PowerStressModule] S:PS:48
|
||||
[PowerStressModule] S:PM:0x000009d0,
|
||||
[RadioIf] Completed sending (id=0x5fa2665f fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x8 encrypted hopStart=3 priority=10)
|
||||
[RadioIf] S:PM:0x000009c0,
|
||||
[RadioIf] S:PM:0x000009c8,
|
||||
Telling client we have new packets 7
|
||||
BLE notify fromNum
|
||||
getFromRadio=STATE_SEND_PACKETS
|
||||
phone downloaded packet (id=0x5fa26661 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64d rxtime=1720725848 priority=120)
|
||||
toRadioWriteCb data 0x2001ffea, len 31
|
||||
PACKET FROM PHONE (id=0x8f26f64e fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP)
|
||||
Enqueued local (id=0x8f26f64e fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725853)
|
||||
Telling client we have new packets 8
|
||||
[Router] Add packet record (id=0x8f26f64e fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725853)
|
||||
[Router] handleReceived(REMOTE) (id=0x8f26f64e fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725853)
|
||||
[Router] Module 'powerstress' wantsPacket=1
|
||||
[Router] Received powerstress from=0x0, id=0x8f26f64e, portnum=74, payloadlen=7
|
||||
[Router] Received PowerStress cmd=49
|
||||
[Router] PowerStress operation 48 already in progress! Can't start new command
|
||||
[Router] Asked module 'powerstress' to send a response
|
||||
[Router] Module 'powerstress' handled and skipped other processing
|
||||
[Router] No one responded, send a nak
|
||||
[Router] Alloc an err=8,to=0x67f63246,idFrom=0x8f26f64e,id=0x5fa26662
|
||||
[Router] Enqueued local (id=0x5fa26662 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64e rxtime=1720725853 priority=120)
|
||||
[Router] Rx someone rebroadcasting for us (id=0x5fa26662 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64e rxtime=1720725853 priority=120)
|
||||
[Router] didn't find pending packet
|
||||
[Router] Add packet record (id=0x5fa26662 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64e rxtime=1720725853 priority=120)
|
||||
[Router] handleReceived(REMOTE) (id=0x5fa26662 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64e rxtime=1720725853 priority=120)
|
||||
[Router] Module 'canned' wantsPacket=1
|
||||
[Router] showing standard frames
|
||||
[Router] Showing 0 module frames
|
||||
[Router] Total frame count: 103
|
||||
[Router] Added modules. numframes: 0
|
||||
[Router] Finished building frames. numframes: 7
|
||||
[Router] Module 'canned' considered
|
||||
[Router] Module 'routing' wantsPacket=1
|
||||
[Router] Received routing from=0x67f63246, id=0x5fa26662, portnum=5, payloadlen=2
|
||||
[Router] Routing sniffing (id=0x5fa26662 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64e rxtime=1720725853 priority=120)
|
||||
[Router] Received a nak for 0x8f26f64e, stopping retransmissions
|
||||
[Router] Delivering rx packet (id=0x5fa26662 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64e rxtime=1720725853 priority=120)
|
||||
[Router] Update DB node 0x67f63246, rx_time=1720725853
|
||||
[Router] Forwarding to phone (id=0x5fa26662 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64e rxtime=1720725853 priority=120)
|
||||
[Router] Module 'routing' considered
|
||||
[PowerStressModule] S:PS:0
|
||||
Telling client we have new packets 9
|
||||
BLE notify fromNum
|
||||
[Power] Battery: usbPower=0, isCharging=0, batMv=3191, batPct=4
|
||||
getFromRadio=STATE_SEND_PACKETS
|
||||
phone downloaded packet (id=0x5fa26662 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64e rxtime=1720725853 priority=120)
|
||||
toRadioWriteCb data 0x2001ffea, len 31
|
||||
PACKET FROM PHONE (id=0x8f26f64f fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP)
|
||||
Enqueued local (id=0x8f26f64f fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725858)
|
||||
Telling client we have new packets 10
|
||||
BLE notify fromNum
|
||||
[Router] Add packet record (id=0x8f26f64f fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725858)
|
||||
[Router] handleReceived(REMOTE) (id=0x8f26f64f fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725858)
|
||||
[Router] Module 'powerstress' wantsPacket=1
|
||||
[Router] Received powerstress from=0x0, id=0x8f26f64f, portnum=74, payloadlen=7
|
||||
[Router] Received PowerStress cmd=80
|
||||
[Router] Asked module 'powerstress' to send a response
|
||||
[Router] Module 'powerstress' handled and skipped other processing
|
||||
[Router] No one responded, send a nak
|
||||
[Router] Alloc an err=8,to=0x67f63246,idFrom=0x8f26f64f,id=0x5fa26663
|
||||
[Router] Enqueued local (id=0x5fa26663 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64f rxtime=1720725858 priority=120)
|
||||
[Router] Rx someone rebroadcasting for us (id=0x5fa26663 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64f rxtime=1720725858 priority=120)
|
||||
[Router] didn't find pending packet
|
||||
[Router] Add packet record (id=0x5fa26663 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64f rxtime=1720725858 priority=120)
|
||||
[Router] handleReceived(REMOTE) (id=0x5fa26663 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64f rxtime=1720725858 priority=120)
|
||||
[Router] Module 'canned' wantsPacket=1
|
||||
[Router] showing standard frames
|
||||
[Router] Showing 0 module frames
|
||||
[Router] Added modules. numframes: 0
|
||||
[Router] Finished building frames. numframes: 7
|
||||
[Router] Module 'canned' considered
|
||||
[Router] Module 'routing' wantsPacket=1
|
||||
[Router] Received routing from=0x67f63246, id=0x5fa26663, portnum=5, payloadlen=2
|
||||
[Router] Routing sniffing (id=0x5fa26663 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64f rxtime=1720725858 priority=120)
|
||||
[Router] Received a nak for 0x8f26f64f, stopping retransmissions
|
||||
[Router] Delivering rx packet (id=0x5fa26663 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64f rxtime=1720725858 priority=120)
|
||||
[Router] Update DB node 0x67f63246, rx_time=1720725858
|
||||
[Router] Forwarding to phone (id=0x5fa26663 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64f rxtime=1720725858 priority=120)
|
||||
[Router] Module 'routing' considered
|
||||
[PowerStressModule] S:PS:80
|
||||
[PowerStressModule] S:PM:0x00000988,
|
||||
[PowerStressModule] Disable NRF52 bluetooth
|
||||
Telling client we have new packets 11
|
||||
BLE notify fromNum
|
||||
[DeviceTelemetryModule] (Sending): air_util_tx=0.031694, channel_utilization=1.901667, battery_level=4, voltage=3.191000, uptime=50
|
||||
[DeviceTelemetryModule] updateTelemetry LOCAL
|
||||
[DeviceTelemetryModule] Node status update: 1 online, 82 total
|
||||
[DeviceTelemetryModule] Sending packet to mesh
|
||||
[DeviceTelemetryModule] Update DB node 0x67f63246, rx_time=1720725859
|
||||
[DeviceTelemetryModule] handleReceived(LOCAL) (id=0x5fa26664 fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x0 Portnum=67 rxtime=1720725859 priority=10)
|
||||
[DeviceTelemetryModule] No modules interested in portnum=67, src=LOCAL
|
||||
[DeviceTelemetryModule] localSend to channel 0
|
||||
[DeviceTelemetryModule] Add packet record (id=0x5fa26664 fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x0 Portnum=67 rxtime=1720725859 priority=10)
|
||||
[DeviceTelemetryModule] Expanding short PSK #1
|
||||
[DeviceTelemetryModule] Using AES128 key!
|
||||
[DeviceTelemetryModule] nRF52 encrypt fr=67f63246, num=5fa26664, numBytes=30!
|
||||
[DeviceTelemetryModule] enqueuing for send (id=0x5fa26664 fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x8 encrypted rxtime=1720725859 hopStart=3 priority=10)
|
||||
[DeviceTelemetryModule] txGood=2,rxGood=0,rxBad=0
|
||||
[DeviceTelemetryModule] Using channel 0 (hash 0x8)
|
||||
[DeviceTelemetryModule] Expanding short PSK #1
|
||||
[DeviceTelemetryModule] Using AES128 key!
|
||||
[DeviceTelemetryModule] nRF52 encrypt fr=67f63246, num=5fa26664, numBytes=30!
|
||||
[DeviceTelemetryModule] decoded message (id=0x5fa26664 fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x0 Portnum=67 rxtime=1720725859 hopStart=3 priority=10)
|
||||
Telling client we have new packets 13
|
||||
BLE notify fromNum
|
||||
[RadioIf] Can not send yet, busyRx
|
||||
[RadioIf] Can not send yet, busyRx
|
||||
[RadioIf] Can not send yet, busyRx
|
||||
[RadioIf] Can not send yet, busyRx
|
||||
getFromRadio=STATE_SEND_PACKETS
|
||||
[RadioIf] Can not send yet, busyRx
|
||||
[RadioIf] Can not send yet, busyRx
|
||||
[RadioIf] Can not send yet, busyRx
|
||||
[RadioIf] Can not send yet, busyRx
|
||||
[RadioIf] Can not send yet, busyRx
|
||||
getFromRadio=STATE_SEND_PACKETS
|
||||
phone downloaded packet (id=0x5fa26663 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64f rxtime=1720725858 priority=120)
|
||||
[RadioIf] Ignore false preamble detection.
|
||||
[RadioIf] S:PM:0x00000980,
|
||||
[RadioIf] Starting low level send (id=0x5fa26664 fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x8 encrypted rxtime=1720725859 hopStart=3 priority=10)
|
||||
[RadioIf] S:PM:0x00000990,
|
||||
[RadioIf] (bw=250, sf=11, cr=4/5) packet symLen=8 ms, payloadSize=46, time 575 ms
|
||||
[RadioIf] AirTime - Packet transmitted : 575ms
|
||||
getFromRadio=STATE_SEND_PACKETS
|
||||
phone downloaded packet (id=0x5fa26664 fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x0 Portnum=67 rxtime=1720725859 hopStart=3 priority=10)
|
||||
[RadioIf] Completed sending (id=0x5fa26664 fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x8 encrypted rxtime=1720725859 hopStart=3 priority=10)
|
||||
[RadioIf] S:PM:0x00000980,
|
||||
[RadioIf] S:PM:0x00000988,
|
||||
toRadioWriteCb data 0x2001ffea, len 31
|
||||
PACKET FROM PHONE (id=0x8f26f650 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP)
|
||||
Enqueued local (id=0x8f26f650 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725864)
|
||||
Telling client we have new packets 14
|
||||
BLE notify fromNum
|
||||
[Router] Add packet record (id=0x8f26f650 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725864)
|
||||
[Router] handleReceived(REMOTE) (id=0x8f26f650 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725864)
|
||||
[Router] Module 'powerstress' wantsPacket=1
|
||||
[Router] Received powerstress from=0x0, id=0x8f26f650, portnum=74, payloadlen=7
|
||||
[Router] Received PowerStress cmd=81
|
||||
[Router] PowerStress operation 80 already in progress! Can't start new command
|
||||
[Router] Asked module 'powerstress' to send a response
|
||||
[Router] Module 'powerstress' handled and skipped other processing
|
||||
[Router] No one responded, send a nak
|
||||
[Router] Alloc an err=8,to=0x67f63246,idFrom=0x8f26f650,id=0x5fa26665
|
||||
[Router] Enqueued local (id=0x5fa26665 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f650 rxtime=1720725864 priority=120)
|
||||
[Router] Rx someone rebroadcasting for us (id=0x5fa26665 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f650 rxtime=1720725864 priority=120)
|
||||
[Router] didn't find pending packet
|
||||
[Router] Add packet record (id=0x5fa26665 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f650 rxtime=1720725864 priority=120)
|
||||
[Router] handleReceived(REMOTE) (id=0x5fa26665 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f650 rxtime=1720725864 priority=120)
|
||||
[Router] Module 'canned' wantsPacket=1
|
||||
[Router] showing standard frames
|
||||
[Router] Showing 0 module frames
|
||||
[Router] Total frame count: 103
|
||||
[Router] Added modules. numframes: 0
|
||||
[Router] Finished building frames. numframes: 7
|
||||
[Router] Module 'canned' considered
|
||||
[Router] Module 'routing' wantsPacket=1
|
||||
[Router] Received routing from=0x67f63246, id=0x5fa26665, portnum=5, payloadlen=2
|
||||
[Router] Routing sniffing (id=0x5fa26665 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f650 rxtime=1720725864 priority=120)
|
||||
[Router] Received a nak for 0x8f26f650, stopping retransmissions
|
||||
[Router] Delivering rx packet (id=0x5fa26665 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f650 rxtime=1720725864 priority=120)
|
||||
[Router] Update DB node 0x67f63246, rx_time=1720725864
|
||||
[Router] Forwarding to phone (id=0x5fa26665 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f650 rxtime=1720725864 priority=120)
|
||||
[Router] Module 'routing' considered
|
||||
Telling client we have new packets 15
|
||||
BLE notify fromNum
|
||||
getFromRadio=STATE_SEND_PACKETS
|
||||
[PowerStressModule] S:PS:0
|
||||
getFromRadio=STATE_SEND_PACKETS
|
||||
phone downloaded packet (id=0x5fa26665 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f650 rxtime=1720725864 priority=120)
|
||||
toRadioWriteCb data 0x2001ffea, len 31
|
||||
PACKET FROM PHONE (id=0x8f26f651 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP)
|
||||
Enqueued local (id=0x8f26f651 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725869)
|
||||
Telling client we have new packets 16
|
||||
BLE notify fromNum
|
||||
[Router] Add packet record (id=0x8f26f651 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725869)
|
||||
[Router] handleReceived(REMOTE) (id=0x8f26f651 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725869)
|
||||
[Router] Module 'powerstress' wantsPacket=1
|
||||
[Router] Received powerstress from=0x0, id=0x8f26f651, portnum=74, payloadlen=7
|
||||
[Router] Received PowerStress cmd=34
|
||||
[Router] Asked module 'powerstress' to send a response
|
||||
[Router] Module 'powerstress' handled and skipped other processing
|
||||
[Router] No one responded, send a nak
|
||||
[Router] Alloc an err=8,to=0x67f63246,idFrom=0x8f26f651,id=0x5fa26666
|
||||
[Router] Enqueued local (id=0x5fa26666 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f651 rxtime=1720725869 priority=120)
|
||||
[Router] Rx someone rebroadcasting for us (id=0x5fa26666 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f651 rxtime=1720725869 priority=120)
|
||||
[Router] didn't find pending packet
|
||||
[Router] Add packet record (id=0x5fa26666 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f651 rxtime=1720725869 priority=120)
|
||||
[Router] handleReceived(REMOTE) (id=0x5fa26666 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f651 rxtime=1720725869 priority=120)
|
||||
[Router] Module 'canned' wantsPacket=1
|
||||
[Router] showing standard frames
|
||||
[Router] Showing 0 module frames
|
||||
[Router] Total frame count: 103
|
||||
[Router] Added modules. numframes: 0
|
||||
[Router] Finished building frames. numframes: 7
|
||||
[Router] Module 'canned' considered
|
||||
[Router] Module 'routing' wantsPacket=1
|
||||
[Router] Received routing from=0x67f63246, id=0x5fa26666, portnum=5, payloadlen=2
|
||||
[Router] Routing sniffing (id=0x5fa26666 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f651 rxtime=1720725869 priority=120)
|
||||
[Router] Received a nak for 0x8f26f651, stopping retransmissions
|
||||
[Router] Delivering rx packet (id=0x5fa26666 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f651 rxtime=1720725869 priority=120)
|
||||
[Router] Update DB node 0x67f63246, rx_time=1720725869
|
||||
[Router] Forwarding to phone (id=0x5fa26666 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f651 rxtime=1720725869 priority=120)
|
||||
[Router] Module 'routing' considered
|
||||
[PowerStressModule] S:PS:34
|
||||
[PowerStressModule] getFromRadio=STATE_SEND_PACKETS
|
||||
[PowerStressModule] getFromRadio=STATE_SEND_PACKETS
|
||||
[PowerStressModule] phone downloaded packet (id=0x5fa26666 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f651 rxtime=1720725869 priority=120)
|
||||
Telling client we have new packets 17
|
||||
BLE notify fromNum
|
||||
[Power] Battery: usbPower=0, isCharging=0, batMv=3202, batPct=5
|
||||
[PowerStressModule] S:PS:0
|
||||
toRadioWriteCb data 0x2001ffea, len 31
|
||||
PACKET FROM PHONE (id=0x8f26f652 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP)
|
||||
Enqueued local (id=0x8f26f652 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725874)
|
||||
Telling client we have new packets 18
|
||||
[Router] Add packet record (id=0x8f26f652 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725874)
|
||||
[Router] handleReceived(REMOTE) (id=0x8f26f652 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725875)
|
||||
[Router] Module 'powerstress' wantsPacket=1
|
||||
[Router] Received powerstress from=0x0, id=0x8f26f652, portnum=74, payloadlen=7
|
||||
[Router] Received PowerStress cmd=32
|
||||
[Router] Asked module 'powerstress' to send a response
|
||||
[Router] Module 'powerstress' handled and skipped other processing
|
||||
[Router] No one responded, send a nak
|
||||
[Router] Alloc an err=8,to=0x67f63246,idFrom=0x8f26f652,id=0x5fa26667
|
||||
[Router] Enqueued local (id=0x5fa26667 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f652 rxtime=1720725875 priority=120)
|
||||
[Router] Rx someone rebroadcasting for us (id=0x5fa26667 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f652 rxtime=1720725875 priority=120)
|
||||
[Router] didn't find pending packet
|
||||
[Router] Add packet record (id=0x5fa26667 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f652 rxtime=1720725875 priority=120)
|
||||
[Router] handleReceived(REMOTE) (id=0x5fa26667 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f652 rxtime=1720725875 priority=120)
|
||||
[Router] Module 'canned' wantsPacket=1
|
||||
[Router] showing standard frames
|
||||
[Router] Showing 0 module frames
|
||||
[Router] Total frame count: 103
|
||||
[Router] Added modules. numframes: 0
|
||||
[Router] Finished building frames. numframes: 7
|
||||
[Router] Module 'canned' considered
|
||||
[Router] Module 'routing' wantsPacket=1
|
||||
[Router] Received routing from=0x67f63246, id=0x5fa26667, portnum=5, payloadlen=2
|
||||
[Router] Routing sniffing (id=0x5fa26667 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f652 rxtime=1720725875 priority=120)
|
||||
[Router] Received a nak for 0x8f26f652, stopping retransmissions
|
||||
[Router] Delivering rx packet (id=0x5fa26667 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f652 rxtime=1720725875 priority=120)
|
||||
[Router] Update DB node 0x67f63246, rx_time=1720725875
|
||||
[Router] Forwarding to phone (id=0x5fa26667 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f652 rxtime=1720725875 priority=120)
|
||||
[Router] Module 'routing' considered
|
||||
[PowerStressModule] S:PS:32
|
||||
Telling client we have new packets 19
|
||||
BLE notify fromNum
|
||||
getFromRadio=STATE_SEND_PACKETS
|
||||
phone downloaded packet (id=0x5fa26667 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f652 rxtime=1720725875 priority=120)
|
||||
toRadioWriteCb data 0x2001ffea, len 2
|
||||
Disconnecting from phone
|
||||
[PowerStressModule] S:PS:0
|
||||
BIN
meshtastic/tests/slog-test-input/slog.feather
Normal file
BIN
meshtastic/tests/slog-test-input/slog.feather
Normal file
Binary file not shown.
25
meshtastic/tests/test_analysis.py
Normal file
25
meshtastic/tests/test_analysis.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""Test analysis processing."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from meshtastic.analysis.__main__ import main
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_analysis(caplog):
|
||||
"""Test analysis processing"""
|
||||
|
||||
cur_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
slog_input_dir = os.path.join(cur_dir, "slog-test-input")
|
||||
|
||||
sys.argv = ["fakescriptname", "--no-server", "--slog", slog_input_dir]
|
||||
|
||||
with caplog.at_level(logging.DEBUG):
|
||||
logging.getLogger().propagate = True # Let our testing framework see our logs
|
||||
main()
|
||||
|
||||
assert "Exiting without running visualization server" in caplog.text
|
||||
@@ -11,6 +11,8 @@ from ..protobuf import mesh_pb2, config_pb2
|
||||
from .. import BROADCAST_ADDR, LOCAL_ADDR
|
||||
from ..mesh_interface import MeshInterface, _timeago
|
||||
from ..node import Node
|
||||
from ..slog import LogSet
|
||||
from ..powermon import SimPowerSupply
|
||||
|
||||
# TODO
|
||||
# from ..config import Config
|
||||
@@ -47,11 +49,15 @@ def test_MeshInterface(capsys):
|
||||
|
||||
iface.localNode.localConfig.lora.CopyFrom(config_pb2.Config.LoRaConfig())
|
||||
|
||||
# Also get some coverage of the structured logging/power meter stuff by turning it on as well
|
||||
log_set = LogSet(iface, None, SimPowerSupply())
|
||||
|
||||
iface.showInfo()
|
||||
iface.localNode.showInfo()
|
||||
iface.showNodes()
|
||||
iface.sendText("hello")
|
||||
iface.close()
|
||||
log_set.close()
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r"Owner: None \(None\)", out, re.MULTILINE)
|
||||
assert re.search(r"Nodes", out, re.MULTILINE)
|
||||
@@ -179,7 +185,7 @@ 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
|
||||
|
||||
@@ -25,9 +25,13 @@ from meshtastic.supported_device import supported_devices
|
||||
from meshtastic.version import get_active_version
|
||||
|
||||
"""Some devices such as a seger jlink or st-link we never want to accidentally open
|
||||
0x1915 NordicSemi (PPK2)
|
||||
0483 STMicroelectronics ST-LINK/V2
|
||||
0136 SEGGER J-Link
|
||||
1915 NordicSemi (PPK2)
|
||||
0925 Lakeview Research Saleae Logic (logic analyzer)
|
||||
04b4:602a Cypress Semiconductor Corp. Hantek DSO-6022BL (oscilloscope)
|
||||
"""
|
||||
blacklistVids = dict.fromkeys([0x1366, 0x0483, 0x1915])
|
||||
blacklistVids = dict.fromkeys([0x1366, 0x0483, 0x1915, 0x0925, 0x04b4])
|
||||
|
||||
"""Some devices are highly likely to be meshtastic.
|
||||
0x239a RAK4631
|
||||
@@ -266,9 +270,10 @@ class Acknowledgment:
|
||||
class DeferredExecution:
|
||||
"""A thread that accepts closures to run, and runs them as they are received"""
|
||||
|
||||
def __init__(self, name=None):
|
||||
def __init__(self, name):
|
||||
self.queue = Queue()
|
||||
self.thread = threading.Thread(target=self._run, args=(), name=name)
|
||||
# this thread must be marked as daemon, otherwise it will prevent clients from exiting
|
||||
self.thread = threading.Thread(target=self._run, args=(), name=name, daemon=True)
|
||||
self.thread.daemon = True
|
||||
self.thread.start()
|
||||
|
||||
|
||||
2783
poetry.lock
generated
2783
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
Submodule protobufs updated: 0c90a6814f...d0fe91ab99
@@ -1,13 +1,13 @@
|
||||
[tool.poetry]
|
||||
name = "meshtastic"
|
||||
version = "2.3.12"
|
||||
version = "2.4.2"
|
||||
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
|
||||
python = "^3.9,<3.13" # 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"
|
||||
@@ -22,6 +22,10 @@ pypubsub = "^4.0.3"
|
||||
bleak = "^0.21.1"
|
||||
packaging = "^24.0"
|
||||
print-color = "^0.4.6"
|
||||
dash = { version = "^2.17.1", optional = true }
|
||||
pytap2 = { version = "^2.3.0", optional = true }
|
||||
dash-bootstrap-components = { version = "^1.6.0", optional = true }
|
||||
pandas = { version = "^2.2.2", optional = true }
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
hypothesis = "^6.103.2"
|
||||
@@ -30,7 +34,6 @@ 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"
|
||||
@@ -39,13 +42,45 @@ types-tabulate = "^0.9.0.20240106"
|
||||
types-requests = "^2.31.0.20240406"
|
||||
types-setuptools = "^69.5.0.20240423"
|
||||
types-pyyaml = "^6.0.12.20240311"
|
||||
pyarrow-stubs = "^10.0.1.7"
|
||||
pandas-stubs = "^2.2.2.240603"
|
||||
|
||||
[tool.poetry.group.powermon]
|
||||
optional = true
|
||||
|
||||
[tool.poetry.group.powermon.dependencies]
|
||||
riden = { git = "https://github.com/geeksville/riden.git#1.2.1" }
|
||||
ppk2-api = "^0.9.2"
|
||||
parse = "^1.20.2"
|
||||
pyarrow = "^16.1.0"
|
||||
platformdirs = "^4.2.2"
|
||||
|
||||
# If you are doing power analysis you might want these extra devtools
|
||||
[tool.poetry.group.analysis]
|
||||
optional = true
|
||||
|
||||
[tool.poetry.group.analysis.dependencies]
|
||||
jupyterlab = "^4.2.2"
|
||||
matplotlib = "^3.9.0"
|
||||
ipympl = "^0.9.4"
|
||||
ipywidgets = "^8.1.3"
|
||||
jupyterlab-widgets = "^3.0.11"
|
||||
|
||||
[tool.poetry.extras]
|
||||
tunnel = ["pytap2"]
|
||||
analysis = ["dash", "dash-bootstrap-components", "pandas", "pandas-stubs"]
|
||||
|
||||
[tool.poetry.scripts]
|
||||
meshtastic = "meshtastic.__main__:main"
|
||||
mesh-tunnel = "meshtastic.__main__:tunnelMain [tunnel]"
|
||||
mesh-analysis = "meshtastic.analysis.__main__:main [analysis]"
|
||||
|
||||
# "Poe the poet" (optional) provides an easy way of running non python tools inside the poetry virtualenv
|
||||
# if you would like to use it run "pipx install poe"
|
||||
# then you can do stuff like "poe code" to run vscode inside this environment
|
||||
[tool.poe.tasks]
|
||||
code = "code ."
|
||||
juypter = "poetry run jupyter lab"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
|
||||
Reference in New Issue
Block a user